Skip to content

Outbox pattern for relational transports (SqlServer + PostgreSQL)#138

Merged
blehnen merged 79 commits into
masterfrom
feature/outbox-pattern
May 15, 2026
Merged

Outbox pattern for relational transports (SqlServer + PostgreSQL)#138
blehnen merged 79 commits into
masterfrom
feature/outbox-pattern

Conversation

@blehnen
Copy link
Copy Markdown
Owner

@blehnen blehnen commented May 14, 2026

Outbox Pattern Support for Relational Transports

Adds transactional outbox pattern support for SqlServer and PostgreSQL transports via a new opt-in IRelationalProducerQueue<T> capability-cast interface. Producer accepts a caller-supplied DbTransaction and never commits, rolls back, or disposes caller-owned resources. The existing producer surface is preserved; non-relational transports (Memory, Redis, LiteDb, SQLite) are unaffected.

Highlights

  • IRelationalProducerQueue<T> — new derived interface in Transport.RelationalDatabase; implemented by SqlServer + PostgreSQL producers only. IProducerQueue<T> is IRelationalProducerQueue<T> capability cast surfaces the new tx-aware overloads.
  • 4 caller-tx overloads per transportSend/SendAsync × single/batch, accepting a System.Data.Common.DbTransaction parameter.
  • Lifecycle contract — producer never commits, rolls back, or disposes the caller's connection/transaction. Caller owns retry (the producer's Polly chain bypasses on this path via IRetrySkippable).
  • Cross-DB validationExternalTransactionValidator throws InvalidOperationException before any write if the caller's transaction connection points to a different database than the queue's configured catalog.
  • 24 integration tests — 12 per transport (method-coverage matrix + validation + retry-bypass + AdditionalMessageData round-trip). Validated on Jenkins full 14-stage matrix.
  • docs/outbox-pattern.md — tutorial + 5 reference sections (lifecycle, retry, schema deploy prereq, DB-name comparison semantics, supported transports). README points to it.

PROJECT.md Success Criteria

§SC Criterion Status
#1 IRelationalProducerQueue<T> exists, implemented by SqlServer + PG producers MET
#2 Memory, Redis, LiteDb, SQLite producers do NOT implement the interface MET
#3 Capability-cast pattern works at runtime MET
#4 Atomic commit verified (queue row + business row visible) MET
#5 Atomic rollback verified (neither row present) MET
#6 Cross-DB validation throws before write MET
#7 Caller-owned resources never disposed by producer MET
#8 Polly retry decorator bypass under transient failure MET
#9 No regressions in existing test suite MET
#10 docs/outbox-pattern.md covers lifecycle, retry, capability-cast, schema-deploy, DB-name semantics, supported-transports MET
#11 Jenkins green on full 14-stage matrix before merge MET

All 11 success criteria satisfied.

CI

Jenkins PR-138 build #6 SUCCESS on 24a1e3d7 (latest HEAD pushes also green). All 14 stages, GH Actions CI, CodeRabbit, codecov/patch, codecov/project: SUCCESS.

Test plan

  • All Phase 6 integration tests green on Jenkins (24 tests, 12 per transport)
  • dotnet build "Source/DotNetWorkQueueNoTests.sln" -c Release -p:CI=true returns 0 errors, 0 CS1591
  • Source Link metadata verified in nuspec (<repository ... commit="9156ad25..." />)
  • README docs/outbox-pattern.md link resolves
  • Negative-path tests confirm Memory/Redis/LiteDb/SQLite producers do NOT implement the interface
  • Cross-phase security audit CLEAN (see .shipyard/AUDIT-SHIP.md)

Known follow-up items (non-blocking)

  • ISSUE-037 — *OutboxAdditionalDataTests priority round-trip not asserted (symmetric coverage gap, both transports)
  • ISSUE-039 — PROJECT.md "OrdinalIgnoreCase vs Ordinal" text outdated (Phase 3 pass-through fix made both transports symmetric Ordinal)
  • ISSUE-040 — docs/outbox-pattern.md has no SendAsync worked example (deferred coverage)
  • ISSUE-041 — IRelationalProducerQueue<T> XML doc link is plain-text <c> not <see href> (needs stable master URL post-merge)
  • ISSUE-042 — SendMessageCommand.ExternalTransaction is public init (future-proofing; no exploitable risk for current transports)

Lessons learned (5 captured in CLAUDE.md + LESSONS.md)

  • String-comparator drift — normalize both sides or neither
  • No Tx abbreviation for "transaction" — use the full word
  • Plan code shapes can drift from current API surface; reviewers should trace examples mentally
  • Phase scope reframing — RESEARCH should validate the phase isn't already done
  • <WarningsNotAsErrors> pattern for accepted advisory carry-forward

Documentation

  • docs/outbox-pattern.md — new user-facing doc
  • README.md — single bullet under "High-level features"
  • XML doc comments on every new public type (verified zero CS1591 warnings on Release -p:CI=true build)
  • Wiki page — deferred to manual post-ship task per CONTEXT-7 Decision 1

Generated with Shipyard 7-phase workflow. See .shipyard/MILESTONE-REPORT.md for the full delivery record.

🤖 Generated with Claude Code

blehnen and others added 30 commits May 12, 2026 16:09
Brainstorm output for relational-transport caller-supplied
transaction support (outbox pattern, producer-side, SqlServer +
PostgreSQL only). Captures the IRelationalProducerQueue<T>
capability-cast design, ownership/validation invariants, and the
7-phase risk-front-loaded roadmap that starts with a throwaway
Polly-bypass spike before any production code lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 6 was scenario-driven (12 tests) but didn't ensure every new
public method on IRelationalProducerQueue<T> had its own integration
test. Since SendMessageCommandHandlerAsync is a separate class from
SendMessageCommandHandler, async branches need explicit integration
coverage — codecov on this repo is driven primarily by integration
tests, so sync coverage doesn't infer async.

Phase 6 expanded to ~22 tests (11 per transport): 8 method×outcome
atomic tests, 1 IAdditionalMessageData round-trip, 2 validation, 1
retry-bypass. Total project estimate moves to 46-61h.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 is a discovery spike resolving PROJECT.md Risk #1. Research
finds the decorator chain is identical on both SqlServer and
PostgreSQL (Trace(Retry(Handler))) and the existing retry decorator
already has two fallthrough branches (no-pipeline + PR-#121 shutdown
race). Recommended mechanism: IRetrySkippable marker interface in
Transport.Shared, evaluated as a third fallthrough branch.

