Skip to content

fix: BugHunt remediation PR C-1 - persistence integrity + event delivery (M1, M2, M6, M7, M8, M10, M29, M32) - #179

Merged
ivanball merged 2 commits into
mainfrom
bugfix/bughunt-c1-persistence-events
Aug 1, 2026
Merged

fix: BugHunt remediation PR C-1 - persistence integrity + event delivery (M1, M2, M6, M7, M8, M10, M29, M32)#179
ivanball merged 2 commits into
mainfrom
bugfix/bughunt-c1-persistence-events

Conversation

@ivanball

@ivanball ivanball commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

First of two MMCA.Common BugHunt remediation PRs (waves W1 persistence + W2 events/background). Every item was adversarially re-verified against source before implementation; several fixes were corrected by that pass (see ledger notes below).

Item Fix
M1 Commit-phase transient failures surface as the new TransactionCommitAmbiguousException instead of re-entering the retry delegate after a possibly durable commit (EF's retriability check walks inner exceptions, so the failure is returned past the strategy). Multi-source witness verification deferred and documented.
M6 The bounded save-pass loop now fails loudly (naming the dirty contexts) instead of silently dropping changes materialized after pass 3.
M7 Domain-event capture skips exactly the entries SaveWithIdentityInsertAsync temporarily hides (scoped exclusion table); events raised on genuinely Unchanged aggregates keep dispatching (blanket state filtering was disqualified: it would drop UserRegistered).
M29 EFRepository.Save()/SaveChangesAsync() route through the ApplicationDbContext user-id overloads so direct saves stamp the acting user.
M2 SafeDomainEventHandler rethrows via a log-and-rethrow filter; the advertised outbox retry/dead-letter actually engages. Doc states the batch-redelivery consequence (at-least-once).
M8 Graceful shutdown best-effort persists in-memory ProcessedOn stamps (bounded 5s token, never replaces the OCE), closing the 300s lease-expiry redelivery window.
M32 BrokerEventBus batch publish: one save, one signal, empty batches no-op (mirrors InProcessEventBus).
M10 Warmup tasks bounded at 120s so a hanging dependency cannot keep /health/ready closed forever (replica-stuck-out-of-rotation fix).

Verification

  • dotnet build MMCA.Common.slnx clean Debug + Release (five analyzers at error severity)
  • Full dotnet test --solution MMCA.Common.slnx -c Release: 2547 passed, 0 failed
  • FACTS drift check: up to date (additive public surface only: TransactionCommitAmbiguousException)
  • Helpdesk source-canary built clean with 91 tests passing against this source (W1)
  • New regression tests per item, including a retry-on-any control fixture proving the commit-ambiguity path is not re-entered

🤖 Generated with Claude Code

https://claude.ai/code/session_018aXCqcfqf5Ka4hKWQphh3i

ivanball and others added 2 commits July 31, 2026 23:22
…capture, repository audit stamping

- M1: a transient failure during CommitTransaction no longer re-enters the
  execution-strategy delegate; it surfaces as the new
  TransactionCommitAmbiguousException (original as inner) so a possibly
  durable commit is never blindly re-run. Multi-source witness verification
  deliberately deferred and documented.
- M6: after the bounded save-pass loop, any cached context still reporting
  ChangeTracker.HasChanges() fails the save loudly instead of dropping the
  changes silently.
- M7: domain-event capture now skips exactly the entries
  SaveWithIdentityInsertAsync temporarily hid as Unchanged (scoped exclusion
  table); events raised on genuinely Unchanged aggregates keep dispatching.
- M29: EFRepository.Save()/SaveChangesAsync() route through the
  ApplicationDbContext user-id overloads so direct repository saves stamp
  the acting user, not the system sentinel.

BugHunt ledger: M1, M6, M7, M29 (Common wave W1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018aXCqcfqf5Ka4hKWQphh3i
…roker publish, bounded warmup

- M2: SafeDomainEventHandler rethrows via a log-and-rethrow filter instead
  of swallowing, so the advertised outbox retry/dead-letter path actually
  engages; class doc states the batch-redelivery consequence and the
  at-least-once contract subclasses must tolerate.
- M8: a graceful-shutdown cancellation mid-batch now best-effort persists
  the in-memory ProcessedOn stamps (own try/catch, 5s bounded token) before
  propagating, closing the lease-expiry redelivery window for
  already-dispatched messages.
- M32: BrokerEventBus batch publish stages every outbox row, saves once,
  signals once (mirrors InProcessEventBus); empty batches no-op.
- M10: every warmup task is bounded (120s) so a hanging dependency lands on
  the log-and-continue path and the readiness gate always opens; timeout
  and time source injectable for tests.

BugHunt ledger: M2, M8, M32, M10 (Common wave W2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018aXCqcfqf5Ka4hKWQphh3i
@ivanball
ivanball merged commit b116ea5 into main Aug 1, 2026
13 checks passed
@ivanball
ivanball deleted the bugfix/bughunt-c1-persistence-events branch August 1, 2026 13:05
ivanball added a commit that referenced this pull request Aug 1, 2026
BugHunt remediation release: changelog entry for the 24 fixes shipped in
PRs #179 and #180, with the four behavioral changes called out
(SafeDomainEventHandler rethrow, strict money payloads, audit actor
stamping on direct saves, sort/pagination corrections).


Claude-Session: https://claude.ai/code/session_018aXCqcfqf5Ka4hKWQphh3i

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant