Skip to content

Add SQLite outbox pattern support (issue #149)#151

Merged
blehnen merged 25 commits into
masterfrom
sqlite-outbox-149
May 25, 2026
Merged

Add SQLite outbox pattern support (issue #149)#151
blehnen merged 25 commits into
masterfrom
sqlite-outbox-149

Conversation

@blehnen
Copy link
Copy Markdown
Owner

@blehnen blehnen commented May 22, 2026

Summary

Adds transactional outbox-pattern producer support to the SQLite transport (issue #149), mirroring the existing SqlServer and PostgreSQL outbox shape. Inbox / hold-transaction support remains a permanent non-goal — SQLite's BEGIN EXCLUSIVE-on-write semantics make it structurally non-viable (PR #143 confirmed this across 14 Jenkins runs).

4 phases, 23 commits, ADVISORY security audit (0 critical / 0 important).

What's included

  • Production code: SqliteRelationalProducerQueue<T>, SqLiteExternalDbNameExtractor (with symmetric .db3-stripping), SqLiteExternalTransactionValidator (subclass overriding Validate with Path.GetFileNameWithoutExtension on both sides before StringComparison.Ordinal compare), outbox-branch added to sync + async send handlers, DI wiring in SqLiteMessageQueueInit
  • Cross-phase production fix: IRetrySkippable guard added to SQLite retry decorators (66f7e864) — caught by Phase 3 integration test review. Without it, outbox SkipRetry=true would have silently re-entered Polly retry on SQLite.
  • Unit tests: 4 new test files in Source/DotNetWorkQueue.Transport.SQLite.Tests/Basic/ — extractor, validator subclass, producer queue, capability-cast positive-flip (replaces deleted negative-guard file). 25 unit tests, all pass locally (166 total in the SQLite Tests project pass).
  • Integration tests: 6 new files in Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/ (Base + Validation + Send + SendAsync + AdditionalData + RetryBypass). 28 test cases via [DataRow(true)] [DataRow(false)] parameterization (:memory: + on-disk). Jenkins is the runtime source of truth (local WSL libdl.so env block on the dev host).
  • Documentation: docs/outbox-pattern.md adds #### SQLite note, ### SQLite Concurrency Caveat, DB-Name table row, and restructures Supported Transports to 3 tiers (Supported / Supported with caveats / Not supported). Provider-name accuracy fix (447250fc): note uses SQLiteConnection/SQLiteTransaction from System.Data.SQLite — explicitly disambiguated from Microsoft.Data.Sqlite.
  • Lessons captured: 4 new entries in CLAUDE.md Lessons Learned (decorator-chain parity audit, provider-name distinction, FullUri DataSource gap, team-mode task-list autopilot).

Test plan

  • dotnet test Source/DotNetWorkQueue.Transport.SQLite.Tests/... — 166/166 PASS (4 m 27 s)
  • dotnet test Source/DotNetWorkQueue.Transport.RelationalDatabase.Tests/... — 221/221 PASS (495 ms)
  • PLAN-2.5 RetryBypass integration test locally — 2/2 PASS (2 s) after the production fix
  • Jenkins 14-stage matrix runs the new SQLite integration tests on Docker agents (PR triggers Jenkins)
  • CodeRabbit review (non-draft PR triggers it)

Notable

  • Capability cast: producer is IRelationalProducerQueue<T> now succeeds for SQLite producers, matching PG/SS.
  • Inbox stays out: git grep IRelationalWorkerNotification Source/DotNetWorkQueue.Transport.SQLite/ returns zero hits. Permanent non-goal documented in docs/outbox-pattern.md § SQLite Concurrency Caveat with link to issue Add outbox pattern support for SQLite (inbox not workable — single-writer lock) #149.
  • No new dependencies: zero PackageReference additions in the diff.
  • No Jenkinsfile changes: new outbox tests are auto-discovered by the existing stage('SQLite') invocation.

Deferred to follow-up (low priority)

  • 5 minor test-code cleanups documented in .shipyard/NOTES.md (method rename, weak Windows-path assertion, duplicate WHY comments, duplicate connection-info boilerplate, dead conn.Database stub)
  • Pre-existing ConnectionString.cs:25 literal "\\\\" path separator on Linux — latent risk for on-disk IT on Jenkins/Docker; not introduced by this milestone

Closes #149.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • SQLite transport supports caller-supplied-transaction outbox sends, preserving additional message data and returning correlation IDs.
  • Behavior Changes / Bug Fixes

    • Caller-transaction sends are single-attempt when appropriate; validation now blocks mismatched/invalid transactions before any queue write. Commit/rollback deterministically control visibility of queue and business rows.
  • Documentation

    • Outbox docs updated with SQLite-specific naming, connection, concurrency and retry caveats.
  • Tests

    • Extensive SQLite integration and unit tests added to cover validation, visibility, retries, and metadata preservation.

Review Change Stack

blehnen and others added 23 commits May 22, 2026 09:04
… Validate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ath normalization

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ric path normalization

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…SqlServer shape

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…lerAsync

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds RelationalSendMessageCommand fork check inside Handle() and a new
HandleExternalTransaction private method that reuses the caller's
SQLiteTransaction/SQLiteConnection without committing or disposing them.
Mirrors the PG/SqlServer pattern (PG line 106). Uses fully-qualified
System.Data.SQLite.SQLiteTransaction to avoid collision with the
DotNetWorkQueue.Transport.SQLite.Basic.SQLiteTransaction wrapper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ueInit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…it tests

6 tests: 3 base-behavior preservation checks + .db3 round-trip, :memory: mode, and mismatch-with-normalized-values for SQLite-specific normalization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8 tests covering null-transaction guards, SQLiteTransaction cast guard, validator-fires-before-guard ordering, batch validator-once-per-batch, and type-hierarchy assertions. Guard-pass happy path deferred to Phase 3 integration tests (SQLiteConnection is sealed/unmockable).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ive-path flip)

The corresponding delete of SqliteProducerDoesNotImplementRelationalTests.cs
landed earlier in commit 58cb3e0 (PLAN-1.1 scope leak — the extractor-tests
commit included the unrelated delete). This commit closes PLAN-1.4 by adding
the positive-assertion replacement file. End state matches CONTEXT-2 D2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wave 1 base class for SQLite outbox integration tests. Mirrors PostgreSqlOutboxIntegrationTestBase shape with SQLite-specific adaptations: TEXT column type, Convert.ToInt64 for INTEGER affinity, explicit-interface caveat for EnableHoldTransactionUntilMessageCommitted (hardwired false on SQLite), BEGIN EXCLUSIVE serialization documented in XML doc, WSL interop-lock workaround documented in XML doc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PLAN-2.1. 4 test methods × [DataRow(true)] [DataRow(false)] = 8 cases.
Covers DB-name mismatch, .db3 path-normalization, null-tx, and guard-pass
happy path (closes Phase 2 deferral). Local test runs fail with libdl.so
missing on WSL host — pre-existing environment issue affecting all SQLite
integration tests; Jenkins Docker agents have libdl and will execute the
suite. If Jenkins surfaces a real validator path-comparison defect, fix
forward in a follow-up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PLAN-2.2. 4 sync methods × [DataRow(true)] [DataRow(false)] = 8 cases:
Commit_SingleSend, Rollback_SingleSend, Commit_Batch, Rollback_Batch.
Real SQLiteConnection + BeginTransaction round-trip. Local libdl.so
WSL issue applies; Jenkins is the source of truth.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 3 PLAN-2.5 review surfaced that SQLite's RetryCommandHandlerOutput-
Decorator and Async variant lack the `if (command is IRetrySkippable &&
SkipRetry) return _decorated.Handle(command);` guard that PG and SqlServer
have (line 54/55 in their respective files). Without it, outbox-pattern
Send(...) with caller-managed transactions still goes through the Polly
retry loop on SQLite, defeating the IRetrySkippable contract that
RelationalSendMessageCommand carries.

Mirrors SqlServer/PG shape exactly: adds `using DotNetWorkQueue.Transport.
RelationalDatabase;` and the early-return guard at the top of Handle /
HandleAsync.

Closes a Phase 1 gap surfaced by Phase 3 integration testing — exactly
the kind of finding integration tests should catch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ts update

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All anchor targets resolve, no Tx abbreviation, identifier capitalization
correct. No content edits required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phase 4 review caught a Critical accuracy defect in the SQLite Quick Start
note: the example referenced `SqliteConnection`/`SqliteTransaction`, which
are the Microsoft.Data.Sqlite provider's type names. This transport is
built on System.Data.SQLite, whose types are `SQLiteConnection` and
`SQLiteTransaction` (note all-caps `SQLite`). A reader copy-pasting the
wrong names would compile against the wrong provider package and hit
`InvalidOperationException` at the first Send call from
GuardSQLiteTransaction.

Three corrections:
- `#### SQLite note` lines 108-109: rename to `SQLiteConnection`/
  `SQLiteTransaction` and explicitly disambiguate "from the
  System.Data.SQLite provider — not Microsoft.Data.Sqlite"
- Capability-cast comment line 62: add SQLite to the supported-transports
  list (was SqlServer + PostgreSQL only)
- `busy_timeout` bullet line 286-289: replace internal `IntegrationConnectionInfo`
  reference with caller-facing guidance "On the open SQLiteConnection
  before calling BeginTransaction()"

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tone

Adds 4 tactical lessons to CLAUDE.md ## Lessons Learned section,
distilled from the SQLite Outbox Pattern Support milestone (issue #149):

- Decorator-chain parity audit when porting features across transports
  (caught the IRetrySkippable gap in commit 66f7e86)
- System.Data.SQLite vs Microsoft.Data.Sqlite provider name distinction
  (caught the doc accuracy defect in commit 447250f)
- SQLiteConnectionStringBuilder.DataSource returns empty for FullUri=
  (latent risk, not yet observed in CI)
- Team-mode Agent dispatch shares parent task list with teammates
  (Phase 1 Wave 2 autopilot incident; 3 commits reverted)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

Adds caller-transaction outbox support for SQLite: makes base validator extensible, adds SQLite extractor/validator, implements a SQLite relational producer and handler forks that reuse caller transactions, wires retry-bypass, adds unit/integration tests, DI wiring, and documents SQLite-specific concurrency caveats.

Changes

SQLite Outbox Pattern Implementation

Layer / File(s) Summary
Base extensibility contract for external transaction validation
Source/DotNetWorkQueue.Transport.RelationalDatabase/Basic/ExternalTransactionValidator.cs
ExternalTransactionValidator is unsealed and Validate() is now virtual so transport-specific subclasses may normalize names before comparison.
SQLite transaction validation and database name extraction
Source/DotNetWorkQueue.Transport.SQLite/Basic/SqLiteExternalDbNameExtractor.cs, Source/DotNetWorkQueue.Transport.SQLite/Basic/SqLiteExternalTransactionValidator.cs
Adds SqLiteExternalDbNameExtractor (prefers FullUri, falls back to DataSource, extracts file stem) and SqLiteExternalTransactionValidator which normalizes both sides and performs an ordinal equality check, throwing on mismatch.
Retry bypass for external transactions
Source/DotNetWorkQueue.Transport.SQLite/Decorator/RetryCommandHandlerOutputDecorator.cs, Source/DotNetWorkQueue.Transport.SQLite/Decorator/RetryCommandHandlerOutputDecoratorAsync.cs
Decorators now detect IRetrySkippable with SkipRetry and delegate directly to the wrapped handler, bypassing retry/resilience pipeline.
SQLite relational producer and external-transaction dispatch
Source/DotNetWorkQueue.Transport.SQLite/Basic/SqliteRelationalProducerQueue.cs
New sealed SqliteRelationalProducerQueue<TMessage> validates external DbTransaction, enforces System.Data.SQLite.SQLiteTransaction type, and routes single/batch sync/async sends via injected command handlers.
External transaction handling in send command handlers
Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs, Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs
Adds HandleExternalTransaction/HandleExternalTransactionAsync forks that reuse caller-supplied SQLiteConnection/SQLiteTransaction, enforce job uniqueness within the caller transaction, insert message/metadata/status rows, and return last_insert_rowid() without committing/disposing caller resources.
DI wiring and integration test infrastructure
Source/DotNetWorkQueue.Transport.SQLite/Basic/SqLiteMessageQueueInit.cs, Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxIntegrationTestBase.cs
Registers the extractor, validator, and relational producer in DI. Adds SqliteOutboxIntegrationTestBase with helpers for queue/business-table lifecycle, in-transaction business inserts, queue/message assertions via SQL, and scoped cleanup.
Unit tests for validator, extractor, and producer type behavior
Source/DotNetWorkQueue.Transport.SQLite.Tests/Basic/*
Adds comprehensive unit tests for SqLiteExternalTransactionValidator (guards, normalization), SqLiteExternalDbNameExtractor (connection-string shapes, symmetry), and SqliteRelationalProducerQueue (null/non-SQLite transaction handling, validator ordering, batch validation once-per-batch, type hierarchy).
Integration tests for outbox operations
Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/*
Adds integration tests covering send/commit/rollback atomicity (single and batch), external-transaction validation guards (cross-database, closed connection, wrong transaction type), retry-bypass single-attempt verification, and additional-message-data metadata round-trip checks.
Documentation and operational guidance
docs/outbox-pattern.md, CLAUDE.md
Updates docs to list SQLite as supported with caveats, documents database-name normalization semantics and single-writer concurrency caveats, and adds operational lessons in CLAUDE.md (FullUri canonicalization, inbox limitation, decorator parity, provider naming, Shipyard agent-team dispatch caveat).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

"A rabbit penned in a dev tree,
Hopped through tests with jubilee;
Transactions kept, metadata found,
Stem names matched, retries unbound.
🐇📦🥕"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add SQLite outbox pattern support (issue #149)' clearly and concisely describes the main change: adding outbox pattern support for SQLite transport, and correctly references the linked issue.
Linked Issues check ✅ Passed All concrete coding objectives from issue #149 are met: SQLite outbox producer implementation [#149], DB-name extractor and validator with normalization [#149], retry decorator IRetrySkippable guards [#149], comprehensive unit and integration tests [#149], and documentation updates [#149].
Out of Scope Changes check ✅ Passed All changes directly support the stated #149 objectives: outbox producer implementation, extractor/validator classes, decorator fixes, test suites, and documentation—no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (1)
Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxIntegrationTestBase.cs (1)

109-110: ⚡ Quick win

Replace hold-tx with hold-transaction in comments.

Use the full word for consistency with repository naming rules.

As per coding guidelines, "Do not use the Tx abbreviation for 'transaction' — use the full word 'transaction' in all identifiers and documentation."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxIntegrationTestBase.cs`
around lines 109 - 110, Update the comment text in the
SqliteOutboxIntegrationTestBase class to use the full word "hold-transaction"
instead of the abbreviation "hold-tx"; search for occurrences in the inline
comment shown and the class XML doc above (and any other comments in that file)
and replace "hold-tx" with "hold-transaction" to comply with the repository
naming guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxIntegrationTestBase.cs`:
- Around line 147-155: Add an ActivityListener before any sends in the
SqliteOutboxIntegrationTestBase so trace decorators are invoked (register an
ActivityListener via ActivitySource.AddActivityListener at test setup or inside
CreateRelationalProducer before using the producer pipeline); harden disposal in
QueueScope.Dispose and ProducerScope.Dispose by adding an atomic guard (use an
Interlocked flag/boolean to ensure Dispose is executed only once) to prevent
double-dispose races; and update the inline comment that uses the abbreviation
"hold-tx" to read "hold transaction" for clarity. Ensure you reference
CreateRelationalProducer when adding the ActivityListener and modify
QueueScope.Dispose and ProducerScope.Dispose implementations to use Interlocked
operations and adjust the comment text accordingly.
- Around line 73-85: QueueScope.Dispose and ProducerScope.Dispose need to be
made atomic/idempotent: add a private int _disposed field to each of the nested
classes and at the start of Dispose use Interlocked.Exchange(ref _disposed, 1)
(or Interlocked.CompareExchange) to run the cleanup body only when the previous
value was 0; keep the existing cleanup calls (OCreation?.RemoveQueue(),
OCreation?.Dispose(), Scope?.Dispose(), QueueCreator?.Dispose() in QueueScope
and the corresponding disposals in ProducerScope) but wrap them so they execute
only on the first successful exchange to ensure Dispose is thread-safe and only
runs once.

In
`@Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxRetryBypassTests.cs`:
- Around line 67-77: The test currently catches any Exception into the variable
thrown after calling producer.RelationalProducer.Send(new FakeMessage(), new
AdditionalMessageData(), completedTransaction) which can mask unrelated fast
failures; change the assertion to verify the specific expected exception type
and, if applicable, the expected exception message (e.g., assert thrown is the
expected exception type and thrown.Message equals the expected text) for this
completed-transaction path, and apply the same tighter assertion to the other
similar block (the one at lines 85-87); reference the Send call, the thrown
variable, FakeMessage/AdditionalMessageData and completedTransaction when making
the replacement.

In
`@Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxValidationTests.cs`:
- Around line 134-136: The test currently only checks DB row visibility but
doesn't assert the API result from the happy-path call; update the test in
SqliteOutboxValidationTests to capture the return value of
producer.RelationalProducer.Send(FakeMessage, AdditionalMessageData,
transaction) (or any overload used) and assert it's a success/empty-error per
the Send contract (e.g., not null/false/no exception or matches expected result
type), ensuring the call to Send(...) explicitly asserts the expected success
result in addition to the existing DB assertions.

In
`@Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs`:
- Around line 253-255: The handler currently downcasts
relCommand.ExternalTransaction to System.Data.SQLite types in
SendMessageCommandHandler and SendMessageCommandHandlerAsync; replace those
sealed casts by treating relCommand.ExternalTransaction as IDbTransaction and
obtaining the connection via transaction.Connection cast to IDbConnection so the
code relies on IDbTransaction/IDbConnection abstractions (use
relCommand.ExternalTransaction as IDbTransaction, var transaction =
(IDbTransaction)relCommand.ExternalTransaction; use transaction.Connection as
IDbConnection for command/connection access), updating any variable names
(sqliteTransaction/sqliteConn) and usages to the IDb* types to remove the
provider coupling.

In
`@Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs`:
- Around line 255-256: Replace provider-specific SQLite casts in
HandleExternalTransactionAsync: stop downcasting
RelationalSendMessageCommand.ExternalTransaction to
SQLiteTransaction/SQLiteConnection and instead use IDbTransaction and
IDbConnection locals (e.g., var dbTx =
(IDbTransaction)relCommand.ExternalTransaction; var dbConn =
(IDbConnection)dbTx.Connection). Update call sites that pass
sqliteConn/sqliteTransaction into DoesJobExistQuery<IDbConnection,
IDbTransaction> and SetJobLastKnownEventCommand<IDbConnection, IDbTransaction>
to pass dbConn/dbTx. Make the same change in the sync handler
SendMessageCommandHandler.cs so both handlers are provider-agnostic.

---

Nitpick comments:
In
`@Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxIntegrationTestBase.cs`:
- Around line 109-110: Update the comment text in the
SqliteOutboxIntegrationTestBase class to use the full word "hold-transaction"
instead of the abbreviation "hold-tx"; search for occurrences in the inline
comment shown and the class XML doc above (and any other comments in that file)
and replace "hold-tx" with "hold-transaction" to comply with the repository
naming guideline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ed9adb6b-0ce2-4775-bb71-b9107778d7a8

📥 Commits

Reviewing files that changed from the base of the PR and between 26a405a and c3a5da6.

📒 Files selected for processing (22)
  • CLAUDE.md
  • Source/DotNetWorkQueue.Transport.RelationalDatabase/Basic/ExternalTransactionValidator.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxAdditionalDataTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxIntegrationTestBase.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxRetryBypassTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxSendAsyncTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxSendTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxValidationTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Tests/Basic/SqLiteExternalTransactionValidatorTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Tests/Basic/SqliteExternalDbNameExtractorTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Tests/Basic/SqliteProducerDoesNotImplementRelationalTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Tests/Basic/SqliteProducerImplementsRelationalTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Tests/Basic/SqliteRelationalProducerQueueTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Basic/SqLiteExternalDbNameExtractor.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Basic/SqLiteExternalTransactionValidator.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Basic/SqLiteMessageQueueInit.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Basic/SqliteRelationalProducerQueue.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Decorator/RetryCommandHandlerOutputDecorator.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Decorator/RetryCommandHandlerOutputDecoratorAsync.cs
  • docs/outbox-pattern.md
💤 Files with no reviewable changes (1)
  • Source/DotNetWorkQueue.Transport.SQLite.Tests/Basic/SqliteProducerDoesNotImplementRelationalTests.cs

Comment on lines +255 to +256
var sqliteTransaction = (SQLiteTransaction)relCommand.ExternalTransaction;
var sqliteConn = (SQLiteConnection)sqliteTransaction.Connection;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo_root="$(pwd)"
file="Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs"

echo "== File exists? =="
ls -la "$file"

echo "== Lines 230-360 excerpt =="
nl -ba "$file" | sed -n '230,360p'

echo "== Find all provider-specific downcasts in the async handler =="
rg -n "SQLite(Transaction|Connection)|\b(SQLiteTransaction|SQLiteConnection)\b" "$file"

echo "== Check for IDbConnection/IDbTransaction patterns in async handler =="
rg -n "\bIDb(Transaction|Connection)\b" "$file"

echo "== Look for sync-path counterpart and inspect for similar casts =="
sync_file="Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs"
if [ -f "$sync_file" ]; then
  echo "== Sync file exists: $sync_file =="
  nl -ba "$sync_file" | sed -n '200,360p'
  rg -n "SQLite(Transaction|Connection)|\b(SQLiteTransaction|SQLiteConnection)\b" "$sync_file"
  rg -n "\bIDb(Transaction|Connection)\b" "$sync_file"
else
  echo "== Sync counterpart not found at expected path: $sync_file =="
fi

echo "== Search repository for other SendMessageCommandHandler*.cs in SQLite transport =="
fd -t f "SendMessageCommandHandler*.cs" Source/DotNetWorkQueue.Transport.SQLite || true

Repository: blehnen/DotNetWorkQueue

Length of output: 321


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs"
sync_file="Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs"

echo "== Grep excerpt lines 230-360 (with line numbers via awk) =="
awk 'NR>=230 && NR<=360 {printf "%6d | %s\n", NR, $0}' "$file"

echo "== All provider-specific downcasts in async handler =="
rg -n "SQLiteTransaction|SQLiteConnection" "$file" || true

echo "== Any IDbTransaction/IDbConnection usage in async handler =="
rg -n "\bIDbTransaction\b|\bIDbConnection\b" "$file" || true

if [ -f "$sync_file" ]; then
  echo "== Grep excerpt from sync counterpart lines 230-360 =="
  awk 'NR>=230 && NR<=360 {printf "%6d | %s\n", NR, $0}' "$sync_file"

  echo "== Downcasts in sync handler =="
  rg -n "SQLiteTransaction|SQLiteConnection" "$sync_file" || true

  echo "== IDbConnection/IDbTransaction usage in sync handler =="
  rg -n "\bIDbTransaction\b|\bIDbConnection\b" "$sync_file" || true
else
  echo "== Sync counterpart not found: $sync_file =="
fi

echo "== Find other SendMessageCommandHandler*.cs in SQLite transport =="
fd -t f "SendMessageCommandHandler*.cs" Source/DotNetWorkQueue.Transport.SQLite || true

Repository: blehnen/DotNetWorkQueue

Length of output: 18793


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo_root="$(pwd)"
async_file="Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs"
sync_file="Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs"

echo "== Async handler top usings (first 80 lines) =="
sed -n '1,90p' "$async_file"

echo "== Sync handler top usings (first 90 lines) =="
sed -n '1,90p' "$sync_file"

echo "== Where does SQLiteTransaction / SQLiteConnection resolve in async file? (grep for type declarations/usings) =="
rg -n "\bSQLiteTransaction\b|\bSQLiteConnection\b" "$async_file" | head -n 50

echo "== Find definitions of SQLiteTransaction in SQLite transport =="
rg -n "class SQLiteTransaction|interface .*SQLiteTransaction|struct SQLiteTransaction" Source/DotNetWorkQueue.Transport.SQLite --glob='*.cs' || true

echo "== Find definitions of SQLiteConnection in SQLite transport =="
rg -n "class SQLiteConnection|interface .*SQLiteConnection|struct SQLiteConnection" Source/DotNetWorkQueue.Transport.SQLite --glob='*.cs' || true

echo "== Find GuardSQLiteTransaction implementation =="
rg -n "GuardSQLiteTransaction" Source/DotNetWorkQueue.Transport.SQLite --glob='*.cs' || true

Repository: blehnen/DotNetWorkQueue

Length of output: 12006


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Files of interest
rel_cmd_files=$(fd -t f "RelationalSendMessageCommand*.cs" Source/DotNetWorkQueue.Transport --hidden || true)
echo "== RelationalSendMessageCommand files =="
echo "$rel_cmd_files"

# Dump the definition (small file only)
for f in $rel_cmd_files; do
  echo "== Inspect: $f (first 220 lines) =="
  wc -l "$f"
  sed -n '1,220p' "$f"
done

echo "== ISQLiteTransactionWrapper =="
sqlite_trans_file="Source/DotNetWorkQueue.Transport.SQLite/ISQLiteTransaction.cs"
if [ -f "$sqlite_trans_file" ]; then
  wc -l "$sqlite_trans_file"
  sed -n '1,220p' "$sqlite_trans_file"
else
  echo "missing $sqlite_trans_file"
fi

echo "== SqliteRelationalProducerQueue excerpts around GuardSQLiteTransaction =="
producer_file="Source/DotNetWorkQueue.Transport.SQLite/Basic/SqliteRelationalProducerQueue.cs"
wc -l "$producer_file"
sed -n '150,240p' "$producer_file"

echo "== Search for ExternalTransaction assignment in SqliteRelationalProducerQueue =="
rg -n "ExternalTransaction|ISQLiteTransactionWrapper|GuardSQLiteTransaction" "$producer_file"

Repository: blehnen/DotNetWorkQueue

Length of output: 5647


🏁 Script executed:

#!/bin/bash
set -euo pipefail

async_file="Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs"
sync_file="Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs"

echo "== Locate SendMessage.GetMainCommand signature =="
rg -n "GetMainCommand\\(" Source/DotNetWorkQueue --glob='*.cs'

echo "== Locate SendMessage.CreateMetaDataRecord signature =="
rg -n "CreateMetaDataRecord\\(" Source/DotNetWorkQueue --glob='*.cs'

echo "== Locate RelationalSendMessageCommand definition =="
rg -n "class RelationalSendMessageCommand|struct RelationalSendMessageCommand|record RelationalSendMessageCommand" Source/DotNetWorkQueue --glob='*.cs' || true

# If found, print the relevant section around ExternalTransaction
rel_cmd_file="$(rg -l "RelationalSendMessageCommand" Source/DotNetWorkQueue --glob='*.cs' | head -n 20 | tr '\n' ' ')"
echo "== Candidate files containing RelationalSendMessageCommand (top 20) =="
echo "$rel_cmd_file"

# Better: direct search for ExternalTransaction property usage
echo "== Search for ExternalTransaction property/type =="
rg -n "ExternalTransaction" Source/DotNetWorkQueue --glob='*.cs' | head -n 50

Repository: blehnen/DotNetWorkQueue

Length of output: 117


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find SendMessage helper type files =="
fd -t f "SendMessage*.cs" Source || true
fd -t f "*SendMessage*.cs" Source/DotNetWorkQueue.Transport.SQLite || true
fd -t f "*SendMessage*.cs" Source/DotNetWorkQueue.Transport.RelationalDatabase || true
fd -t f "SendMessage.cs" Source || true

echo "== Search for GetMainCommand/CreateMetaDataRecord without assuming filenames =="
rg -n "GetMainCommand|CreateMetaDataRecord" Source --glob='*.cs' || true

echo "== Find RelationalSendMessageCommand definition =="
rg -n "RelationalSendMessageCommand" Source --glob='*.cs' | head -n 50

rel_cmd_path="$(rg -l "RelationalSendMessageCommand" Source --glob='*.cs' | head -n 1 || true)"
echo "== Candidate RelationalSendMessageCommand file =="
echo "$rel_cmd_path"
if [ -n "${rel_cmd_path:-}" ] && [ -f "$rel_cmd_path" ]; then
  sed -n '1,220p' "$rel_cmd_path"
  rg -n "ExternalTransaction" "$rel_cmd_path" || true
fi

Repository: blehnen/DotNetWorkQueue

Length of output: 27844


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rel_cmd="Source/DotNetWorkQueue.Transport.RelationalDatabase/Basic/Command/RelationalSendMessageCommand.cs"
if [ -f "$rel_cmd" ]; then
  echo "== RelationalSendMessageCommand.cs snippet (first 140 lines) =="
  sed -n '1,160p' "$rel_cmd"
  echo "== ExternalTransaction property/type =="
  rg -n "ExternalTransaction" "$rel_cmd" || true
  echo "== ExternalTransaction declaration context =="
  sed -n '1,220p' "$rel_cmd" | sed -n '1,220p'
else
  echo "Missing: $rel_cmd"
  # Fallback: search for the class file
  f="$(rg -l "class RelationalSendMessageCommand" Source --glob='*.cs' | head -n 1 || true)"
  echo "Found candidate: $f"
  [ -n "$f" ] && sed -n '1,200p' "$f"
fi

Repository: blehnen/DotNetWorkQueue

Length of output: 8274


🏁 Script executed:

#!/bin/bash
set -euo pipefail

send_message_file="Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessage.cs"
wc -l "$send_message_file"
sed -n '1,120p' "$send_message_file"
sed -n '120,260p' "$send_message_file"

rg -n "GetMainCommand\\(|CreateMetaDataRecord" "$send_message_file"

Repository: blehnen/DotNetWorkQueue

Length of output: 10854


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file="Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs"
echo "== Occurrences of provider-specific SQLite casts in async handler =="
rg -n "\(\s*SQLite(Transaction|Connection)\s*\)" "$file" || true
echo "== Occurrences of any SQLite* identifiers in async handler (casts/usage) =="
rg -n "SQLite(Transaction|Connection|Command|DataReader|Parameter)" "$file" || true

Repository: blehnen/DotNetWorkQueue

Length of output: 825


Remove provider-specific SQLite downcasts in HandleExternalTransactionAsync (use IDbTransaction/IDbConnection only).

RelationalSendMessageCommand.ExternalTransaction is a DbTransaction, but SendMessageCommandHandlerAsync.HandleExternalTransactionAsync downcasts it to SQLiteTransaction/SQLiteConnection (lines 255-256). Replace those with IDbTransaction/IDbConnection-typed locals to keep the handler provider-agnostic, including at the call sites where sqliteConn/sqliteTransaction are passed into DoesJobExistQuery<IDbConnection, IDbTransaction> and SetJobLastKnownEventCommand<IDbConnection, IDbTransaction> (lines 274-275, 329-330).

The sync handler in the same folder still contains the same sealed casts (lines 253-254 in SendMessageCommandHandler.cs), so apply the same abstraction fix there for parity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs`
around lines 255 - 256, Replace provider-specific SQLite casts in
HandleExternalTransactionAsync: stop downcasting
RelationalSendMessageCommand.ExternalTransaction to
SQLiteTransaction/SQLiteConnection and instead use IDbTransaction and
IDbConnection locals (e.g., var dbTx =
(IDbTransaction)relCommand.ExternalTransaction; var dbConn =
(IDbConnection)dbTx.Connection). Update call sites that pass
sqliteConn/sqliteTransaction into DoesJobExistQuery<IDbConnection,
IDbTransaction> and SetJobLastKnownEventCommand<IDbConnection, IDbTransaction>
to pass dbConn/dbTx. Make the same change in the sync handler
SendMessageCommandHandler.cs so both handlers are provider-agnostic.

…xtractor

The outbox validator failed every FullUri=file:NAME?mode=memory&cache=shared
integration test (Jenkins, issue #149 / PR #151) with:

  Caller-supplied transaction's connection points to database
  'file:Iabc?mode=memory&cache=shared' but the queue is configured
  for database ''.

Root cause is asymmetric connection-string parsing. SQLiteConnectionStringBuilder
populates FullUri (not DataSource) for the FullUri= form, so the queue side
(SqliteConnectionInformation.Container -> DbDataSource.DataSource -> builder.DataSource)
returns empty. The connection side reads conn.DataSource directly, which the
opened FullUri connection reports as the raw URI. The Ordinal compare always
fails for in-memory shared databases.

The fix routes both sides through SqLiteExternalDbNameExtractor.Canonicalize
(new internal static), which parses connection strings via
SQLiteConnectionStringBuilder and selects FullUri ?? DataSource before
Path.GetFileNameWithoutExtension. Identical normalization on both sides
guarantees symmetry across all supported connection-string forms:
Data Source=, FullUri=file:..., and :memory:.

DbDataSource and SqliteConnectionInformation.Container are unchanged so the
fix has zero blast radius outside the outbox path.

Unit tests rewritten to mock conn.ConnectionString (was conn.DataSource) and
extended with FullUri symmetry cases; 171/171 SQLite unit tests pass.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 24, 2026

Codecov Report

❌ Patch coverage is 73.96450% with 44 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.72%. Comparing base (a288dcd) to head (348d334).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
.../Basic/CommandHandler/SendMessageCommandHandler.cs 51.38% 29 Missing and 6 partials ⚠️
...port.SQLite/Basic/SqliteRelationalProducerQueue.cs 84.90% 6 Missing and 2 partials ⚠️
...port.SQLite/Basic/SqLiteExternalDbNameExtractor.cs 90.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #151      +/-   ##
==========================================
- Coverage   89.78%   89.72%   -0.07%     
==========================================
  Files        1002     1005       +3     
  Lines       29703    29872     +169     
  Branches     2405     2426      +21     
==========================================
+ Hits        26670    26803     +133     
- Misses       2368     2395      +27     
- Partials      665      674       +9     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

CodeRabbit review on PR #151 surfaced 6 actionable findings; all addressed.

PRODUCTION (Major: sealed-cast coupling per CLAUDE.md lesson):
- SendMessageCommandHandler.HandleExternalTransaction and the async fork
  no longer downcast relCommand.ExternalTransaction to System.Data.SQLite's
  sealed SQLiteTransaction/SQLiteConnection. Both handlers now operate on
  IDbTransaction/IDbConnection only — the producer's GuardSQLiteTransaction
  still enforces the concrete type at the API boundary. Removing the casts:
    * matches the PG/SS transport discipline (sealed-cast lesson, CLAUDE.md)
    * makes the outbox-fork path unit-testable with NSubstitute on the IDb*
      interfaces (the sealed types cannot be mocked at all)

TESTS (Minor + Major fixes):
- SqliteOutboxIntegrationTestBase: added EnsureActivityListenerRegistered
  helper + ClassInitialize hooks across all 5 outbox suites, so trace
  decorators are exercised (CLAUDE.md trace-decorator coverage lesson;
  mirrors PG/SS inbox base).
- QueueScope/ProducerScope Dispose() now guard with Interlocked.Exchange
  for idempotent double-dispose safety.
- SqliteOutboxValidationTests.Validation_GuardPass_HappyPath_SingleSend
  now asserts result.HasError == false in addition to row visibility.
- SqliteOutboxRetryBypassTests.RetryBypass_TransientError_SingleAttempt
  uses Assert.ThrowsExactly<InvalidOperationException> instead of catch-all
  + null check — false-positive resistance.
- Comment cleanup: "hold-tx" → "hold transaction" (matches no-Tx-abbreviation
  preference and CodeRabbit ask).

LESSON CAPTURE:
- CLAUDE.md: new SQLite FullUri/SQLiteConnectionStringBuilder symmetry
  lesson anchored to commit d111b0b — documents the asymmetric-builder
  bug shape for future SQLite work.

171/171 SQLite unit tests pass; both prod and IT projects build clean.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs (1)

21-21: 💤 Low value

Unused using System.Data.SQLite; directive.

The handler now operates exclusively on IDbTransaction/IDbConnection abstractions. The System.Data.SQLite namespace import is no longer referenced anywhere in this file and can be removed.

♻️ Suggested fix
 using System;
 using System.Data;
-using System.Data.SQLite;
 using System.Diagnostics.CodeAnalysis;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs`
at line 21, Remove the unused System.Data.SQLite using directive from
SendMessageCommandHandler.cs; the handler now uses IDbTransaction/IDbConnection
abstractions (e.g., the SendMessageCommandHandler class and its methods) so
delete the top-level "using System.Data.SQLite;" import and verify there are no
remaining references to types from that namespace in the
SendMessageCommandHandler class.
Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs (1)

21-21: 💤 Low value

Unused using System.Data.SQLite; directive.

Same as the sync handler — the async handler now operates on IDbTransaction/IDbConnection abstractions only. This import is no longer referenced.

♻️ Suggested fix
 using System;
 using System.Data;
-using System.Data.SQLite;
 using System.Diagnostics.CodeAnalysis;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs`
at line 21, Remove the unused System.Data.SQLite import in
SendMessageCommandHandlerAsync.cs: delete the `using System.Data.SQLite;`
directive at the top of the file (the async handler uses
IDbTransaction/IDbConnection abstractions only, similar to the sync handler),
ensuring the class SendMessageCommandHandlerAsync compiles without that
redundant using.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLAUDE.md`:
- Line 182: Update the Lessons Learned paragraph to consistently state that the
FullUri comparison bug was fixed (commit d111b0b5) and remove or reword the
sentence claiming the issue is still latent in CI; reference the implemented fix
by name and mechanism (SqLiteExternalDbNameExtractor.Canonicalize which uses
SQLiteConnectionStringBuilder and selects FullUri ?? DataSource, and the
validator now using Canonicalize(_connectionInfo.ConnectionString)) and drop the
contradictory statement about Container/DbDataSource.DataSource vs
builder.DataSource to reflect the current resolved state.

---

Nitpick comments:
In
`@Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs`:
- Line 21: Remove the unused System.Data.SQLite using directive from
SendMessageCommandHandler.cs; the handler now uses IDbTransaction/IDbConnection
abstractions (e.g., the SendMessageCommandHandler class and its methods) so
delete the top-level "using System.Data.SQLite;" import and verify there are no
remaining references to types from that namespace in the
SendMessageCommandHandler class.

In
`@Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs`:
- Line 21: Remove the unused System.Data.SQLite import in
SendMessageCommandHandlerAsync.cs: delete the `using System.Data.SQLite;`
directive at the top of the file (the async handler uses
IDbTransaction/IDbConnection abstractions only, similar to the sync handler),
ensuring the class SendMessageCommandHandlerAsync compiles without that
redundant using.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d850ab6-ec37-4694-98e5-98f9ca59aea7

📥 Commits

Reviewing files that changed from the base of the PR and between d111b0b and 348d334.

📒 Files selected for processing (9)
  • CLAUDE.md
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxAdditionalDataTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxIntegrationTestBase.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxRetryBypassTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxSendAsyncTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxSendTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Integration.Tests/Outbox/SqliteOutboxValidationTests.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandler.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs

Comment thread CLAUDE.md
- **Plan code shapes can drift from the actual API surface.** Outbox PR #138 / REVIEW-1.2 caught a missing `using DotNetWorkQueue.Configuration;` in the `docs/outbox-pattern.md` tutorial code block — copy-paste would have failed CS0246 because `QueueConnection` lives in `DotNetWorkQueue.Configuration` not `DotNetWorkQueue` root. Pattern: reviewers for doc/code-example plans should TRACE the example against the current API surface (mentally compile it), not just verify it matches the plan's code shape.
- **Phase scope reframing — RESEARCH should validate the phase isn't already done.** ROADMAP Phase 7 framed as "add XML doc comments to phases 2-4 public types." RESEARCH §1 surfaced that the builders had already added docs as they went. Phase 7 reframed to a VERIFICATION pass + csproj gate fixes (net8 `<DocumentationFile>` gap + ISSUE-032 NU1902 closure on Transport.SQLite). Pattern: when a phase title implies authoring, researcher should explicitly confirm the work was not already incidentally done before architect plans new authoring work.
- **`<WarningsNotAsErrors>` pattern for accepted advisory carry-forward.** ISSUE-032 (OpenTelemetry NU1902 advisory) blocked the strictest `dotnet build -c Release -p:CI=true` build path on `Transport.SQLite`. Phase 7 PLAN-1.1 / commit `88ff8996` added `<WarningsNotAsErrors>NU1902</WarningsNotAsErrors>` to all 3 Release PropertyGroup blocks (`Release|net10.0`, `Release|net8.0`, `Release|AnyCPU`). The advisory still surfaces as a visible warning on every Release build (long-term remediation isn't forgotten) but the build is no longer blocked. Reusable pattern when a ship-blocking advisory is out of scope for the current milestone.
- **`SQLiteConnectionStringBuilder.DataSource` is empty when `FullUri=` is used — both sides of any compare MUST go through the builder symmetrically.** Outbox milestone PR #151 shipped with an asymmetric validator (`Container` side via builder, connection side via `conn.DataSource`) that passed local on-disk integration but failed every shared-in-memory FullUri test on Jenkins with `'' != 'file:Iabc?mode=memory&cache=shared'`. Pre-fix: extractor read `conn.DataSource` (raw URI on opened FullUri connection); queue side read `Container` → `DbDataSource.DataSource` → `builder.DataSource` (empty). Fix (commit `d111b0b5`): added `SqLiteExternalDbNameExtractor.Canonicalize(string)` that parses via `SQLiteConnectionStringBuilder` and selects `FullUri ?? DataSource` before `Path.GetFileNameWithoutExtension`; validator now uses `Canonicalize(_connectionInfo.ConnectionString)` for `expected`. Pattern: any new "compare two SQLite database identities" code must route both sides through the same builder + selection, never one side via the builder and the other via the open connection's `DataSource` property. Same root shape as the SqlServer `ToUpperInvariant` symmetric-normalization lesson from PR #138.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve contradictory SQLite FullUri guidance in Lessons Learned.

Line 182 says the FullUri comparison bug was fixed, but Line 186 still describes it as latent and not yet seen in CI. Please reconcile these so the doc has one current state.

🧰 Tools
🪛 LanguageTool

[style] ~182-~182: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...ontainerDbDataSource.DataSourcebuilder.DataSource(empty). Fix (commitd111b0b`): adde...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[grammar] ~182-~182: Ensure spelling is correct
Context: ...ourceproperty. Same root shape as the SqlServerToUpperInvariant` symmetric-normalizat...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` at line 182, Update the Lessons Learned paragraph to consistently
state that the FullUri comparison bug was fixed (commit d111b0b5) and remove or
reword the sentence claiming the issue is still latent in CI; reference the
implemented fix by name and mechanism
(SqLiteExternalDbNameExtractor.Canonicalize which uses
SQLiteConnectionStringBuilder and selects FullUri ?? DataSource, and the
validator now using Canonicalize(_connectionInfo.ConnectionString)) and drop the
contradictory statement about Container/DbDataSource.DataSource vs
builder.DataSource to reflect the current resolved state.

@blehnen blehnen merged commit 33aa6e9 into master May 25, 2026
4 of 5 checks passed
@blehnen blehnen deleted the sqlite-outbox-149 branch May 25, 2026 02:19
blehnen added a commit that referenced this pull request May 26, 2026
Revert SQLite outbox pattern support (#151)
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.

Add outbox pattern support for SQLite (inbox not workable — single-writer lock)

1 participant