Single plan, three tasks:
1. Memo at .shipyard/notes/phase-1-polly-bypass-spike.md
2. Throwaway PoC test (_SpikePollyBypassPoC.cs) demonstrating the
   marker mechanism in a near-copy of the retry decorator
3. PROJECT.md Risk #1 downgrade to LOW

Critique verdict READY: all file paths exist, API refs resolve,
verification commands runnable, no hidden deps.

Researcher and verifier agents both stalled during dispatch — used
orchestrator-direct pattern per feedback_agent_lockups.md and the
[2026-04-17] project memory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document decorator inventory (SqlServer + PostgreSQL sync/async),
confirm no per-transport divergence, choose IRetrySkippable marker
mechanism, enumerate Phase 2 files-to-touch list, and downgrade
Risk #1 to low.
Spike-local marker interface, a SendMessageCommand subclass that
implements it, and a near-copy of RetryCommandHandlerOutputDecorator
with the proposed early-return branch. Two tests cover the positive
bypass path (single inner-handler call, no registry access) and the
negative pipeline path. Production decorator and SendMessageCommand
are untouched; Phase 2 Task 1 deletes this file.
Risk #1 (Polly decorator bypass cleanness) moves from mid to low —
mechanism proven feasible by Phase 1 spike. Links to the durable
memo at .shipyard/notes/phase-1-polly-bypass-spike.md.
Verify=PASS (4/4 plan checks + 141/141 SqlServer.Tests, 0 regressions)
Audit=CLEAN (no production code, no deps changed)
Simplify=LOW (accept as-is, throwaway scope)
Docs=SUFFICIENT (memo is the deliverable; Phase 7 owns docs/outbox-pattern.md)
5 plans, 15 tasks, 3 waves. Verify=PASS, Critique=READY.

Wave 1 (sequential prereqs):
  PLAN-1.1: delete PoC + SendMessageCommand.ExternalTransaction + IRetrySkippable

Wave 2 (parallel):
  PLAN-2.1: IExternalDbNameExtractor + ExternalTransactionValidator + tests
  PLAN-2.2: RelationalSendMessageCommand + IRelationalProducerQueue<T> + RelationalProducerQueue<T>

Wave 3 (parallel):
  PLAN-3.1: SqlServer retry-decorator branches (sync+async) + tests
  PLAN-3.2: PostgreSQL retry-decorator branches (sync+async) + tests
3 plans, 7 tasks, 2 waves. Verify=PASS, Critique=READY.

Wave 1 (foundation):
  PLAN-1.1: SqlServerExternalDbNameExtractor + SqlServerRelationalProducerQueue<T>
            + DI wiring + 9 unit tests (2 extractor, 6 producer subclass,
            1 capability-cast smoke)

Wave 2 (parallel handler forks):
  PLAN-2.1: HandleExternalTx fork in SendMessageCommandHandler.cs + 3 smoke tests
  PLAN-2.2: HandleExternalTxAsync fork in SendMessageCommandHandlerAsync.cs + 3 smoke tests

4 user decisions captured in CONTEXT-3.md:
  1. Batch caller-tx path: sequential foreach (NOT Parallel.ForEach) — ADO.NET tx not thread-safe
  2. Fork placement: early-branch inside existing handler
  3. Validator invocation: in producer override before command construction
  4. Plan structure: 3 plans / 2 waves

Research surfaced two RESEARCH.md §11 discrepancies vs CONTEXT-3:
  - Existing SendMessageCommandHandler is SqlConnection/SqlTransaction-typed
    end-to-end; fork is grandfathered to cast (CLAUDE.md no-sealed rule
    applies to NEW handlers only)
  - Direct fork unit tests infeasible (SqlConnection sealed → NSubstitute can't
    mock); test surface shifted to producer-subclass tests in PLAN-1.1 +
    source-grep smoke tests in PLAN-2.1/2.2

Phase 3 compile-targets Phase 2's planned (not yet built) API surface.
Phase 2 build must complete before Phase 3 build starts.
Removes the spike PoC file (_SpikePollyBypassPoC.cs) per Phase 2
CONTEXT-2 Exit Criterion 8. The PoC's mechanism is captured in
.shipyard/notes/phase-1-polly-bypass-spike.md; the file itself was
throwaway-by-design and has no production dependencies. The 3 baseline
RetryCommandHandlerOutputDecoratorTests continue to pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mand

Adds an optional DbTransaction ExternalTransaction { get; init; }
property to the existing SendMessageCommand class in Transport.Shared.
When set by a caller, the relational send-message handler will skip
its internal connection/transaction management and use the supplied
transaction's connection. When null (the default), behavior is
unchanged. The property is init-only so all 4 existing constructor
call sites in Transport.Shared/Basic/SendMessages.cs compile without
modification.

Wave 1 PLAN-1.1 Task 2 of the outbox pattern feature. The
IRetrySkippable wiring lives in the derived RelationalSendMessageCommand
(Transport.RelationalDatabase) to keep Transport.Shared free of
references to Transport.RelationalDatabase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new marker interface IRetrySkippable in
Transport.RelationalDatabase. Command objects that implement this
interface can opt out of the relational retry decorator on a per-call
basis by returning true from SkipRetry. The decorator inspects the
property at Handle() time and invokes the inner handler directly when
the flag is set.

The interface lives in Transport.RelationalDatabase (not
Transport.Shared) per CONTEXT-2 Decision 2 Option B, so the outbox
bypass implementation in the Wave 2 derived RelationalSendMessageCommand
can implement it without cross-layer references. SqlServer and
PostgreSQL transport projects already reference
Transport.RelationalDatabase, so the Wave 3 decorator branches need
no new ProjectReference entries.

Wave 1 PLAN-1.1 Task 3 of the outbox pattern feature.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Derived class extending SendMessageCommand with IRetrySkippable. Sets
base ExternalTransaction via constructor. SkipRetry => ExternalTransaction
!= null wires the Wave 3 retry-decorator bypass.

Plan: .shipyard/phases/2/plans/PLAN-2.2.md Task 1
Per-provider database-name extractor contract for the outbox pattern
external-transaction validator. Phase 2 ships only the interface;
implementations (SqlServer = OrdinalIgnoreCase, PostgreSQL = Ordinal)
land in Phase 3/4.

Plan: .shipyard/phases/2/plans/PLAN-2.1.md Task 1
Capability-cast extension of IProducerQueue<TMessage> exposing 6
caller-supplied-transaction Send/SendAsync overloads. Used as a runtime
capability test (cast IProducerQueue<T> as IRelationalProducerQueue<T>);
Memory/Redis/LiteDb/SQLite producers deliberately do not implement it.

Batch overloads use List<QueueMessage<,>> to match existing
IProducerQueue shape (PROJECT.md spec used IEnumerable; deviation
flagged for verifier).

Plan: .shipyard/phases/2/plans/PLAN-2.2.md Task 2
Sealed validator running the 4 PROJECT.md checks (null tx, null conn,
non-open conn, db-name mismatch) against caller-supplied DbTransactions
before the producer enlists in them. Uses IConnectionInformation.Container
as the configured-DB accessor (transport-agnostic per RESEARCH §6).
DI registration deferred to Phase 3/4 alongside per-provider
IExternalDbNameExtractor implementations.

Plan: .shipyard/phases/2/plans/PLAN-2.1.md Task 2
5 MSTest 4.x tests covering each PROJECT.md §Validation failure mode
plus the happy path: null transaction, null connection, non-open
connection, DB-name mismatch (both names asserted in error per
diagnostics requirement), and the all-checks-pass case. NSubstitute
mocks DbTransaction + DbConnection abstract base classes per
CLAUDE.md mocking lesson (DbTransaction.Connection lives on the base,
not on IDbTransaction).

Plan: .shipyard/phases/2/plans/PLAN-2.1.md Task 3
Inherits ProducerQueue<T>, implements IRelationalProducerQueue<T>. 6
public tx-aware overloads route to 4 protected virtual hooks
(SendWithExternalTransaction + Async + Batch + BatchAsync). Each hook
throws InvalidOperationException by default; Phase 3 (SqlServer) and
Phase 4 (PostgreSQL) override them to enlist queue INSERTs on the
caller's transaction.

Plan: .shipyard/phases/2/plans/PLAN-2.2.md Task 3
5 plans, 3 waves, 15 atomic task commits land the foundation layer for
the outbox pattern. New types in Transport.RelationalDatabase:
IRetrySkippable marker, IExternalDbNameExtractor, ExternalTransactionValidator,
RelationalSendMessageCommand, IRelationalProducerQueue<T>, RelationalProducerQueue<T>.
SqlServer + PostgreSQL retry decorators gain IRetrySkippable bypass branches
(sync + async, 4 total).

Gates: verify=PASS (1395/1395 tests), audit=CLEAN, simplify=LOW_FINDINGS,
docs=SUFFICIENT. ISSUE-032 carries forward (pre-existing NU1902 on
Transport.SQLite from OpenTelemetry 1.15.2 advisory — not Phase 2's
regression; Phase 1 baseline already exhibits it on Release CI=true).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blehnen and others added 3 commits May 15, 2026 08:52
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n round-trip)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lData complete)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 15, 2026
@blehnen blehnen marked this pull request as ready for review May 15, 2026 14:55
…SUE-036)

Mirrors commit 9858f04 which renamed the SqlServer outbox feature variable
names. PG Wave 2 builder followed the pre-rename plan code shapes verbatim;
simplifier flagged the cross-transport inconsistency during Phase 6 close-out.
Test-only naming change; Jenkins green required to confirm no regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
blehnen and others added 6 commits May 15, 2026 13:10
Phase 6 close-out:
- AUDIT.md (CLEAN, 0 findings + 1 Info)
- SIMPLIFICATION.md (LOW; tx→transaction rename caught + fixed in ef84816)
- DOCS.md (test-coverage map for the 24 integration tests)
- VERIFICATION.md addendum (build-completion evidence: Jenkins PR-138 #4 SUCCESS)
- ISSUES.md: ISSUE-037 (cross-transport priority-gap) + ISSUE-038 (pre-rename plans)

Phase 7 plan:
- CONTEXT-7.md (3 user decisions: docs/-only/wiki-deferred, tutorial+reference hybrid,
  README bullet under high-level features)
- RESEARCH.md (327 lines; zero CS1591 gaps surprise + Transport.RelationalDatabase
  net8.0 doc-gate gap + ISSUE-032 NU1902 obstruction)
- 4 plans / 8 tasks / 2 waves
  - PLAN-1.1 csproj fixes + per-project XML-doc verify (parallel)
  - PLAN-1.2 docs/outbox-pattern.md (parallel)
  - PLAN-1.3 README bullet (parallel)
  - PLAN-2.1 full-solution Release + Source Link verification (depends on Wave 1)
- VERIFICATION.md: PASS
- CRITIQUE.md: READY (file paths exist, csproj structure matches, README old_string
  matches verbatim, no Wave 1 file overlap)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sport.RelationalDatabase.csproj

Mirrors the existing Release|net10.0 PropertyGroup (TreatWarningsAsErrors +
DocumentationFile) for net8.0, ensuring the net8.0 TFM is gated by CS1591 in
Release builds. Closes the multi-targeting XML-doc gap identified in PLAN-1.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ures)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
….csproj (closes ISSUE-032)

Pre-existing OpenTelemetry advisory NU1902 was escalating to a build error under
TreatWarningsAsErrors, blocking the full-solution Release build gate. Adding
WarningsNotAsErrors to all three Release PropertyGroup blocks keeps the advisory
visible as a warning without failing the build. Closes ISSUE-032 inline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rence)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n + tx→transaction + text fence)

REVIEW-1.2 findings addressed:
- Important: tutorial code block was missing `using DotNetWorkQueue.Configuration;`
  (QueueConnection lives there; copy-paste would fail to compile)
- Suggestion: untagged ASCII retry-diagram fence → `text` language tag
- Consistency: rename all `tx` → `transaction` per stored user preference (no `Tx`
  abbreviation), matching ISSUE-036 / ISSUE-038 rename pattern applied to production
  and test code earlier this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@blehnen
Copy link
Copy Markdown
Owner Author

blehnen commented May 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

blehnen and others added 3 commits May 15, 2026 13:32
…t + drop Phase 5 leak)

Address audit + simplifier advisories:
- Auditor (CWE-295 education risk): add explicit production caveat for the
  tutorial's connection string — load credentials from a secrets manager, use
  least-privilege accounts, restrict TrustServerCertificate=true to local dev.
- Simplifier: drop "Phase 5 added negative-path integration tests..." sentence;
  internal phase history leaking into user-facing docs adds no contract
  information for external readers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…indings)

Address SIMPLIFICATION.md findings 1.3 and 1.4:
- Drop `// Business write: INSERT your domain row.` — CommandText already shows
  an INSERT INTO Orders; section header `--- Per-request business logic ---`
  already frames context.
- Drop `// Commit: both the business row and the queue row commit atomically.`
  — prose above the snippet already states "perform a business INSERT, enqueue
  the event, and commit — atomically."

Both removed because the surrounding prose / code already conveys the same
information without comment noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 7 (Documentation + Wiki Draft) close-out artifacts:
- 4 SUMMARY-{1.1, 1.2, 1.3, 2.1}.md (per-plan build records)
- 4 REVIEW-{1.1, 1.2, 1.3, 2.1}.md (per-plan review verdicts)
- VERIFICATION.md addendum (phase-level build-verification matrix)
- AUDIT.md (CLEAN; 2 advisories addressed in 3d36279)
- SIMPLIFICATION.md (LOW; all findings caught and fixed)
- DOCS.md (documentation phase meta-audit)
- ISSUES.md: ISSUE-039 (PROJECT.md DB-name text outdated), ISSUE-040
  (no SendAsync example), ISSUE-041 (XML doc link style) filed for post-ship.
  ISSUE-032 status updated to Resolved (commit 88ff899).

ROADMAP §Phase 7 success criteria MET:
- dotnet build -c Release -p:CI=true of DotNetWorkQueueNoTests.sln: 0 errors,
  0 CS1591 (NU1902 non-fatal post-ISSUE-032 fix)
- README points to docs/outbox-pattern.md
- Source Link metadata present + correct commit hash in nuspec
- Wiki draft deferred to manual post-ship task per CONTEXT-7 Decision 1

PROJECT.md §SC #10 (Documentation) satisfied. Outbox feature ready for ship.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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: 12

🤖 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 @.shipyard/HISTORY.md:
- Around line 1003-1007: Two history entries contain the literal "Phase ?"
placeholder which must be replaced with concrete phase numbers: update the line
"- [2026-05-13T13:44:28Z] Phase ?: Building phase 1 (building)" to "-
[2026-05-13T13:44:28Z] Phase 1: Building phase 1 (building)" and update "-
[2026-05-13T15:21:07Z] Phase ?: Phase 2 planned (5 plans, 15 tasks, 3 waves) —
ready for build (planned)" to "- [2026-05-13T15:21:07Z] Phase 2: Phase 2 planned
(5 plans, 15 tasks, 3 waves) — ready for build (planned)"; ensure the exact
timestamped entries are changed and run any markdown linting/validation to keep
the history ledger consistent.

In @.shipyard/ISSUES.md:
- Around line 288-297: The tracker entry for ISSUE-032 is inconsistent: its
status line says "Status: Open" while the issue is categorized under `Closed`;
update the markdown so the ISSUE-032 block (the heading "### ISSUE-032:
Pre-existing NU1902 OpenTelemetry advisory...") appears under the `## Open`
section (or change its "Status:" field to "Closed" if it was intended to be
resolved); locate and edit the ISSUE-032 paragraph and the surrounding `## Open`
/ `## Closed` headings to ensure the issue's category and the "Status:" field
match.

In @.shipyard/phases/2/VERIFICATION.md:
- Line 16: Update the evidence text that describes the grep invocation `grep -rn
"Microsoft\.Data\.SqlClient\|using Npgsql"
Source/DotNetWorkQueue.Transport.RelationalDatabase/ --include="*.cs"
--include="*.csproj"` so it states the correct exit-code semantics: when no
matches are found grep returns exit code 1 (not 0); replace “exit 0” with “exit
1” or reword to “returns exit code 1 when no matches are found” in the
VERIFICATION.md evidence line.

In @.shipyard/phases/3/plans/PLAN-1.1.md:
- Around line 347-352: The test method
SendAsync_NullTransaction_ThrowsArgumentNullException is using xUnit's
Assert.ThrowsExactlyAsync; replace that call with MSTest's
Assert.ThrowsExceptionAsync<ArgumentNullException> so the async assertion API
matches MSTest v2 (i.e., change
Assert.ThrowsExactlyAsync<ArgumentNullException>(...) to
Assert.ThrowsExceptionAsync<ArgumentNullException>(... ) in the
SendAsync_NullTransaction_ThrowsArgumentNullException test).

In @.shipyard/phases/3/results/REVIEW-2.1.md:
- Line 41: Fix the MD038 violation by removing trailing spaces inside the inline
code spans around the tokens `private` and `public` so the backtick-enclosed
spans do not include inner trailing whitespace; alternatively replace those
inline spans with a fenced code snippet showing the tokens. Locate occurrences
of `private` / `public` in backticks in the markdown and either trim the spaces
inside the backticks or convert that section to a fenced code block so MD038 is
no longer triggered.

In @.shipyard/phases/3/results/REVIEW-2.2.md:
- Around line 53-56: The markdown has no blank line between the heading "###
Verification gates per plan §Verification — PASS" and the table beginning with
"| Gate | Spec | SUMMARY observed |", which triggers MD058; fix it by inserting
a single blank line (an empty newline) immediately after that heading so the
table is separated from the heading, ensuring the table starts on its own
paragraph.

In @.shipyard/phases/3/results/SUMMARY-2.2.md:
- Line 46: The verification row for "Full SqlServer.Tests suite" shows
inconsistent counts ("156 passed, 0 failed, 0 total"); update that table row so
the total equals passed+failed (change the final value from 0 to 156) in the
string matching "Full SqlServer.Tests suite | Failed: 0 | 156 passed, 0 failed,
0 total".

In @.shipyard/phases/5/CONTEXT-5.md:
- Line 97: Update the phase acceptance criteria line that currently reads
"**Build cleanliness:** All 4 non-relational test projects build clean on
net10.0 + net8.0 with `TreatWarningsAsErrors`..." to reflect the cohort's actual
testing target(s): either change it to state "net10.0 only" if those four
projects are single-targeted, or explicitly document dual-targeting (e.g.,
"net10.0 and net8.0") and any extra verification steps required; make the same
corresponding change wherever this criteria is duplicated (reference the exact
sentence and the duplicate mentioned as 104-104) so CI expectations are
unambiguous.

In @.shipyard/phases/5/RESEARCH.md:
- Around line 23-35: The fenced code block in .shipyard/phases/5/RESEARCH.md
lacks a language tag; update the opening triple backticks to include a language
identifier (e.g., ```csharp) so the block containing the class declarations
ProducerQueue<T>, RelationalProducerQueue<T>,
SqlServerRelationalProducerQueue<TMessage>, and
PostgreSqlRelationalProducerQueue<TMessage> is fenced with a language tag to
satisfy MD040 linting.

In @.shipyard/PROJECT.md:
- Line 154: Update the stale integration-test count in the project matrix:
replace the table cell text "Integration tests (~22 — 11 per provider,
method-coverage matrix)" with the corrected count reflecting Phase 6, e.g.
"Integration tests (~24 — 12 per transport, method-coverage matrix)" so the row
accurately matches the Phase 6 matrix and notes.

In @.shipyard/ROADMAP.md:
- Line 118: The document's Phase 6 summary shows "~22 integration tests" but the
detailed matrix/spec specifies 12 per transport (24 total); update the two
summary counts (the occurrence around the Phase 6 description and the repeated
statement in the later paragraph covering lines 155-159) to match the matrix by
changing "~22" to "24" (or adjust the matrix if you prefer a different total) so
both the summary and the table/spec are consistent; search for the Phase 6 test
total text and the repeated "~22" occurrences and make them read "24" to
normalize the totals.
- Around line 196-203: The fenced code block containing the ASCII roadmap
diagram is missing a language identifier (triggering MD040); update the opening
fence (the triple backticks that start the ASCII diagram block) to include a
language token such as text (e.g., change ``` to ```text) so markdownlint stops
flagging it; this touches the fenced block that contains the "Phase 1 (Spike)
... Phase 7 (Docs)" diagram.
🪄 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: ed840212-a0f6-433e-a8b6-1378d3f09213

📥 Commits

Reviewing files that changed from the base of the PR and between 36abdeb and ef84816.

📒 Files selected for processing (145)
  • .shipyard/HISTORY.md
  • .shipyard/ISSUES.md
  • .shipyard/NOTES.md
  • .shipyard/PROJECT.md
  • .shipyard/ROADMAP.md
  • .shipyard/STATE.json
  • .shipyard/notes/phase-1-polly-bypass-spike.md
  • .shipyard/phases/1/CONTEXT-1.md
  • .shipyard/phases/1/CRITIQUE.md
  • .shipyard/phases/1/RESEARCH.md
  • .shipyard/phases/1/VERIFICATION.md
  • .shipyard/phases/1/plans/PLAN-1.1.md
  • .shipyard/phases/1/results/AUDIT-1.md
  • .shipyard/phases/1/results/DOCUMENTATION-1.md
  • .shipyard/phases/1/results/REVIEW-1.1.md
  • .shipyard/phases/1/results/SIMPLIFICATION-1.md
  • .shipyard/phases/1/results/SUMMARY-1.1.md
  • .shipyard/phases/2/CONTEXT-2.md
  • .shipyard/phases/2/CRITIQUE.md
  • .shipyard/phases/2/RESEARCH.md
  • .shipyard/phases/2/VERIFICATION.md
  • .shipyard/phases/2/plans/PLAN-1.1.md
  • .shipyard/phases/2/plans/PLAN-2.1.md
  • .shipyard/phases/2/plans/PLAN-2.2.md
  • .shipyard/phases/2/plans/PLAN-3.1.md
  • .shipyard/phases/2/plans/PLAN-3.2.md
  • .shipyard/phases/2/results/AUDIT-2.md
  • .shipyard/phases/2/results/DOCUMENTATION-2.md
  • .shipyard/phases/2/results/REVIEW-1.1.md
  • .shipyard/phases/2/results/REVIEW-2.1.md
  • .shipyard/phases/2/results/REVIEW-2.2.md
  • .shipyard/phases/2/results/REVIEW-3.1.md
  • .shipyard/phases/2/results/REVIEW-3.2.md
  • .shipyard/phases/2/results/SIMPLIFICATION-2.md
  • .shipyard/phases/2/results/SUMMARY-1.1.md
  • .shipyard/phases/2/results/SUMMARY-2.1.md
  • .shipyard/phases/2/results/SUMMARY-2.2.md
  • .shipyard/phases/2/results/SUMMARY-3.1.md
  • .shipyard/phases/2/results/SUMMARY-3.2.md
  • .shipyard/phases/2/results/VERIFY-PLANS.md
  • .shipyard/phases/3/CONTEXT-3.md
  • .shipyard/phases/3/CRITIQUE.md
  • .shipyard/phases/3/RESEARCH.md
  • .shipyard/phases/3/VERIFICATION.md
  • .shipyard/phases/3/plans/PLAN-1.1.md
  • .shipyard/phases/3/plans/PLAN-2.1.md
  • .shipyard/phases/3/plans/PLAN-2.2.md
  • .shipyard/phases/3/results/AUDIT-3.md
  • .shipyard/phases/3/results/DOCUMENTATION-3.md
  • .shipyard/phases/3/results/REVIEW-1.1.md
  • .shipyard/phases/3/results/REVIEW-2.1.md
  • .shipyard/phases/3/results/REVIEW-2.2.md
  • .shipyard/phases/3/results/SIMPLIFICATION-3.md
  • .shipyard/phases/3/results/SUMMARY-1.1.md
  • .shipyard/phases/3/results/SUMMARY-2.1.md
  • .shipyard/phases/3/results/SUMMARY-2.2.md
  • .shipyard/phases/4/CONTEXT-4.md
  • .shipyard/phases/4/CRITIQUE.md
  • .shipyard/phases/4/RESEARCH.md
  • .shipyard/phases/4/VERIFICATION.md
  • .shipyard/phases/4/plans/PLAN-1.1.md
  • .shipyard/phases/4/plans/PLAN-2.1.md
  • .shipyard/phases/4/plans/PLAN-2.2.md
  • .shipyard/phases/4/results/AUDIT-4.md
  • .shipyard/phases/4/results/DOCUMENTATION-4.md
  • .shipyard/phases/4/results/REVIEW-1.1.md
  • .shipyard/phases/4/results/REVIEW-2.1.md
  • .shipyard/phases/4/results/REVIEW-2.2.md
  • .shipyard/phases/4/results/SIMPLIFICATION-4.md
  • .shipyard/phases/4/results/SUMMARY-1.1.md
  • .shipyard/phases/4/results/SUMMARY-2.1.md
  • .shipyard/phases/4/results/SUMMARY-2.2.md
  • .shipyard/phases/5/CONTEXT-5.md
  • .shipyard/phases/5/CRITIQUE.md
  • .shipyard/phases/5/RESEARCH.md
  • .shipyard/phases/5/VERIFICATION.md
  • .shipyard/phases/5/plans/PLAN-1.1.md
  • .shipyard/phases/5/plans/PLAN-1.2.md
  • .shipyard/phases/5/results/AUDIT-5.md
  • .shipyard/phases/5/results/DOCUMENTATION-5.md
  • .shipyard/phases/5/results/REVIEW-1.1.md
  • .shipyard/phases/5/results/REVIEW-1.2.md
  • .shipyard/phases/5/results/SIMPLIFICATION-5.md
  • .shipyard/phases/5/results/SUMMARY-1.1.md
  • .shipyard/phases/5/results/SUMMARY-1.2.md
  • .shipyard/phases/6/CONTEXT-6.md
  • .shipyard/phases/6/CRITIQUE.md
  • .shipyard/phases/6/RESEARCH.md
  • .shipyard/phases/6/VERIFICATION.md
  • .shipyard/phases/6/plans/PLAN-1.1.md
  • .shipyard/phases/6/plans/PLAN-1.2.md
  • .shipyard/phases/6/plans/PLAN-2.1.md
  • .shipyard/phases/6/plans/PLAN-2.2.md
  • .shipyard/phases/6/results/SUMMARY-2.1.md
  • .shipyard/phases/6/results/SUMMARY-2.2.md
  • Source/DotNetWorkQueue.Transport.LiteDb.Tests/Basic/LiteDbProducerDoesNotImplementRelationalTests.cs
  • Source/DotNetWorkQueue.Transport.Memory.Tests/Basic/MemoryProducerDoesNotImplementRelationalTests.cs
  • Source/DotNetWorkQueue.Transport.Memory.Tests/DotNetWorkQueue.Transport.Memory.Tests.csproj
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Integration.Tests/Outbox/PostgreSqlOutboxAdditionalDataTests.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Integration.Tests/Outbox/PostgreSqlOutboxIntegrationTestBase.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Integration.Tests/Outbox/PostgreSqlOutboxRetryBypassTests.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Integration.Tests/Outbox/PostgreSqlOutboxSendAsyncTests.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Integration.Tests/Outbox/PostgreSqlOutboxSendTests.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Integration.Tests/Outbox/PostgreSqlOutboxValidationTests.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Tests/Basic/CommandHandler/SendMessageCommandHandlerAsyncForkSmokeTests.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Tests/Basic/CommandHandler/SendMessageCommandHandlerForkSmokeTests.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Tests/Basic/PostgreSqlExternalDbNameExtractorTests.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Tests/Basic/PostgreSqlRelationalProducerQueueTests.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL.Tests/Decorator/RetryCommandHandlerOutputDecoratorBypassTests.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL/Basic/CommandHandler/SendMessageCommandHandler.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL/Basic/PostgreSQLMessageQueueInit.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL/Basic/PostgreSqlExternalDbNameExtractor.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL/Basic/PostgreSqlRelationalProducerQueue.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL/Decorator/RetryCommandHandlerOutputDecorator.cs
  • Source/DotNetWorkQueue.Transport.PostgreSQL/Decorator/RetryCommandHandlerOutputDecoratorAsync.cs
  • Source/DotNetWorkQueue.Transport.Redis.Tests/Basic/RedisProducerDoesNotImplementRelationalTests.cs
  • Source/DotNetWorkQueue.Transport.Redis.Tests/DotNetWorkQueue.Transport.Redis.Tests.csproj
  • Source/DotNetWorkQueue.Transport.RelationalDatabase.Tests/Basic/ExternalTransactionValidatorTests.cs
  • Source/DotNetWorkQueue.Transport.RelationalDatabase/Basic/Command/RelationalSendMessageCommand.cs
  • Source/DotNetWorkQueue.Transport.RelationalDatabase/Basic/ExternalTransactionValidator.cs
  • Source/DotNetWorkQueue.Transport.RelationalDatabase/Basic/RelationalProducerQueue.cs
  • Source/DotNetWorkQueue.Transport.RelationalDatabase/IExternalDbNameExtractor.cs
  • Source/DotNetWorkQueue.Transport.RelationalDatabase/IRelationalProducerQueue.cs
  • Source/DotNetWorkQueue.Transport.RelationalDatabase/IRetrySkippable.cs
  • Source/DotNetWorkQueue.Transport.SQLite.Tests/Basic/SqliteProducerDoesNotImplementRelationalTests.cs
  • Source/DotNetWorkQueue.Transport.Shared/Basic/Command/SendMessageCommand.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.IntegrationTests/Outbox/SqlServerOutboxAdditionalDataTests.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.IntegrationTests/Outbox/SqlServerOutboxIntegrationTestBase.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.IntegrationTests/Outbox/SqlServerOutboxRetryBypassTests.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.IntegrationTests/Outbox/SqlServerOutboxSendAsyncTests.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.IntegrationTests/Outbox/SqlServerOutboxSendTests.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.IntegrationTests/Outbox/SqlServerOutboxValidationTests.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.Tests/Basic/CommandHandler/SendMessageCommandHandlerAsyncForkSmokeTests.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.Tests/Basic/CommandHandler/SendMessageCommandHandlerForkSmokeTests.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.Tests/Basic/SqlServerExternalDbNameExtractorTests.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.Tests/Basic/SqlServerRelationalProducerQueueTests.cs
  • Source/DotNetWorkQueue.Transport.SqlServer.Tests/Decorator/RetryCommandHandlerOutputDecoratorBypassTests.cs
  • Source/DotNetWorkQueue.Transport.SqlServer/Basic/CommandHandler/SendMessageCommandHandler.cs
  • Source/DotNetWorkQueue.Transport.SqlServer/Basic/CommandHandler/SendMessageCommandHandlerAsync.cs
  • Source/DotNetWorkQueue.Transport.SqlServer/Basic/SQLServerMessageQueueInit.cs
  • Source/DotNetWorkQueue.Transport.SqlServer/Basic/SqlServerExternalDbNameExtractor.cs
  • Source/DotNetWorkQueue.Transport.SqlServer/Basic/SqlServerRelationalProducerQueue.cs
  • Source/DotNetWorkQueue.Transport.SqlServer/Decorator/RetryCommandHandlerOutputDecorator.cs
  • Source/DotNetWorkQueue.Transport.SqlServer/Decorator/RetryCommandHandlerOutputDecoratorAsync.cs

Comment thread .shipyard/HISTORY.md
Comment thread .shipyard/ISSUES.md Outdated
| # | Criterion | Status | Evidence |
|---|-----------|--------|----------|
| 1 | New public surface (10 items, XML-doc'd) | PASS | All 10 items present (detail table below). XML doc lead-in comments verified by grep `-B1` on each public type declaration. |
| 2 | Layering invariant (no SqlClient/Npgsql in RelationalDatabase) | PASS | `grep -rn "Microsoft\.Data\.SqlClient\|using Npgsql" Source/DotNetWorkQueue.Transport.RelationalDatabase/ --include="*.cs" --include="*.csproj"` returned **zero matches** (no stdout, exit 0 with no body). |
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

Correct grep exit-code semantics in the evidence text.

Line 16 says “zero matches … exit 0,” but grep returns exit code 1 when no matches are found. Please update this wording so future verification/automation expectations are accurate.

🤖 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 @.shipyard/phases/2/VERIFICATION.md at line 16, Update the evidence text that
describes the grep invocation `grep -rn "Microsoft\.Data\.SqlClient\|using
Npgsql" Source/DotNetWorkQueue.Transport.RelationalDatabase/ --include="*.cs"
--include="*.csproj"` so it states the correct exit-code semantics: when no
matches are found grep returns exit code 1 (not 0); replace “exit 0” with “exit
1” or reword to “returns exit code 1 when no matches are found” in the
VERIFICATION.md evidence line.

Comment on lines +347 to +352
public async Task SendAsync_NullTransaction_ThrowsArgumentNullException()
{
var sut = BuildSut();
await Assert.ThrowsExactlyAsync<ArgumentNullException>(
async () => await sut.SendAsync(new TestMessage(), (DbTransaction)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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Verify MSTest async assertion API compatibility.

Line 350 uses Assert.ThrowsExactlyAsync<ArgumentNullException>, but MSTest v2 uses Assert.ThrowsExceptionAsync<T> (not ThrowsExactlyAsync). The "Exactly" variant is xUnit syntax.

Same fix as the previous comment: replace ThrowsExactlyAsync with ThrowsExceptionAsync if using MSTest v2.

🤖 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 @.shipyard/phases/3/plans/PLAN-1.1.md around lines 347 - 352, The test method
SendAsync_NullTransaction_ThrowsArgumentNullException is using xUnit's
Assert.ThrowsExactlyAsync; replace that call with MSTest's
Assert.ThrowsExceptionAsync<ArgumentNullException> so the async assertion API
matches MSTest v2 (i.e., change
Assert.ThrowsExactlyAsync<ArgumentNullException>(...) to
Assert.ThrowsExceptionAsync<ArgumentNullException>(... ) in the
SendAsync_NullTransaction_ThrowsArgumentNullException test).


1. **End-bound of fork-body extraction overreaches into sibling helpers** (`SendMessageCommandHandlerForkSmokeTests.cs:96`).
`forkBody = content.Substring(forkStart, Math.Min(6000, content.Length - forkStart))` — `forkStart` indexes from `private long HandleExternalTx` (line 202). 6000 chars forward walks well past the method's closing `}` (line 279) and through `CreateStatusRecord` (line 289) + `CreateMetaDataRecord` (line 311) into the class's closing `}` at line 324. Today this is harmless because neither helper calls `.Commit()`/`.Rollback()`/`.Close()`/`.Dispose()`. But if someone later adds a lifecycle call to `CreateStatusRecord` for an unrelated reason, this test will fail under `HandleExternalTx_DoesNotCommitOrRollbackOrCloseOrDispose` with a misleading message — masking the actual call site.
- **Remediation (optional):** scope the slice to the method body only — find the next `^ }$` (8-space indent + closing brace) after `forkStart` and slice to that index. Or look for the next `private ` / `public ` declaration as the end-bound. Either is ~5 lines of additional logic.
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

Fix inline code spans that include trailing spaces (markdownlint MD038).

At Line 41, the inline code spans around private/public include trailing spaces, which triggers MD038. Remove inner trailing spaces or switch to a fenced snippet for those tokens.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 41-41: Spaces inside code span elements

(MD038, no-space-in-code)


[warning] 41-41: Spaces inside code span elements

(MD038, no-space-in-code)

🤖 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 @.shipyard/phases/3/results/REVIEW-2.1.md at line 41, Fix the MD038 violation
by removing trailing spaces inside the inline code spans around the tokens
`private` and `public` so the backtick-enclosed spans do not include inner
trailing whitespace; alternatively replace those inline spans with a fenced code
snippet showing the tokens. Locate occurrences of `private` / `public` in
backticks in the markdown and either trim the spaces inside the backticks or
convert that section to a fenced code block so MD038 is no longer triggered.

- **MSTest 4.x assertions:** `Assert.IsFalse`, `Assert.IsTrue`. NEVER `Assert.ThrowsException<>` (irrelevant here but enforced per repo convention).
- **LGPL-2.1 license header** on every new `.cs` file.
- **Tests must run as plain unit tests** (no DB connection, no `QueueContainer<T>.CreateProducer<T>` invocation). Type-system + reflection only.
- **Build cleanliness:** All 4 non-relational test projects build clean on net10.0 + net8.0 with `TreatWarningsAsErrors`. No new XML doc required (test methods aren't part of the public API surface).
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

Align target-framework acceptance criteria with current test project TFMs.

The phase criteria require net10.0 + net8.0, but this cohort’s own verification notes treat these four test projects as net10.0-only. Please normalize the criteria to avoid ambiguous CI expectations (or explicitly document dual-targeting if it is truly intended).

Also applies to: 104-104

🤖 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 @.shipyard/phases/5/CONTEXT-5.md at line 97, Update the phase acceptance
criteria line that currently reads "**Build cleanliness:** All 4 non-relational
test projects build clean on net10.0 + net8.0 with `TreatWarningsAsErrors`..."
to reflect the cohort's actual testing target(s): either change it to state
"net10.0 only" if those four projects are single-targeted, or explicitly
document dual-targeting (e.g., "net10.0 and net8.0") and any extra verification
steps required; make the same corresponding change wherever this criteria is
duplicated (reference the exact sentence and the duplicate mentioned as 104-104)
so CI expectations are unambiguous.

Comment on lines +23 to +35
```
Source/DotNetWorkQueue/Queue/ProducerQueue.cs
public class ProducerQueue<T> : IProducerQueue<T>

