Skip to content

RG-T55 RMS First pass - #494

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

RG-T55 RMS First pass#494
ucswift merged 1 commit into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

This PR delivers the first major pass of the new Records (RMS) module as the successor to legacy Logs, including department activation/cutover, record lifecycle management, revisions, search, permissions, workflows, reporting, attachment hygiene/scanning, and department branding for records and operational emails.

What changed

Added the new Records module foundation

  • Introduced a full Records domain model for department records, including:
    • operational record headers and typed details
    • participants and unit responses
    • attachments
    • immutable revisions and revision snapshots
    • external references
    • access auditing
    • group visibility scope and shares
    • print layouts
    • search projections and index state
  • Added the main IRecordsService and related services for:
    • draft creation and saving
    • submit/review/approve/finalize workflows
    • amendments, voiding, cancellation, and reassignment
    • revision history and diffs
    • attachment handling
    • accountability/reminders
    • reporting
    • search indexing and search access

Added department-level Records activation and cutover

  • Added a feature-flagged Records system with separate department activation state.
  • Introduced a cutover model and service to:
    • preview activation
    • migrate relevant legacy permission rows into Records permissions
    • activate Records for a department
    • block new legacy Log and UnitLog writes after activation
    • support rollback decisions and clean revert handling
  • Updated legacy log and unit log services/controllers so that, once Records is active, old logs become read-only rather than editable.

Added Records permissions and claims

  • Added new permission types, actions, resources, policies, and helper methods for Records-specific access.
  • Added support for a new permission action mode that includes:
    • department admins
    • group admins
    • selected roles
  • Wired Records permissions into claims generation, web authorization helpers, and both Web and Web.Services startup policy registration.
  • Added UI support for managing Records permissions in Security settings.

Added Records UI in the web app

  • Added a new RecordsController and Records views for:
    • queue/list view
    • activation
    • create/edit
    • details
    • revision view
    • diff view
    • print/PDF views
    • settings
    • accountability
  • Updated navigation so that when Records is enabled, it takes the Logs sidebar position instead of showing both.
  • Added list export support for Records in CSV and JSON formats.
  • Added read-only notices to legacy Logs and Unit Logs pages after Records activation.
  • Added personnel deletion warnings for members who still own unfinished Records.

Added immutable revision history and diff support

  • Added canonical snapshot serialization and checksum generation for Records.
  • Added on-demand revision diff support.
  • Ensured restricted fields remain withheld in diffs for users without restricted-record access.

Added Records search infrastructure

  • Added a new shared Lucene-based search project and host for Records search.
  • Added:
    • Records search contracts
    • index document builder
    • indexer
    • search service
    • worker-driven index maintenance and rebuild flow
  • Added configuration for shared search host behavior.
  • Search is explicitly disabled by default and reports unavailable rather than degrading to unsafe text matching.

Added workflow integration for Records

  • Added native Records workflow trigger types for key lifecycle events.
  • Added workflow event transport through a reusable transactional domain-event outbox.
  • Added support for:
    • event IDs
    • correlation/causation IDs
    • sequence/origin tracking
    • skipped workflow run visibility
  • Added compatibility behavior so existing LogAdded workflows can continue firing for eligible finalized Records during migration.
  • Extended workflow sample data, variable catalog, run detail/history, and health views for Records events.

Added reporting and accountability across cutover

  • Added reporting services that merge legacy Logs and finalized Records into a single activity shape so reporting continues across cutover.
  • Added Records accountability reporting to show:
    • open records
    • overdue reviews
    • returned records
    • time-to-finalize metrics
  • Added reminder sending with rate/cooldown behavior.

Added attachment hygiene and malware scanning

  • Added attachment media hygiene for Records uploads:
    • strips image metadata
    • rejects active content types like SVG/executables/scripts
    • re-encodes supported images
  • Added a scanning provider seam plus:
    • default null scanner behavior
    • ClamAV/clamd-based scanner implementation
    • attachment scanning configuration
  • Scanning is off by default and supports fail-closed/fail-open behavior based on configuration.

Added department profile branding and email mastheads

  • Added department profile media storage for logos and generated renditions.
  • Added department profile management UI and public masthead serving endpoint.
  • Added support for department-branded operational emails:
    • call emails
    • message emails
    • report delivery emails
    • notification sender display naming
  • Added print/header branding support for Records outputs.
  • Added translations and shared labels for Department Profile and Records branding features.

Added persistence and worker support

  • Added SQL Server and PostgreSQL migrations for all new RMS/Records tables, feature flags, cutover tracking, search state, outbox, print layouts, and department profile media.
  • Registered all new repositories and services in Autofac modules.
  • Added worker commands/tasks for:
    • domain event outbox dispatch
    • records search index maintenance

Added localization and tests

  • Added a full new Records localization resource set and expanded related department, logs, personnel, security, workflow, and common translations.
  • Added broad automated test coverage for:
    • lifecycle rules
    • cutover and legacy write blocking
    • claims and permission behavior
    • outbox dispatch
    • revision snapshots/diffs
    • search
    • accountability
    • reporting
    • attachment hygiene/scanning
    • department profile branding
    • email branding
    • container composition
    • identifier allocations and policy parity

Functional impact

This PR establishes the first production-ready pass of the Records/RMS platform inside Resgrid, allowing departments to begin transitioning from legacy Logs to a new record-based system with stronger auditing, lifecycle control, search, branding, permissions, and workflow integration, while preserving read-only access to historical Logs after cutover.

}
@if (Model.ModuleState.RecordsUsable && ClaimsAuthorizationHelper.CanExportRecords())
{
<a class="btn btn-default" asp-controller="Records" asp-action="ExportList" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-format="csv"><i class="fa fa-download"></i> @localizer["ExportCsv"]</a>
}
@if (Model.ModuleState.RecordsUsable && ClaimsAuthorizationHelper.CanExportRecords())
{
<a class="btn btn-default" asp-controller="Records" asp-action="ExportList" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-format="csv"><i class="fa fa-download"></i> @localizer["ExportCsv"]</a>
}
@if (Model.ModuleState.RecordsUsable && ClaimsAuthorizationHelper.CanExportRecords())
{
<a class="btn btn-default" asp-controller="Records" asp-action="ExportList" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-format="csv"><i class="fa fa-download"></i> @localizer["ExportCsv"]</a>
@if (Model.ModuleState.RecordsUsable && ClaimsAuthorizationHelper.CanExportRecords())
{
<a class="btn btn-default" asp-controller="Records" asp-action="ExportList" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-format="csv"><i class="fa fa-download"></i> @localizer["ExportCsv"]</a>
<a class="btn btn-default" asp-controller="Records" asp-action="ExportList" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-format="json"><i class="fa fa-download"></i> @localizer["ExportJson"]</a>
@if (Model.ModuleState.RecordsUsable && ClaimsAuthorizationHelper.CanExportRecords())
{
<a class="btn btn-default" asp-controller="Records" asp-action="ExportList" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-format="csv"><i class="fa fa-download"></i> @localizer["ExportCsv"]</a>
<a class="btn btn-default" asp-controller="Records" asp-action="ExportList" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-format="json"><i class="fa fa-download"></i> @localizer["ExportJson"]</a>
@if (Model.ModuleState.RecordsUsable && ClaimsAuthorizationHelper.CanExportRecords())
{
<a class="btn btn-default" asp-controller="Records" asp-action="ExportList" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-format="csv"><i class="fa fa-download"></i> @localizer["ExportCsv"]</a>
<a class="btn btn-default" asp-controller="Records" asp-action="ExportList" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-format="json"><i class="fa fa-download"></i> @localizer["ExportJson"]</a>
@for (var p = 1; p <= Model.TotalPages; p++)
{
<li class="@(p == Model.Page ? "active" : string.Empty)">
<a asp-controller="Records" asp-action="Index" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-owner="@Model.OwnerFilter" asp-route-group="@Model.GroupFilter" asp-route-page="@p">@p</a>
@for (var p = 1; p <= Model.TotalPages; p++)
{
<li class="@(p == Model.Page ? "active" : string.Empty)">
<a asp-controller="Records" asp-action="Index" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-owner="@Model.OwnerFilter" asp-route-group="@Model.GroupFilter" asp-route-page="@p">@p</a>
@for (var p = 1; p <= Model.TotalPages; p++)
{
<li class="@(p == Model.Page ? "active" : string.Empty)">
<a asp-controller="Records" asp-action="Index" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-owner="@Model.OwnerFilter" asp-route-group="@Model.GroupFilter" asp-route-page="@p">@p</a>
@for (var p = 1; p <= Model.TotalPages; p++)
{
<li class="@(p == Model.Page ? "active" : string.Empty)">
<a asp-controller="Records" asp-action="Index" asp-route-area="User" asp-route-year="@Model.Year" asp-route-definitionKey="@Model.DefinitionKey" asp-route-state="@Model.StateFilter" asp-route-q="@Model.Query" asp-route-owner="@Model.OwnerFilter" asp-route-group="@Model.GroupFilter" asp-route-page="@p">@p</a>
@request-info

request-info Bot commented Sep 3, 2026

Copy link
Copy Markdown

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

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 196 files, which is 46 over the limit of 150.

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

Upgrade to Team to raise the limit.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Essentials

Run ID: 9a6eefab-0606-45c7-ab8a-6d8e14696dfb

📥 Commits

Reviewing files that changed from the base of the PR and between 430bbf0 and c53ae88.

⛔ Files ignored due to path filters (118)
  • Core/Resgrid.Config/AttachmentScanningConfig.cs is excluded by !**/Core/Resgrid.Config/**
  • Core/Resgrid.Config/SearchConfig.cs is excluded by !**/Core/Resgrid.Config/**
  • Core/Resgrid.Localization/Areas/User/Department/Department.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Department/Department.uk.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Logs/Logs.uk.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Personnel/Person.uk.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
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Areas/User/Workflows/Workflows.uk.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.ar.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.de.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.el.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.en.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.es.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.fr.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.it.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.pl.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.sv.resx is excluded by !**/*.resx
  • Core/Resgrid.Localization/Common.uk.resx is excluded by !**/*.resx
  • Tests/Resgrid.Tests/Allocations/trigger-baseline.json is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Bootstrapper.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Localization/TranslationCompletenessTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Providers/ClamAvAttachmentScannerTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Providers/DepartmentEmailBrandingTemplateTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/Call.html is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/Message.html is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/ReportDelivery.html is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Providers/TransactionalEmailTemplateTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Resgrid.Tests.csproj is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/ClaimsAuthorizationHelperParityTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/DepartmentProfileMediaServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/DomainEventOutboxServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/FakeRmsStore.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/LegacyWriteGuardTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/PermissionsServiceSelectRolesTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordAttachmentHygieneTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordClaimsTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordSnapshotSerializerTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsAccountabilityServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsCutoverServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsGroupScopePreviewTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsListExportTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsNotificationServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsPermissionRowsTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsPrintLayoutServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsReportingServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsSearchIndexMaintenanceServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsSearchTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsServiceTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RecordsWorkflowTriggerTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RmsCascadeSafetyTests.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/RmsInventoryUsageAdapterTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/RmsLifecycleTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/SystemApiKeyRecordPolicyTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Rms/WorkflowRunEnvelopeTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/DocumentDatabaseProviderSelectionTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/EmailServiceBrandingTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/EmailServiceUsabilityTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/UnitsServiceProtectedWriteTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/WorkflowTemplateContextBuilderTests.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Web/User/SecurityControllerTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (196)
  • Core/Resgrid.Localization/Areas/User/Records/Records.cs
  • Core/Resgrid.Model/DepartmentEmailBranding.cs
  • Core/Resgrid.Model/DepartmentProfile.cs
  • Core/Resgrid.Model/DepartmentProfileMedia.cs
  • Core/Resgrid.Model/DepartmentSettingTypes.cs
  • Core/Resgrid.Model/Events/DomainEventDispatchedEvent.cs
  • Core/Resgrid.Model/Events/EventTypes.cs
  • Core/Resgrid.Model/Events/RecordsWorkflowEvent.cs
  • Core/Resgrid.Model/FeatureFlagKeys.cs
  • Core/Resgrid.Model/PermissionActions.cs
  • Core/Resgrid.Model/PermissionTypes.cs
  • Core/Resgrid.Model/Providers/IEmailProvider.cs
  • Core/Resgrid.Model/Providers/IRecordAttachmentScanner.cs
  • Core/Resgrid.Model/Records/DomainEventOutboxEntry.cs
  • Core/Resgrid.Model/Records/LogAddedCompatibility.cs
  • Core/Resgrid.Model/Records/RecordPermissionCatalog.cs
  • Core/Resgrid.Model/Records/RecordPermissionEvaluation.cs
  • Core/Resgrid.Model/Records/RecordsAccountability.cs
  • Core/Resgrid.Model/Records/RecordsContracts.cs
  • Core/Resgrid.Model/Records/RecordsDepartmentSettings.cs
  • Core/Resgrid.Model/Records/RecordsGroupScopePreview.cs
  • Core/Resgrid.Model/Records/RecordsReportingContracts.cs
  • Core/Resgrid.Model/Records/RecordsSearchContracts.cs
  • Core/Resgrid.Model/Records/RmsAccessAudit.cs
  • Core/Resgrid.Model/Records/RmsAuditEnums.cs
  • Core/Resgrid.Model/Records/RmsDefinitionKeys.cs
  • Core/Resgrid.Model/Records/RmsDepartmentCutover.cs
  • Core/Resgrid.Model/Records/RmsEnums.cs
  • Core/Resgrid.Model/Records/RmsExternalReference.cs
  • Core/Resgrid.Model/Records/RmsLifecycle.cs
  • Core/Resgrid.Model/Records/RmsOperationalRecord.cs
  • Core/Resgrid.Model/Records/RmsOperationalRecordDetail.cs
  • Core/Resgrid.Model/Records/RmsRecordAttachment.cs
  • Core/Resgrid.Model/Records/RmsRecordGroupScope.cs
  • Core/Resgrid.Model/Records/RmsRecordParticipant.cs
  • Core/Resgrid.Model/Records/RmsRecordPrintLayout.cs
  • Core/Resgrid.Model/Records/RmsRecordSearchProjection.cs
  • Core/Resgrid.Model/Records/RmsRecordUnitResponse.cs
  • Core/Resgrid.Model/Records/RmsRevision.cs
  • Core/Resgrid.Model/Repositories/IDepartmentProfileRepositories.cs
  • Core/Resgrid.Model/Repositories/IRmsLegacyStatsRepository.cs
  • Core/Resgrid.Model/Repositories/IRmsRepositories.cs
  • Core/Resgrid.Model/Repositories/IWorkflowRunRepository.cs
  • Core/Resgrid.Model/Services/IDepartmentProfileMediaService.cs
  • Core/Resgrid.Model/Services/IDepartmentSettingsService.cs
  • Core/Resgrid.Model/Services/IDomainEventOutboxService.cs
  • Core/Resgrid.Model/Services/IRecordsAccountabilityService.cs
  • Core/Resgrid.Model/Services/IRecordsAuthorizationService.cs
  • Core/Resgrid.Model/Services/IRecordsCutoverService.cs
  • Core/Resgrid.Model/Services/IRecordsNotificationService.cs
  • Core/Resgrid.Model/Services/IRecordsPrintLayoutService.cs
  • Core/Resgrid.Model/Services/IRecordsReportingService.cs
  • Core/Resgrid.Model/Services/IRecordsSearchIndexMaintenanceService.cs
  • Core/Resgrid.Model/Services/IRecordsSearchIndexer.cs
  • Core/Resgrid.Model/Services/IRecordsSearchService.cs
  • Core/Resgrid.Model/Services/IRecordsService.cs
  • Core/Resgrid.Model/Services/IRmsInventoryUsageAdapter.cs
  • Core/Resgrid.Model/Services/IRmsRecordValueService.cs
  • Core/Resgrid.Model/UdfEntityType.cs
  • Core/Resgrid.Model/WorkflowHealthSummary.cs
  • Core/Resgrid.Model/WorkflowRun.cs
  • Core/Resgrid.Model/WorkflowRunEnvelope.cs
  • Core/Resgrid.Model/WorkflowTemplateVariableCatalog.cs
  • Core/Resgrid.Model/WorkflowTriggerEventType.cs
  • Core/Resgrid.Search/LuceneRecordsIndexHost.cs
  • Core/Resgrid.Search/LuceneRecordsIndexer.cs
  • Core/Resgrid.Search/LuceneRecordsSearchService.cs
  • Core/Resgrid.Search/RecordsIndexFields.cs
  • Core/Resgrid.Search/RecordsSearchDocumentBuilder.cs
  • Core/Resgrid.Search/Resgrid.Search.csproj
  • Core/Resgrid.Search/SearchModule.cs
  • Core/Resgrid.Services/DepartmentLogoRenditions.cs
  • Core/Resgrid.Services/DepartmentProfileMediaService.cs
  • Core/Resgrid.Services/DepartmentSettingsService.cs
  • Core/Resgrid.Services/EmailService.cs
  • Core/Resgrid.Services/PermissionsService.cs
  • Core/Resgrid.Services/Records/DomainEventOutboxService.cs
  • Core/Resgrid.Services/Records/NullRecordAttachmentScanner.cs
  • Core/Resgrid.Services/Records/RecordAttachmentHygiene.cs
  • Core/Resgrid.Services/Records/RecordSnapshotSerializer.cs
  • Core/Resgrid.Services/Records/RecordsAccountabilityService.cs
  • Core/Resgrid.Services/Records/RecordsAuthorizationService.cs
  • Core/Resgrid.Services/Records/RecordsCutoverService.cs
  • Core/Resgrid.Services/Records/RecordsNotificationService.cs
  • Core/Resgrid.Services/Records/RecordsPrintLayoutService.cs
  • Core/Resgrid.Services/Records/RecordsReportingService.cs
  • Core/Resgrid.Services/Records/RecordsSearchIndexMaintenanceService.cs
  • Core/Resgrid.Services/Records/RecordsService.cs
  • Core/Resgrid.Services/Records/RmsInventoryUsageAdapter.cs
  • Core/Resgrid.Services/Records/RmsRecordValueService.cs
  • Core/Resgrid.Services/ServicesModule.cs
  • Core/Resgrid.Services/UnitsService.cs
  • Core/Resgrid.Services/WorkLogsService.cs
  • Core/Resgrid.Services/WorkflowSampleDataGenerator.cs
  • Core/Resgrid.Services/WorkflowService.cs
  • Core/Resgrid.Services/WorkflowTemplateContextBuilder.cs
  • Providers/Resgrid.Providers.Bus/WorkflowEventProvider.cs
  • Providers/Resgrid.Providers.Claims/ClaimsLogic.cs
  • Providers/Resgrid.Providers.Claims/ClaimsPrincipalFactory.cs
  • Providers/Resgrid.Providers.Claims/JwtTokenProvider.cs
  • Providers/Resgrid.Providers.Claims/ResgridClaimTypes.cs
  • Providers/Resgrid.Providers.Claims/ResgridIdentity.cs
  • Providers/Resgrid.Providers.Claims/ResgridResources.cs
  • Providers/Resgrid.Providers.Email/PostmarkTemplateProvider.cs
  • Providers/Resgrid.Providers.Email/Template/Call.html
  • Providers/Resgrid.Providers.Email/Template/Message.html
  • Providers/Resgrid.Providers.Email/Template/ReportDelivery.html
  • Providers/Resgrid.Providers.Migrations/Migrations/M0150_AddRmsRecordsCore.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0151_AddRmsParticipantsUnitsAttachments.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0152_SeedRecordsFeatureFlags.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0153_AddDomainEventOutbox.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0154_AddRmsDepartmentCutover.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0155_AddRmsRevisionsAndAudit.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0156_AddRmsRecordSearchProjection.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0157_AddRmsGroupScopeAndShares.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0160_AddRmsPrintLayouts.cs
  • Providers/Resgrid.Providers.Migrations/Migrations/M0172_AddDepartmentProfileMediaAndBranding.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0150_AddRmsRecordsCorePg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0151_AddRmsParticipantsUnitsAttachmentsPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0152_SeedRecordsFeatureFlagsPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0153_AddDomainEventOutboxPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0154_AddRmsDepartmentCutoverPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0155_AddRmsRevisionsAndAuditPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0156_AddRmsRecordSearchProjectionPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0157_AddRmsGroupScopeAndSharesPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0160_AddRmsPrintLayoutsPg.cs
  • Providers/Resgrid.Providers.MigrationsPg/Migrations/M0172_AddDepartmentProfileMediaAndBrandingPg.cs
  • Providers/Resgrid.Providers.Scanning/ClamAvAttachmentScanner.cs
  • Providers/Resgrid.Providers.Scanning/Resgrid.Providers.Scanning.csproj
  • Providers/Resgrid.Providers.Scanning/ScanningProviderModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/DepartmentProfileRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/DataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/Modules/TestingDataModule.cs
  • Repositories/Resgrid.Repositories.DataRepository/RmsLegacyStatsRepository.cs
  • Repositories/Resgrid.Repositories.DataRepository/RmsRepositories.cs
  • Repositories/Resgrid.Repositories.DataRepository/WorkflowRunRepository.cs
  • Resgrid.sln
  • Web/Resgrid.Web.Services/Helpers/ClaimsAuthorizationHelper.cs
  • Web/Resgrid.Web.Services/Resgrid.Web.Services.csproj
  • Web/Resgrid.Web.Services/Startup.cs
  • Web/Resgrid.Web/Areas/User/Controllers/DepartmentController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/LogsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/PersonnelController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/RecordsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/ReportsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/SecurityController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/UnitsController.cs
  • Web/Resgrid.Web/Areas/User/Controllers/WorkflowsController.cs
  • Web/Resgrid.Web/Areas/User/Models/DeletePersonModel.cs
  • Web/Resgrid.Web/Areas/User/Models/Logs/LogsIndexView.cs
  • Web/Resgrid.Web/Areas/User/Models/Records/RecordsListExport.cs
  • Web/Resgrid.Web/Areas/User/Models/Records/RecordsViewModels.cs
  • Web/Resgrid.Web/Areas/User/Models/Security/PermissionsView.cs
  • Web/Resgrid.Web/Areas/User/Models/Security/RecordsPermissionRow.cs
  • Web/Resgrid.Web/Areas/User/Models/Settings/DepartmentProfileModel.cs
  • Web/Resgrid.Web/Areas/User/Models/Units/ViewLogsView.cs
  • Web/Resgrid.Web/Areas/User/Views/Department/Profile.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Logs/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Personnel/DeletePerson.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/Accountability.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/Activate.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/Details.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/Diff.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/Print.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/PrintDiff.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/PrintRevision.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/Revision.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/Settings.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/_PrintFooter.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Records/_PrintHeader.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Security/Index.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_Navigation.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Shared/_TopNavbar.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Units/ViewLogs.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Workflows/Edit.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Workflows/Health.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Workflows/New.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Workflows/RunDetail.cshtml
  • Web/Resgrid.Web/Areas/User/Views/Workflows/Runs.cshtml
  • Web/Resgrid.Web/Helpers/ClaimsAuthorizationHelper.cs
  • Web/Resgrid.Web/Resgrid.Web.csproj
  • Web/Resgrid.Web/Startup.cs
  • Web/Resgrid.Web/WebBootstrapper.cs
  • Web/Resgrid.Web/wwwroot/js/app/internal/security/resgrid.security.permissions.js
  • Workers/Resgrid.Workers.Console/Commands/DomainEventOutboxDispatchCommand.cs
  • Workers/Resgrid.Workers.Console/Commands/RecordsSearchIndexCommand.cs
  • Workers/Resgrid.Workers.Console/Program.cs
  • Workers/Resgrid.Workers.Console/Tasks/DomainEventOutboxDispatchTask.cs
  • Workers/Resgrid.Workers.Console/Tasks/RecordsSearchIndexTask.cs
  • Workers/Resgrid.Workers.Framework/Bootstrapper.cs
  • Workers/Resgrid.Workers.Framework/Logic/DomainEventOutboxDispatchLogic.cs
  • Workers/Resgrid.Workers.Framework/Logic/NotificationBroadcastLogic.cs
  • Workers/Resgrid.Workers.Framework/Logic/RecordsSearchIndexLogic.cs
  • Workers/Resgrid.Workers.Framework/Resgrid.Workers.Framework.csproj

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.

@Resgrid-Bot

Resgrid-Bot commented Sep 3, 2026

Copy link
Copy Markdown

Code Review Could Not Complete ⚠️

The review failed before suggestions could be generated.

Reason: This PR exceeded the maximum context size accepted by the configured model (openai).

To resolve, choose one:

  • Switch to a recommended model: pick one of Kodus's recommended models in your BYOK settings (Claude Sonnet 4.6, Claude Opus 4.7, Gemini 3.1 Pro, GPT-5.4, Kimi K2.6 Coding, or GLM 5.1).
  • Split the PR into smaller ones: file count drives prompt overhead.

After fixing the issue, comment @kody review on this PR to re-run the review.

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.

public static class AttachmentScanningConfig
{
/// <summary>Master switch. When false the scanner never opens a connection.</summary>
public static bool Enabled = false;

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/AttachmentScanningConfig.cs because public static bool Enabled = false; declares immutable metadata as a writable field. Mark it const, as shown, or readonly if runtime assignment is required.

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

public const bool Enabled = false;
Prompt for LLM

File Core/Resgrid.Config/AttachmentScanningConfig.cs:

Line 11:

Mutable configuration state in Core/Resgrid.Config/AttachmentScanningConfig.cs because public static bool Enabled = false; declares immutable metadata as a writable field. Mark it const, as shown, or readonly if runtime assignment is required.

Suggested Code:

		public const bool Enabled = false;

Talk to Kody by mentioning @kody

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


namespace Resgrid.Localization.Areas.User.Records
{
public class Records

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

Suspicious empty type in Core/Resgrid.Localization/Areas/User/Records/Records.cs because public class Records has no instance state or behavior and exposes only an implicit public constructor. Make Records static, as shown, or remove it until it has a concrete instantiation purpose.

Kody rule violation: Avoid Private-Only Constructors

public static class Records
Prompt for LLM

File Core/Resgrid.Localization/Areas/User/Records/Records.cs:

Line 7:

Suspicious empty type in Core/Resgrid.Localization/Areas/User/Records/Records.cs because public class Records has no instance state or behavior and exposes only an implicit public constructor. Make Records static, as shown, or remove it until it has a concrete instantiation purpose.

Suggested Code:

	public static class Records

Talk to Kody by mentioning @kody

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

return descriptor;
}

return null;

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

Null return contract in Core/Resgrid.Model/Records/RecordPermissionCatalog.cs because return null; from a reference-returning API forces callers onto a null-handling path, with matching occurrences in Core/Resgrid.Model/Events/RecordsWorkflowEvent.cs:37-37, Tests/Resgrid.Tests/Rms/RecordsReportingServiceTests.cs:158-158, and Repositories/Resgrid.Repositories.DataRepository/WorkflowRunRepository.cs:60-60. Return a non-null default or use an explicit nullable or exception-based absence pattern.

Kody rule violation: Avoid Returning Null in Non-Async Task Methods

return default;
Prompt for LLM

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

Line 81:

Null return contract in Core/Resgrid.Model/Records/RecordPermissionCatalog.cs because return null; from a reference-returning API forces callers onto a null-handling path, with matching occurrences in Core/Resgrid.Model/Events/RecordsWorkflowEvent.cs:37-37, Tests/Resgrid.Tests/Rms/RecordsReportingServiceTests.cs:158-158, and Repositories/Resgrid.Repositories.DataRepository/WorkflowRunRepository.cs:60-60. Return a non-null default or use an explicit nullable or exception-based absence pattern.

Suggested Code:

			return default;

Talk to Kody by mentioning @kody

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

{
try { _directory.Dispose(); } catch (Exception ex) { Logging.LogException(ex); }
}
Analyzer.Dispose();

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

Fragile disposal in Core/Resgrid.Search/LuceneRecordsIndexHost.cs because Analyzer.Dispose() is not guarded even though other IDisposable cleanup follows a deterministic protected pattern, with a matching occurrence in Tests/Resgrid.Tests/Rms/RecordAttachmentHygieneTests.cs:22-22. Wrap disposal in exception handling and log the failure with operation="Dispose" and indexPath = IndexPath.

Kody rule violation: Use using statements for disposable resources

try { Analyzer.Dispose(); } catch (Exception ex) { Logging.LogException(ex, "Disposing analyzer failed.", new { operation = "Dispose", indexPath = IndexPath }); }
Prompt for LLM

File Core/Resgrid.Search/LuceneRecordsIndexHost.cs:

Line 153:

Fragile disposal in Core/Resgrid.Search/LuceneRecordsIndexHost.cs because Analyzer.Dispose() is not guarded even though other IDisposable cleanup follows a deterministic protected pattern, with a matching occurrence in Tests/Resgrid.Tests/Rms/RecordAttachmentHygieneTests.cs:22-22. Wrap disposal in exception handling and log the failure with operation="Dispose" and indexPath = IndexPath.

Suggested Code:

				try { Analyzer.Dispose(); } catch (Exception ex) { Logging.LogException(ex, "Disposing analyzer failed.", new { operation = "Dispose", indexPath = IndexPath }); }

Talk to Kody by mentioning @kody

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

var searcher = manager.Acquire();
try
{
var hits = searcher.Search(new TermQuery(new Term(RecordsIndexFields.DepartmentId, departmentId.ToString())), 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

Misleading async contract in Core/Resgrid.Search/LuceneRecordsIndexer.cs because CountDocumentsAsync performs the synchronous call searcher.Search(new TermQuery(new Term(RecordsIndexFields.DepartmentId, departmentId.ToString())), 1). Use an awaitable API if one exists, or make the method synchronous or rename it to avoid implying non-blocking behavior.

Kody rule violation: Use Awaitable Methods in Async Code

Prompt for LLM

File Core/Resgrid.Search/LuceneRecordsIndexer.cs:

Line 81:

Misleading async contract in Core/Resgrid.Search/LuceneRecordsIndexer.cs because CountDocumentsAsync performs the synchronous call searcher.Search(new TermQuery(new Term(RecordsIndexFields.DepartmentId, departmentId.ToString())), 1). Use an awaitable API if one exists, or make the method synchronous or rename it to avoid implying non-blocking behavior.

Talk to Kody by mentioning @kody

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

Comment on lines +234 to +244
await _accessAuditsRepository.InsertAsync(new RmsAccessAudit
{
DepartmentId = departmentId,
Action = (int)RmsAccessAuditAction.Activation,
ActorUserId = userId,
Purpose = "Records activation",
Successful = true,
OccurredOn = now,
OriginClient = (int)RmsOriginClient.Web,
DetailJson = JsonConvert.SerializeObject(new { cutover.RmsDepartmentCutoverId, reason })
}, cancellationToken, true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules high

Incomplete audit payload in Core/Resgrid.Services/Records/RecordsCutoverService.cs because the RmsAccessAudit activation record omits required fields such as trace_id, actor.role, result, ip, and user_agent, including the similar block at lines 307-317. Emit a tamper-evident DetailJson payload that includes trace_id, actor, action, resource, result, ip, user_agent, and reason_token.

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

await _accessAuditsRepository.InsertAsync(new RmsAccessAudit
{
	DepartmentId = departmentId,
	Action = (int)RmsAccessAuditAction.Activation,
	ActorUserId = userId,
	Purpose = "Records activation",
	Successful = true,
	OccurredOn = now,
	OriginClient = (int)RmsOriginClient.Web,
	DetailJson = JsonConvert.SerializeObject(new { trace_id, actor = new { user_id = userId, role }, action = "records.activation", resource = new { id = cutover.RmsDepartmentCutoverId }, result = "success", ip, user_agent, reason_token })
}, cancellationToken, true);
Prompt for LLM

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

Line 234 to 244:

Incomplete audit payload in Core/Resgrid.Services/Records/RecordsCutoverService.cs because the RmsAccessAudit activation record omits required fields such as trace_id, actor.role, result, ip, and user_agent, including the similar block at lines 307-317. Emit a tamper-evident DetailJson payload that includes trace_id, actor, action, resource, result, ip, user_agent, and reason_token.

Suggested Code:

				await _accessAuditsRepository.InsertAsync(new RmsAccessAudit
				{
					DepartmentId = departmentId,
					Action = (int)RmsAccessAuditAction.Activation,
					ActorUserId = userId,
					Purpose = "Records activation",
					Successful = true,
					OccurredOn = now,
					OriginClient = (int)RmsOriginClient.Web,
					DetailJson = JsonConvert.SerializeObject(new { trace_id, actor = new { user_id = userId, role }, action = "records.activation", resource = new { id = cutover.RmsDepartmentCutoverId }, result = "success", ip, user_agent, reason_token })
				}, cancellationToken, true);

Talk to Kody by mentioning @kody

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

UsageSnapshot snapshot;
try
{
snapshot = string.IsNullOrWhiteSpace(reference.SnapshotJson) ? new UsageSnapshot() : JsonConvert.DeserializeObject<UsageSnapshot>(reference.SnapshotJson) ?? new UsageSnapshot();

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-reference risk in Core/Resgrid.Services/Records/RmsInventoryUsageAdapter.cs because reference.SnapshotJson is dereferenced even though the reference parameter may be absent, with matching occurrences at lines 111, 106, 118, 117, and 113 and in the listed callers. Guard the access with reference?.SnapshotJson or an explicit null check before deserialization.

Kody rule violation: Add null checks before accessing properties

snapshot = string.IsNullOrWhiteSpace(reference?.SnapshotJson)
	? new UsageSnapshot()
	: JsonConvert.DeserializeObject<UsageSnapshot>(reference.SnapshotJson) ?? new UsageSnapshot();
Prompt for LLM

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

Line 99:

Null-reference risk in Core/Resgrid.Services/Records/RmsInventoryUsageAdapter.cs because reference.SnapshotJson is dereferenced even though the reference parameter may be absent, with matching occurrences at lines 111, 106, 118, 117, and 113 and in the listed callers. Guard the access with reference?.SnapshotJson or an explicit null check before deserialization.

Suggested Code:

				snapshot = string.IsNullOrWhiteSpace(reference?.SnapshotJson)
					? new UsageSnapshot()
					: JsonConvert.DeserializeObject<UsageSnapshot>(reference.SnapshotJson) ?? new UsageSnapshot();

Talk to Kody by mentioning @kody

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

recordNumber = "TRN-2026-0042";
revisionNumber = 1;
revisionId = "0f3c1d2e-5b6a-4c7d-8e9f-0a1b2c3d4e5f";
finalizedOn = DateTime.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 Kody Rules high

Timing accuracy issue in Core/Resgrid.Services/WorkflowSampleDataGenerator.cs because DateTime.Now is affected by daylight savings and system clock adjustments, including the occurrences at lines 561, 571, 582, 605, 606, 607, 631, 632, and 633. Use Stopwatch for elapsed timing operations.

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

Prompt for LLM

File Core/Resgrid.Services/WorkflowSampleDataGenerator.cs:

Line 551:

Timing accuracy issue in Core/Resgrid.Services/WorkflowSampleDataGenerator.cs because DateTime.Now is affected by daylight savings and system clock adjustments, including the occurrences at lines 561, 571, 582, 605, 606, 607, 631, 632, and 633. Use Stopwatch for elapsed timing operations.

Talk to Kody by mentioning @kody

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

/// </summary>
private static void AddDepartmentBranding(Dictionary<string, object> templateModel, DepartmentEmailBranding branding)
{
var enabled = branding != null && branding.Enabled && !String.IsNullOrWhiteSpace(branding.LogoUrl);

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-reference risk in Providers/Resgrid.Providers.Email/PostmarkTemplateProvider.cs because branding is dereferenced through branding.Enabled and branding.LogoUrl after a manual null check. Use explicit null-safe access with branding?.Enabled == true and branding?.LogoUrl to make the nullable contract consistent.

Kody rule violation: Add null checks to prevent NullReferenceException

var enabled = branding?.Enabled == true && !String.IsNullOrWhiteSpace(branding?.LogoUrl);
Prompt for LLM

File Providers/Resgrid.Providers.Email/PostmarkTemplateProvider.cs:

Line 721:

Null-reference risk in Providers/Resgrid.Providers.Email/PostmarkTemplateProvider.cs because branding is dereferenced through branding.Enabled and branding.LogoUrl after a manual null check. Use explicit null-safe access with branding?.Enabled == true and branding?.LogoUrl to make the nullable contract consistent.

Suggested Code:

			var enabled = branding?.Enabled == true && !String.IsNullOrWhiteSpace(branding?.LogoUrl);

Talk to Kody by mentioning @kody

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

<tr>
{{#department_branding}}<tr>
<td class="email-masthead">
<a href="{{department_website}}" class="email-masthead_name"><img src="{{department_logo_url}}" alt="{{department_display_name}}" class="email-masthead_logo" width="94" style="width: 94px; max-width: 94px; height: auto; border: 0; display: block; margin: 0 auto 6px auto;" /><span>{{department_display_name}}</span></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 high

Rule mismatch in Providers/Resgrid.Providers.Email/Template/Call.html because the Next.js image requirement does not apply to plain HTML email templates and cshtml views, including the listed occurrences in email templates, tests, and Razor files. Enforce only requirements that are valid for each rendering platform.

Kody rule violation: Use next/image with explicit dimensions and alt

Prompt for LLM

File Providers/Resgrid.Providers.Email/Template/Call.html:

Line 395:

Rule mismatch in Providers/Resgrid.Providers.Email/Template/Call.html because the Next.js image requirement does not apply to plain HTML email templates and cshtml views, including the listed occurrences in email templates, tests, and Razor files. Enforce only requirements that are valid for each rendering platform.

Talk to Kody by mentioning @kody

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

<tr>
{{#department_branding}}<tr>
<td class="email-masthead">
<a href="{{department_website}}" class="email-masthead_name"><img src="{{department_logo_url}}" alt="{{department_display_name}}" class="email-masthead_logo" width="94" style="width: 94px; max-width: 94px; height: auto; border: 0; display: block; margin: 0 auto 6px auto;" /><span>{{department_display_name}}</span></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 high

Incomplete image attributes in Providers/Resgrid.Providers.Email/Template/ReportDelivery.html because the defines width="94" but not an explicit height or loading hints, with matching occurrences in Providers/Resgrid.Providers.Email/Template/Call.html:395-395 and Providers/Resgrid.Providers.Email/Template/Message.html:395-395. Add height="94", loading="lazy", and decoding="async".

Kody rule violation: Serve responsive images with modern formats and lazy-load

<a href="{{department_website}}" class="email-masthead_name"><img src="{{department_logo_url}}" alt="{{department_display_name}}" class="email-masthead_logo" width="94" height="94" loading="lazy" decoding="async" style="width: 94px; max-width: 94px; height: auto; border: 0; display: block; margin: 0 auto 6px auto;" /><span>{{department_display_name}}</span></a>
Prompt for LLM

File Providers/Resgrid.Providers.Email/Template/ReportDelivery.html:

Line 395:

Incomplete image attributes in Providers/Resgrid.Providers.Email/Template/ReportDelivery.html because the <img> defines width="94" but not an explicit height or loading hints, with matching occurrences in Providers/Resgrid.Providers.Email/Template/Call.html:395-395 and Providers/Resgrid.Providers.Email/Template/Message.html:395-395. Add height="94", loading="lazy", and decoding="async".

Suggested Code:

              <a href="{{department_website}}" class="email-masthead_name"><img src="{{department_logo_url}}" alt="{{department_display_name}}" class="email-masthead_logo" width="94" height="94" loading="lazy" decoding="async" style="width: 94px; max-width: 94px; height: auto; border: 0; display: block; margin: 0 auto 6px auto;" /><span>{{department_display_name}}</span></a>

Talk to Kody by mentioning @kody

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

Create.Index("IX_RmsOperationalRecords_Department_Owner").OnTable("RmsOperationalRecords")
.OnColumn("DepartmentId").Ascending().OnColumn("OwnerUserId").Ascending();

Execute.Sql("CREATE NONCLUSTERED INDEX IX_RmsOperationalRecords_Department_Call ON RmsOperationalRecords (DepartmentId, CallId) WHERE CallId IS NOT NULL;");

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

Unsafe index migration strategy in Providers/Resgrid.Providers.Migrations/Migrations/M0150_AddRmsRecordsCore.cs because Execute.Sql("CREATE NONCLUSTERED INDEX IX_RmsOperationalRecords_Department_Call ON RmsOperationalRecords (DepartmentId, CallId) WHERE CallId IS NOT NULL;") creates an index with raw SQL without showing an online or rollback-safe path, with matching occurrences in the listed migration files. Use the database's online or concurrent option where supported, or document why the operation is safe for the target table size and rollback plan.

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

// Use an online migration strategy for index creation on large tables, or document table size/rollback plan.
Execute.Sql("CREATE NONCLUSTERED INDEX IX_RmsOperationalRecords_Department_Call ON RmsOperationalRecords (DepartmentId, CallId) WHERE CallId IS NOT NULL;");
Prompt for LLM

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

Line 84:

Unsafe index migration strategy in Providers/Resgrid.Providers.Migrations/Migrations/M0150_AddRmsRecordsCore.cs because Execute.Sql("CREATE NONCLUSTERED INDEX IX_RmsOperationalRecords_Department_Call ON RmsOperationalRecords (DepartmentId, CallId) WHERE CallId IS NOT NULL;") creates an index with raw SQL without showing an online or rollback-safe path, with matching occurrences in the listed migration files. Use the database's online or concurrent option where supported, or document why the operation is safe for the target table size and rollback plan.

Suggested Code:

				// Use an online migration strategy for index creation on large tables, or document table size/rollback plan.
				Execute.Sql("CREATE NONCLUSTERED INDEX IX_RmsOperationalRecords_Department_Call ON RmsOperationalRecords (DepartmentId, CallId) WHERE CallId IS NOT NULL;");

Talk to Kody by mentioning @kody

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

Execute.Sql("CREATE UNIQUE NONCLUSTERED INDEX UX_RmsOperationalRecords_Department_DraftReference ON RmsOperationalRecords (DepartmentId, DraftReference);");
}

if (!Schema.Table("RmsOperationalRecordDetails").Exists())

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-path requirement in Providers/Resgrid.Providers.Migrations/Migrations/M0150_AddRmsRecordsCore.cs because the RmsOperationalRecordDetails table appears to store ePHI-sensitive operational record data without a corresponding append-only access audit mechanism in the surrounding application flows. Ensure reads and writes emit immutable audit entries with user id, subject id when applicable, action, purpose-of-use, timestamp, and request id.

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

// ePHI-bearing records should be paired with append-only audit logging in application flows.
if (!Schema.Table("RmsOperationalRecordDetails").Exists())
Prompt for LLM

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

Line 92:

Missing audit-path requirement in Providers/Resgrid.Providers.Migrations/Migrations/M0150_AddRmsRecordsCore.cs because the RmsOperationalRecordDetails table appears to store ePHI-sensitive operational record data without a corresponding append-only access audit mechanism in the surrounding application flows. Ensure reads and writes emit immutable audit entries with user id, subject id when applicable, action, purpose-of-use, timestamp, and request id.

Suggested Code:

			// ePHI-bearing records should be paired with append-only audit logging in application flows.
			if (!Schema.Table("RmsOperationalRecordDetails").Exists())

Talk to Kody by mentioning @kody

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

{
Execute.Sql(
"IF NOT EXISTS (SELECT 1 FROM [FeatureFlags] WHERE [FlagKey] = '" + flag[0] + "') " +
"INSERT INTO [FeatureFlags] ([FlagKey], [Name], [Description], [Category], [IsEnabledGlobally]) " +

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

SQL injection pattern in Providers/Resgrid.Providers.Migrations/Migrations/M0152_SeedRecordsFeatureFlags.cs because the INSERT INTO [FeatureFlags] statement is built as raw SQL text, with matching occurrences in Providers/Resgrid.Providers.MigrationsPg/Migrations/M0152_SeedRecordsFeatureFlagsPg.cs:28-28 and :29-29. Use parameterized SQL or migration APIs so values are not concatenated into the query.

Kody rule violation: Prevent SQL Injection in Queries

Prompt for LLM

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

Line 33:

SQL injection pattern in Providers/Resgrid.Providers.Migrations/Migrations/M0152_SeedRecordsFeatureFlags.cs because the INSERT INTO [FeatureFlags] statement is built as raw SQL text, with matching occurrences in Providers/Resgrid.Providers.MigrationsPg/Migrations/M0152_SeedRecordsFeatureFlagsPg.cs:28-28 and :29-29. Use parameterized SQL or migration APIs so values are not concatenated into the query.

Talk to Kody by mentioning @kody

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

{
Create.Table("RmsDepartmentCutoverEvents")
.WithColumn("RmsDepartmentCutoverEventId").AsInt32().NotNullable().PrimaryKey().Identity()
.WithColumn("DepartmentId").AsInt32().NotNullable()

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

Potential missing single-column index in Providers/Resgrid.Providers.Migrations/Migrations/M0154_AddRmsDepartmentCutover.cs because DepartmentId is likely filtered or joined independently even though only a composite index is added later, with a matching occurrence in Providers/Resgrid.Providers.Migrations/Migrations/M0172_AddDepartmentProfileMediaAndBranding.cs:42-42. Verify query plans and add a dedicated DepartmentId index if standalone filtering is part of the workload.

Kody rule violation: Add database indexes for query optimization

.WithColumn("DepartmentId").AsInt32().NotNullable()
// add an index for frequent filtering/joining on DepartmentId
Prompt for LLM

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

Line 45:

Potential missing single-column index in Providers/Resgrid.Providers.Migrations/Migrations/M0154_AddRmsDepartmentCutover.cs because DepartmentId is likely filtered or joined independently even though only a composite index is added later, with a matching occurrence in Providers/Resgrid.Providers.Migrations/Migrations/M0172_AddDepartmentProfileMediaAndBranding.cs:42-42. Verify query plans and add a dedicated DepartmentId index if standalone filtering is part of the workload.

Suggested Code:

					.WithColumn("DepartmentId").AsInt32().NotNullable()
					// add an index for frequent filtering/joining on DepartmentId

Talk to Kody by mentioning @kody

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

{
Execute.Sql(
"INSERT INTO featureflags (flagkey, name, description, category, isenabledglobally) " +
"SELECT '" + flag[0] + "', '" + flag[1] + "', '" + flag[2] + "', '" + flag[3] + "', false " +

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

SQL concatenation in Providers/Resgrid.Providers.MigrationsPg/Migrations/M0152_SeedRecordsFeatureFlagsPg.cs because "SELECT '" + flag[0] + "', '" + flag[1] + "', '" + flag[2] + "', '" + flag[3] + "', false " interpolates values into SQL text, with a matching occurrence at :30-30. Use parameterized placeholders such as @key, @name, @description, and @category instead of string concatenation.

Kody rule violation: Always sanitize user inputs

"SELECT @key, @name, @description, @category, false " +
Prompt for LLM

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

Line 29:

SQL concatenation in Providers/Resgrid.Providers.MigrationsPg/Migrations/M0152_SeedRecordsFeatureFlagsPg.cs because "SELECT '" + flag[0] + "', '" + flag[1] + "', '" + flag[2] + "', '" + flag[3] + "', false " interpolates values into SQL text, with a matching occurrence at :30-30. Use parameterized placeholders such as @key, @name, @description, and @category instead of string concatenation.

Suggested Code:

					"SELECT @key, @name, @description, @category, false " +

Talk to Kody by mentioning @kody

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

}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
Logging.LogError($"ClamAV scan of '{fileName}' timed out after {AttachmentScanningConfig.TimeoutSeconds} seconds ({AttachmentScanningConfig.Host}:{AttachmentScanningConfig.Port}).");

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 error logging in Providers/Resgrid.Providers.Scanning/ClamAvAttachmentScanner.cs because Logging.LogError($"ClamAV scan of '{fileName}' timed out after {AttachmentScanningConfig.TimeoutSeconds} seconds ({AttachmentScanningConfig.Host}:{AttachmentScanningConfig.Port}).") encodes operation context into a message string only. Emit structured fields such as operation="ClamAvScan", fileName, host, port, timeoutSeconds, and the error type so failures remain filterable and correlatable.

Kody rule violation: Include error context in structured logs

Prompt for LLM

File Providers/Resgrid.Providers.Scanning/ClamAvAttachmentScanner.cs:

Line 43:

Unstructured error logging in Providers/Resgrid.Providers.Scanning/ClamAvAttachmentScanner.cs because Logging.LogError($"ClamAV scan of '{fileName}' timed out after {AttachmentScanningConfig.TimeoutSeconds} seconds ({AttachmentScanningConfig.Host}:{AttachmentScanningConfig.Port}).") encodes operation context into a message string only. Emit structured fields such as operation="ClamAvScan", fileName, host, port, timeoutSeconds, and the error type so failures remain filterable and correlatable.

Talk to Kody by mentioning @kody

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

if (scope.CreatedOn == default)
scope.CreatedOn = DateTime.UtcNow;

await InsertAsync(scope, cancellationToken, true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules high

N+1 database write pattern in Repositories/Resgrid.Repositories.DataRepository/RmsRepositories.cs because await InsertAsync(scope, cancellationToken, true); executes inside a loop, with matching occurrences in Core/Resgrid.Services/Records/RecordsService.cs:1119-1120 and :1149-1149. Batch the scopes into a single InsertManyAsync or another aggregated write operation.

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

// Batch inserts or use a single multi-row write instead of awaiting one DB call per scope
await InsertManyAsync(scopes, cancellationToken);
Prompt for LLM

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

Line 787:

N+1 database write pattern in Repositories/Resgrid.Repositories.DataRepository/RmsRepositories.cs because await InsertAsync(scope, cancellationToken, true); executes inside a loop, with matching occurrences in Core/Resgrid.Services/Records/RecordsService.cs:1119-1120 and :1149-1149. Batch the scopes into a single InsertManyAsync or another aggregated write operation.

Suggested Code:

			// Batch inserts or use a single multi-row write instead of awaiting one DB call per scope
			await InsertManyAsync(scopes, cancellationToken);

Talk to Kody by mentioning @kody

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


return await work(UnitOfWork.CreateOrGetConnection());
}
catch (Exception ex)

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

Overbroad exception handling in Repositories/Resgrid.Repositories.DataRepository/RmsRepositories.cs because catch (Exception ex) wraps database operations without classifying provider-specific failures. Catch DbException explicitly, distinguish transient from non-transient errors, add database context, and only then fall back to a general exception handler.

Kody rule violation: Implement proper database error checking

catch (DbException ex)
{
	// add context, classify transient vs non-transient, and rethrow/handle appropriately
	throw;
}
catch (Exception ex)
Prompt for LLM

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

Line 107:

Overbroad exception handling in Repositories/Resgrid.Repositories.DataRepository/RmsRepositories.cs because catch (Exception ex) wraps database operations without classifying provider-specific failures. Catch DbException explicitly, distinguish transient from non-transient errors, add database context, and only then fall back to a general exception handler.

Suggested Code:

			catch (DbException ex)
			{
				// add context, classify transient vs non-transient, and rethrow/handle appropriately
				throw;
			}
			catch (Exception ex)

Talk to Kody by mentioning @kody

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


public void Dispose()
{
try { _listener.Stop(); } catch { }

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 suppression in Tests/Resgrid.Tests/Providers/ClamAvAttachmentScannerTests.cs hides failures from _listener.Stop() and makes test cleanup behavior unverifiable. Log the exception with cleanup context and either rethrow it or handle it explicitly.

Kody rule violation: Avoid empty catch blocks

Prompt for LLM

File Tests/Resgrid.Tests/Providers/ClamAvAttachmentScannerTests.cs:

Line 272:

Exception suppression in Tests/Resgrid.Tests/Providers/ClamAvAttachmentScannerTests.cs hides failures from _listener.Stop() and makes test cleanup behavior unverifiable. Log the exception with cleanup context and either rethrow it or handle it explicitly.

Talk to Kody by mentioning @kody

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

_neverReply = neverReply;
_listener = new TcpListener(IPAddress.Loopback, 0);
_listener.Start();
_ = Task.Run(ServeAsync);

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 work in constructor in Tests/Resgrid.Tests/Providers/ClamAvAttachmentScannerTests.cs because _ = Task.Run(ServeAsync); starts background execution during construction and prevents deterministic awaiting and failure handling. Move ServeAsync startup into an explicit StartAsync or InitializeAsync method.

Kody rule violation: Avoid asynchronous operations in constructors

// start async serving from an explicit StartAsync/InitializeAsync method instead of the constructor
Prompt for LLM

File Tests/Resgrid.Tests/Providers/ClamAvAttachmentScannerTests.cs:

Line 219:

Async work in constructor in Tests/Resgrid.Tests/Providers/ClamAvAttachmentScannerTests.cs because _ = Task.Run(ServeAsync); starts background execution during construction and prevents deterministic awaiting and failure handling. Move ServeAsync startup into an explicit StartAsync or InitializeAsync method.

Suggested Code:

				// start async serving from an explicit StartAsync/InitializeAsync method instead of the constructor

Talk to Kody by mentioning @kody

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

/// <summary>The call export link carries an encrypted token that is not stable across runs; nothing else varies.</summary>
private static string Stable(string html)
{
return Regex.Replace(html, @"query=[^""&]*", "query=TOKEN");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kody code-review Kody Rules high

Regular expression denial-of-service risk in Tests/Resgrid.Tests/Providers/DepartmentEmailBrandingTemplateTests.cs because Regex.Replace(html, @"query=[^""&]*", "query=TOKEN") executes without a timeout on potentially untrusted input, with matching occurrences in Tests/Resgrid.Tests/Rms/ClaimsAuthorizationHelperParityTests.cs:88-88, Tests/Resgrid.Tests/Rms/SystemApiKeyRecordPolicyTests.cs:47-47, and :63-63. Specify an explicit regex timeout.

Kody rule violation: Specify Timeout for Regular Expressions

Prompt for LLM

File Tests/Resgrid.Tests/Providers/DepartmentEmailBrandingTemplateTests.cs:

Line 67:

Regular expression denial-of-service risk in Tests/Resgrid.Tests/Providers/DepartmentEmailBrandingTemplateTests.cs because Regex.Replace(html, @"query=[^""&]*", "query=TOKEN") executes without a timeout on potentially untrusted input, with matching occurrences in Tests/Resgrid.Tests/Rms/ClaimsAuthorizationHelperParityTests.cs:88-88, Tests/Resgrid.Tests/Rms/SystemApiKeyRecordPolicyTests.cs:47-47, and :63-63. Specify an explicit regex timeout.

Talk to Kody by mentioning @kody

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

Postmark automatically inlines all CSS properties for maximum email client
compatibility. You can just update styles here, and Postmark does the rest.
-->
<style type="text/css" rel="stylesheet" media="all">

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

Style-scope leakage in Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/ReportDelivery.html because <style type="text/css" rel="stylesheet" media="all"> introduces a global style block, with matching occurrences in Web/Resgrid.Web/Areas/User/Views/Records/Print.cshtml:20-20, PrintDiff.cshtml:12-12, Diff.cshtml:19-19, Workflows/New.cshtml:68-68, and Diff.cshtml:53-54. Scope selectors to the template or component so they do not leak beyond this markup.

Kody rule violation: Use component-scoped styling

Prompt for LLM

File Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/ReportDelivery.html:

Line 12:

Style-scope leakage in Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/ReportDelivery.html because <style type="text/css" rel="stylesheet" media="all"> introduces a global style block, with matching occurrences in Web/Resgrid.Web/Areas/User/Views/Records/Print.cshtml:20-20, PrintDiff.cshtml:12-12, Diff.cshtml:19-19, Workflows/New.cshtml:68-68, and Diff.cshtml:53-54. Scope selectors to the template or component so they do not leak beyond this markup.

Talk to Kody by mentioning @kody

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

{
(await _service.SendReminderAsync(Dept, "chief", "b2")).Reason.Should().Be(RecordsReminderResult.ReasonNotOpen);

for (var i = 0; i < 30; i++)

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

False positive in Tests/Resgrid.Tests/Rms/RecordsAccountabilityServiceTests.cs because for (var i = 0; i < 30; i++) uses < rather than == or != in the loop termination condition. Do not flag this line under rule 19.

Kody rule violation: Avoid equality operators in loop termination conditions

for (var i = 0; i < 30; i++)
Prompt for LLM

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

Line 182:

False positive in Tests/Resgrid.Tests/Rms/RecordsAccountabilityServiceTests.cs because for (var i = 0; i < 30; i++) uses < rather than == or != in the loop termination condition. Do not flag this line under rule 19.

Suggested Code:

			for (var i = 0; i < 30; i++)

Talk to Kody by mentioning @kody

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


private static Dictionary<string, string> Load(string path)
{
return XDocument.Load(path)

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

Unhandled external IO and XML parsing in Tests/Resgrid.Tests/Rms/RecordsPermissionRowsTests.cs because XDocument.Load(path) can fail on file access or malformed XML across the listed call sites. Wrap the load in contextual exception handling and rethrow an InvalidOperationException that includes the path and operation context.

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

try
{
	return XDocument.Load(path)
		.Root!
		.Elements("data")
		.ToDictionary(
			x => (string)x.Attribute("name")!,
			x => (string)x.Element("value") ?? string.Empty);
}
catch (IOException ex)
{
	throw new InvalidOperationException($"Failed to load resx file: {path}", ex);
}
catch (System.Xml.XmlException ex)
{
	throw new InvalidOperationException($"Invalid XML in resx file: {path}", ex);
}
Prompt for LLM

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

Line 154:

Unhandled external IO and XML parsing in Tests/Resgrid.Tests/Rms/RecordsPermissionRowsTests.cs because XDocument.Load(path) can fail on file access or malformed XML across the listed call sites. Wrap the load in contextual exception handling and rethrow an InvalidOperationException that includes the path and operation context.

Suggested Code:

			try
			{
				return XDocument.Load(path)
					.Root!
					.Elements("data")
					.ToDictionary(
						x => (string)x.Attribute("name")!,
						x => (string)x.Element("value") ?? string.Empty);
			}
			catch (IOException ex)
			{
				throw new InvalidOperationException($"Failed to load resx file: {path}", ex);
			}
			catch (System.Xml.XmlException ex)
			{
				throw new InvalidOperationException($"Invalid XML in resx file: {path}", ex);
			}

Talk to Kody by mentioning @kody

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

{
Narrative = "Scene narrative",
Cause = "Natural",
ContactName = "Jane Doe",

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

Personal data in test and fixture diagnostics in Tests/Resgrid.Tests/Rms/RecordsWorkflowTriggerTests.cs because ContactName = "Jane Doe" stores a raw name, with matching occurrences in Web/Resgrid.Web/Areas/User/Models/Records/RecordsListExport.cs:111-111, Core/Resgrid.Services/Records/RecordsCutoverService.cs:119-119, Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/Message.html:452-452, Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/Call.html:420-420, and Web/Resgrid.Web/Areas/User/Controllers/RecordsController.cs:1407-1407. Replace it with a hashed or tokenized surrogate unless the test explicitly validates name handling.

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

Prompt for LLM

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

Line 165:

Personal data in test and fixture diagnostics in Tests/Resgrid.Tests/Rms/RecordsWorkflowTriggerTests.cs because ContactName = "Jane Doe" stores a raw name, with matching occurrences in Web/Resgrid.Web/Areas/User/Models/Records/RecordsListExport.cs:111-111, Core/Resgrid.Services/Records/RecordsCutoverService.cs:119-119, Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/Message.html:452-452, Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/Call.html:420-420, and Web/Resgrid.Web/Areas/User/Controllers/RecordsController.cs:1407-1407. Replace it with a hashed or tokenized surrogate unless the test explicitly validates name handling.

Talk to Kody by mentioning @kody

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

if (!typeof(IEnumerable).IsAssignableFrom(propertyType))
return false;

var elementType = propertyType.IsGenericType ? propertyType.GetGenericArguments().FirstOrDefault() : propertyType.GetElementType();

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

Ambiguous generic argument access in Tests/Resgrid.Tests/Rms/RmsCascadeSafetyTests.cs because propertyType.IsGenericType guarantees GetGenericArguments() for a constructed generic type, so FirstOrDefault() implies an empty case that should not occur. Use First() to reflect that invariant more accurately.

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

var elementType = propertyType.IsGenericType ? propertyType.GetGenericArguments().First() : propertyType.GetElementType();
Prompt for LLM

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

Line 77:

Ambiguous generic argument access in Tests/Resgrid.Tests/Rms/RmsCascadeSafetyTests.cs because propertyType.IsGenericType guarantees GetGenericArguments() for a constructed generic type, so FirstOrDefault() implies an empty case that should not occur. Use First() to reflect that invariant more accurately.

Suggested Code:

			var elementType = propertyType.IsGenericType ? propertyType.GetGenericArguments().First() : propertyType.GetElementType();

Talk to Kody by mentioning @kody

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

if (!ClaimsAuthorizationHelper.CanViewRestrictedRecords() && snapshot.Details != null)
{
foreach (var field in RecordSnapshotSerializer.RestrictedDetailFields)
typeof(RmsOperationalRecordDetail).GetProperty(field)?.SetValue(snapshot.Details, null);

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

Reflection injection risk in Web/Resgrid.Web/Areas/User/Controllers/RecordsController.cs because typeof(RmsOperationalRecordDetail).GetProperty(field)?.SetValue(snapshot.Details, null); resolves a property name from field at runtime. Validate field against a strict allowlist or replace the reflective setter with explicit property handling.

Kody rule violation: Prevent Reflection Injection Attacks

Prompt for LLM

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

Line 594:

Reflection injection risk in Web/Resgrid.Web/Areas/User/Controllers/RecordsController.cs because typeof(RmsOperationalRecordDetail).GetProperty(field)?.SetValue(snapshot.Details, null); resolves a property name from field at runtime. Validate field against a strict allowlist or replace the reflective setter with explicit property handling.

Talk to Kody by mentioning @kody

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

{
format = JsonFormat,
exportedOn = DateTime.UtcNow,
exportedByUserId,

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

Personal data export in Web/Resgrid.Web/Areas/User/Models/Records/RecordsListExport.cs because exportedByUserId emits a raw user identifier into serialized metadata, with matching occurrences in Tests/Resgrid.Tests/Rms/RecordsWorkflowTriggerTests.cs:165-165, Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/Call.html:420-420, Web/Resgrid.Web/Areas/User/Views/Records/Edit.cshtml:44-44, Core/Resgrid.Services/Records/RecordsService.cs:306-306, Web/Resgrid.Web/Areas/User/Controllers/RecordsController.cs:1407-1407, and Core/Resgrid.Services/Records/RecordsCutoverService.cs:119-119. Omit the raw ID or replace it with a stable pseudonymized token or hash.

Kody rule violation: Mask PII and secrets in logs

// omit raw user id or replace with a stable non-PII token/hash
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Models/Records/RecordsListExport.cs:

Line 111:

Personal data export in Web/Resgrid.Web/Areas/User/Models/Records/RecordsListExport.cs because exportedByUserId emits a raw user identifier into serialized metadata, with matching occurrences in Tests/Resgrid.Tests/Rms/RecordsWorkflowTriggerTests.cs:165-165, Tests/Resgrid.Tests/Providers/Fixtures/EmailBaseline/Call.html:420-420, Web/Resgrid.Web/Areas/User/Views/Records/Edit.cshtml:44-44, Core/Resgrid.Services/Records/RecordsService.cs:306-306, Web/Resgrid.Web/Areas/User/Controllers/RecordsController.cs:1407-1407, and Core/Resgrid.Services/Records/RecordsCutoverService.cs:119-119. Omit the raw ID or replace it with a stable pseudonymized token or hash.

Suggested Code:

				// omit raw user id or replace with a stable non-PII token/hash

Talk to Kody by mentioning @kody

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

</tr>
</thead>
<tbody>
@foreach (var row in Model.Preview.PermissionMapping)

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

Implicit typing and enumeration safety issue in Web/Resgrid.Web/Areas/User/Views/Records/Activate.cshtml because @foreach (var row in Model.Preview.PermissionMapping) uses var and assumes PermissionMapping is non-null. Use an explicit, descriptive loop variable name and null-guard the sequence before enumeration.

Kody rule violation: A module should not import itself

@foreach (var permissionRow in Model.Preview?.PermissionMapping ?? Enumerable.Empty<object>())
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Records/Activate.cshtml:

Line 67:

Implicit typing and enumeration safety issue in Web/Resgrid.Web/Areas/User/Views/Records/Activate.cshtml because @foreach (var row in Model.Preview.PermissionMapping) uses var and assumes PermissionMapping is non-null. Use an explicit, descriptive loop variable name and null-guard the sequence before enumeration.

Suggested Code:

								@foreach (var permissionRow in Model.Preview?.PermissionMapping ?? Enumerable.Empty<object>())

Talk to Kody by mentioning @kody

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

<tbody>
@for (var i = 0; i < Model.AvailableUnits.Count; i++)
{
var unitId = int.Parse(Model.AvailableUnits[i].Value);

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

Format exception risk in Web/Resgrid.Web/Areas/User/Views/Records/Edit.cshtml because int.Parse(Model.AvailableUnits[i].Value) assumes valid user or IO input. Use a TryParse-style conversion and validate the expected culture or format before accepting the value.

Kody rule violation: Use TryParse for string conversions

Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Records/Edit.cshtml:

Line 223:

Format exception risk in Web/Resgrid.Web/Areas/User/Views/Records/Edit.cshtml because int.Parse(Model.AvailableUnits[i].Value) assumes valid user or IO input. Use a TryParse-style conversion and validate the expected culture or format before accepting the value.

Talk to Kody by mentioning @kody

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


@if (isCoroner)
{
<div class="section restricted">

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

Missing consent gate in Web/Resgrid.Web/Areas/User/Views/Records/PrintRevision.cshtml because

marks a sensitive restricted coroner section without evidence that a consent record is verified, with a matching occurrence in Web/Resgrid.Web/Areas/User/Views/Records/Details.cshtml:152-152. Gate the section on a valid consent check and propagate the consent identifier through the request or model.

Kody rule violation: Require explicit consent before processing sensitive data

<div class="section restricted" data-consent-required="true">
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Records/PrintRevision.cshtml:

Line 85:

Missing consent gate in Web/Resgrid.Web/Areas/User/Views/Records/PrintRevision.cshtml because <div class="section restricted"> marks a sensitive restricted coroner section without evidence that a consent record is verified, with a matching occurrence in Web/Resgrid.Web/Areas/User/Views/Records/Details.cshtml:152-152. Gate the section on a valid consent check and propagate the consent identifier through the request or model.

Suggested Code:

		<div class="section restricted" data-consent-required="true">

Talk to Kody by mentioning @kody

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

{
<span>&middot; @localizer["Revision"] @Model.RevisionNumber</span>
}
<span>&middot; @localizer["PrintedBy"]: @Model.PrintedByName</span>

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 exposure in Web/Resgrid.Web/Areas/User/Views/Records/_PrintFooter.cshtml because @Model.PrintedByName renders a personal identifier in a records footer, with similar occurrences in the listed record views, migrations, tests, and templates. Replace it with minimized non-identifying metadata or route identity display through the required audited access path.

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

@* Avoid rendering/logging identifying PHI/PII unless strictly required and authorized; prefer non-identifying metadata or a tokenized user reference. *@
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Records/_PrintFooter.cshtml:

Line 19:

PII exposure in Web/Resgrid.Web/Areas/User/Views/Records/_PrintFooter.cshtml because @Model.PrintedByName renders a personal identifier in a records footer, with similar occurrences in the listed record views, migrations, tests, and templates. Replace it with minimized non-identifying metadata or route identity display through the required audited access path.

Suggested Code:

	@* Avoid rendering/logging identifying PHI/PII unless strictly required and authorized; prefer non-identifying metadata or a tokenized user reference. *@

Talk to Kody by mentioning @kody

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

var eventTypes = Enum.GetValues(typeof(Resgrid.Model.WorkflowTriggerEventType))
.Cast<Resgrid.Model.WorkflowTriggerEventType>()
.Where(e => !usedEventTypes.Contains((int)e))
.Where(e => recordsTriggersAvailable || !Resgrid.Model.WorkflowTriggerEventTypes.IsRecordsTrigger(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

Logic change in Web/Resgrid.Web/Areas/User/Views/Workflows/New.cshtml because replacing .Where(e => recordsTriggersAvailable || !Resgrid.Model.WorkflowTriggerEventTypes.IsRecordsTrigger(e)) with two chained Where calls changes semantics and filters out all results when recordsTriggersAvailable is false, with a matching occurrence in Tests/Resgrid.Tests/Rms/RecordsGroupScopePreviewTests.cs:70-70. Preserve the original predicate or extract it into a named expression without altering behavior.

Kody rule violation: Limit Lengthy LINQ Chains

.Where(e => recordsTriggersAvailable)
.Where(e => !Resgrid.Model.WorkflowTriggerEventTypes.IsRecordsTrigger(e))
Prompt for LLM

File Web/Resgrid.Web/Areas/User/Views/Workflows/New.cshtml:

Line 11:

Logic change in Web/Resgrid.Web/Areas/User/Views/Workflows/New.cshtml because replacing .Where(e => recordsTriggersAvailable || !Resgrid.Model.WorkflowTriggerEventTypes.IsRecordsTrigger(e)) with two chained Where calls changes semantics and filters out all results when recordsTriggersAvailable is false, with a matching occurrence in Tests/Resgrid.Tests/Rms/RecordsGroupScopePreviewTests.cs:70-70. Preserve the original predicate or extract it into a named expression without altering behavior.

Suggested Code:

                        .Where(e => recordsTriggersAvailable)
                        .Where(e => !Resgrid.Model.WorkflowTriggerEventTypes.IsRecordsTrigger(e))

Talk to Kody by mentioning @kody

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

@@ -24,8 +26,10 @@ var resgrid;
}
});
$(selector).on('change', function () {
var url = resgrid.absoluteBaseUrl + '/User/Security/SetPermissionData?type=' + permType + '&data=' + encodeURIComponent(($(selector).val() || []).join(','));

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

Unhandled async failure in Web/Resgrid.Web/wwwroot/js/app/internal/security/resgrid.security.permissions.js because the AJAX request in this permission update flow has no rejection handling, which can surface failed writes as unhandled async errors across the listed occurrences. Await the request inside try/catch or attach a .fail/.catch handler and log contextual fields such as op: 'SetPermissionData', permType, and selector.

Kody rule violation: Handle async operations with proper error handling

var url = resgrid.absoluteBaseUrl + '/User/Security/SetPermissionData?type=' + permType + '&data=' + encodeURIComponent(($(selector).val() || []).join(','));
try {
  await $.ajax({
    url: url + (typeof lockValue === 'function' ? '&lockToGroup=' + lockValue() : ''),
    type: 'POST',
    headers: { 'RequestVerificationToken': antiForgeryToken() }
  });
} catch (err) {
  logger.error('set permission data failed', { op: 'SetPermissionData', permType: permType, selector: selector, err: err });
}
Prompt for LLM

File Web/Resgrid.Web/wwwroot/js/app/internal/security/resgrid.security.permissions.js:

Line 29:

Unhandled async failure in Web/Resgrid.Web/wwwroot/js/app/internal/security/resgrid.security.permissions.js because the AJAX request in this permission update flow has no rejection handling, which can surface failed writes as unhandled async errors across the listed occurrences. Await the request inside try/catch or attach a .fail/.catch handler and log contextual fields such as op: 'SetPermissionData', permType, and selector.

Suggested Code:

var url = resgrid.absoluteBaseUrl + '/User/Security/SetPermissionData?type=' + permType + '&data=' + encodeURIComponent(($(selector).val() || []).join(','));
try {
  await $.ajax({
    url: url + (typeof lockValue === 'function' ? '&lockToGroup=' + lockValue() : ''),
    type: 'POST',
    headers: { 'RequestVerificationToken': antiForgeryToken() }
  });
} catch (err) {
  logger.error('set permission data failed', { op: 'SetPermissionData', permType: permType, selector: selector, err: err });
}

Talk to Kody by mentioning @kody

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

@ucswift

ucswift commented Sep 4, 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 0b699f4 into master Sep 4, 2026
15 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