Skip to content

Bound the reconciliation query for audit records without a batch id - #576

Merged
cdcavell merged 1 commit into
mainfrom
fix/bound-malformed-audit-record-reconciliation-query
Sep 22, 2026
Merged

cdcavell merged 1 commit into
mainfrom
fix/bound-malformed-audit-record-reconciliation-query

Conversation

@cdcavell

Copy link
Copy Markdown
Collaborator

Summary

ApplicationAuditReconciler.ReconcileAsync loaded audit records with batchIds.Contains(id) || MutationBatchId == "". MaximumBatchesPerRun capped only the first half of that filter, so every audit record ever written without a batch id was loaded into memory on each pass of ApplicationAuditReconciliationHostedService. That load grew without limit.

Changes

  • Split the query in two. Batched records are still loaded by the capped batchIds. Records without a batch id now have their own query, ordered newest first (ModifiedOnUtc, then Id) and capped with Take(MaximumMalformedRecordsPerRun).
  • Added ApplicationAuditReconciliationOptions.MaximumMalformedRecordsPerRun (default 1000). It is validated at startup to the range 1–10000, the same as MaximumBatchesPerRun.
  • Added a CHANGELOG entry.

Findings already raised for older malformed records stay in the table and stay open. They just stop being refreshed once they fall outside the per-run window. The resolve step only runs for batched records, so these findings are never auto-resolved.

Tests

  • New: ReconcileAsync_RecordsWithoutBatchId_AreBoundedPerRunNewestFirst. With a cap of 2 and 5 records, only the 2 newest get findings.
  • dotnet build -c Release (warnings as errors) passes; dotnet test: 456/456 passed.

🤖 Generated with Claude Code

ReconcileAsync OR-ed records without a mutation batch id into the bounded
batch query, so every such record ever written was loaded on every pass of
the hosted reconciliation loop. Those records are now read by a separate
query, newest first, capped by the new MaximumMalformedRecordsPerRun option
(default 1000, validated 1-10000).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 21, 2026 23:59
@cdcavell
cdcavell merged commit 9b64085 into main Sep 22, 2026
20 checks passed
@cdcavell
cdcavell deleted the fix/bound-malformed-audit-record-reconciliation-query branch September 22, 2026 00:05

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Resolve the two moderate findings in ApplicationAuditReconciler.cs.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

This PR bounds reconciliation of audit records without a batch ID to prevent unbounded per-run loading.

Changes:

  • Splits batched and malformed-record queries, limiting malformed records newest-first.
  • Adds and validates MaximumMalformedRecordsPerRun (default 1000).
  • Adds regression coverage and changelog documentation.
File Reviewed changes
tests/​ProjectTemplate.Web.Tests/​ApplicationAuditReconciliationTests.cs Tests newest-first bounded processing.
src/​ProjectTemplate.Infrastructure/​Data/​Extensions/​ApplicationAuditReconciliationServiceExtensions.cs Validates the new option range.
src/​ProjectTemplate.Infrastructure/​Data/​Auditing/​ApplicationAuditReconciliationContracts.cs Defines the new option and default.
src/​ProjectTemplate.Infrastructure/​Data/​Auditing/​ApplicationAuditReconciler.cs Applies bounded malformed-record retrieval. Two moderate findings remain: align whitespace-only ID predicates (2 votes) and add a supporting top-N index or equivalent access path (1 vote).
CHANGELOG.md Documents the behavior change.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

// otherwise every such record ever written would be loaded on every reconciliation pass.
List<AuditRecord> malformedRecords = await _dbContext.AuditRecords
.AsNoTracking()
.Where(record => record.MutationBatchId == string.Empty)
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.

2 participants