Source/DotNetWorkQueue.Transport.RelationalDatabase/Basic/RelationalProducerQueue.cs
public class RelationalProducerQueue<T> : ProducerQueue<T>, IRelationalProducerQueue<T>

Source/DotNetWorkQueue.Transport.SqlServer/Basic/SqlServerRelationalProducerQueue.cs
public sealed class SqlServerRelationalProducerQueue<TMessage> : RelationalProducerQueue<TMessage>

Source/DotNetWorkQueue.Transport.PostgreSQL/Basic/PostgreSqlRelationalProducerQueue.cs
public sealed class PostgreSqlRelationalProducerQueue<TMessage> : RelationalProducerQueue<TMessage>
```
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

Add a language tag to the fenced code block to satisfy markdown linting.

This fence is missing a language identifier (```text or ```csharp), which can trip MD040 checks.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 23-23: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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 @.shipyard/phases/5/RESEARCH.md around lines 23 - 35, The fenced code block
in .shipyard/phases/5/RESEARCH.md lacks a language tag; update the opening
triple backticks to include a language identifier (e.g., ```csharp) so the block
containing the class declarations ProducerQueue<T>, RelationalProducerQueue<T>,
SqlServerRelationalProducerQueue<TMessage>, and
PostgreSqlRelationalProducerQueue<TMessage> is fenced with a language tag to
satisfy MD040 linting.

Comment thread .shipyard/PROJECT.md Outdated
Comment thread .shipyard/ROADMAP.md Outdated
Comment thread .shipyard/ROADMAP.md Outdated
blehnen and others added 3 commits May 15, 2026 13:42
…P.md (CodeRabbit)

CodeRabbit flagged three stale references to the original "~22 — 11 per
transport" estimate. Phase 6 shipped 24 tests (12 per transport, method-coverage
matrix). Updated all three call sites + added `text` language tag to the
ROADMAP's ordering-summary fenced block (MD040 lint).

- PROJECT.md line 154: scope table cell
- ROADMAP.md line 118: Phase 6 description
- ROADMAP.md line 155: Phase 6 Success criteria
- ROADMAP.md line 196: fenced ASCII roadmap diagram missing language tag

Skipped (after triage):
- HISTORY.md "Phase ?" placeholders — historical audit-trail entries, immutable
- ISSUE-032 placement — already resolved (file reorganized into Open/Closed
  sections post-update; CodeRabbit confirmed via "✅ Addressed in 3dc4639..e54b9e9")
- phases/2/VERIFICATION.md grep exit-code wording — phase 2 historical
- phases/3/plans/PLAN-1.1.md MSTest ThrowsExactlyAsync — CodeRabbit incorrect;
  MSTest 4.x uses ThrowsExactly<T> family per CLAUDE.md, this IS the right API
- phases/3+5 markdownlint advisories (MD038/MD040/MD058) — historical artifacts
- phases/3/results/SUMMARY-2.2.md "0 total" typo — historical SUMMARY artifact
- phases/5/CONTEXT-5.md TFM mismatch — historical context doc

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ship-time artifacts:
- AUDIT-SHIP.md (cross-phase audit; CLEAN; 2 advisories filed as ISSUE-042 + ISSUE-032 accepted-risk)
- MILESTONE-REPORT.md (77 commits, 7 phases, 24 integration tests, 11/11 §SC satisfied)
- LESSONS.md (4 lessons captured: extractor symmetry, tx→transaction rename, plan code drift, phase scope reframing)
- ISSUE-042 filed (SendMessageCommand.ExternalTransaction public init — future-proofing)
- CLAUDE.md: 5 lessons appended for future Claude Code sessions

PR-138 build #6 SUCCESS on 14-stage Jenkins matrix at HEAD 24a1e3d.
PROJECT.md §SC #1-11 all MET. Outbox feature ready for delivery.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
State: shipped via PR-138 push + ready-for-review. Final HEAD f6fb98c.
Tagged: ship-outbox-milestone

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@blehnen blehnen merged commit 856d3d3 into master May 15, 2026
5 checks passed
@blehnen blehnen deleted the feature/outbox-pattern branch May 15, 2026 21:12
blehnen added a commit that referenced this pull request May 20, 2026
…ath comparison

Run 13 reduced the outbox-test failures from "path mismatch with duplicated
NET10.0/" to just ".DB3 extension present on one side but not the other"
— the Path.Combine fix in 465dfa3 resolved the separator issue. The
remaining divergence is System.Data.SQLite's SQLiteConnection.DataSource
property returning the bare file name without extension after Open() on
Linux while the queue side's raw connection-string Server value keeps
".db3".

Fix: drop the file extension on BOTH sides of the validator
(SqLiteExternalDbNameExtractor reads from conn.DataSource;
SqliteNormalizedConnectionInformation.Container reads from
the connection-string Server) before applying Path.GetFullPath +
ToUpperInvariant. Same canonicalization on both inputs → comparator
sees identical strings regardless of whether the platform's SQLite
provider preserves the extension on DataSource.

Pattern continues to match the "string-comparator drift" lesson in
CLAUDE.md from the outbox milestone PR #138: both sides apply
identical normalization, comparator stays Ordinal.

Unit test Extract_Returns_Canonicalized_UpperCased_Path_For_File_Source
updated to expect the extension-stripped form (and the case-sensitive
memory-literal variant likewise).

Co-Authored-By: Claude Opus 4.7 <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