Skip to content

release: split JSON integrations into SmartPipe.Extensions.Json for 2.1.2 - #27

Merged
MrFr3di merged 22 commits into
mainfrom
upd
Jul 14, 2026
Merged

release: split JSON integrations into SmartPipe.Extensions.Json for 2.1.2#27
MrFr3di merged 22 commits into
mainfrom
upd

Conversation

@MrFr3di

@MrFr3di MrFr3di commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added SmartPipe.Extensions.Json for JSON file sources, sinks, transforms, and dead-letter processing.
    • Added support for array, NDJSON, batch JSON Lines, append mode, streaming limits, recovery policies, and source-generated JSON metadata.
    • Improved dead-letter retries, rollback handling, lifecycle coordination, and partial-record reporting.
    • Added configurable circuit-breaker options and time-provider support.
  • Compatibility
    • Existing JSON namespaces remain compatible through forwarding in SmartPipe.Extensions.
  • Documentation
    • Added migration, installation, AOT, API, and package-ownership guidance.
  • Chores
    • Updated release version to 2.1.2.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

SmartPipe 2.1.2 adds SmartPipe.Extensions.Json, moves JSON integrations with compatibility forwarding, introduces reusable release/package validation, updates JSON streaming and append behavior, and refines circuit-breaker, observer, serialization, and pipeline completion semantics.

Changes

JSON package and compatibility

Layer / File(s) Summary
Dedicated JSON implementation
src/SmartPipe.Extensions.Json/*
Adds JSON sources, sinks, transforms, dead-letter handling, format/options contracts, streaming validation, append rollback, and AOT/source-generated metadata paths.
Extensions forwarding and package wiring
src/SmartPipe.Extensions/*, SmartPipe.Core.slnx, Directory.Build.props
Moves JSON implementations into the dedicated assembly, adds type forwarders and project/package references, and updates version metadata.
Documentation and migration
README.md, docs/*, CHANGELOG.md
Documents package ownership, JSON formats, limits, AOT guidance, append behavior, compatibility, and migration to SmartPipe.Extensions.Json.
JSON validation tests
tests/SmartPipe.Extensions.Json.Tests/*, tests/SmartPipe.Extensions.Tests/*
Covers JSON framing, limits, recovery, append transactions, lifecycle behavior, source-generated metadata, and package ownership.

Core runtime and reliability

Layer / File(s) Summary
Circuit breaker and pipeline timing
src/SmartPipe.Core/CircuitBreaker.cs, src/SmartPipe.Core/Runtime/Execution/PipelineTime.cs
Adds CircuitBreakerOptions, explicit TimeProvider construction, synchronized sliding-window maintenance, and provider-backed timeout creation.
Observer and pipeline completion
src/SmartPipe.Core/PipelineObserverDispatcher.cs, src/SmartPipe.Core/TypedPipelineRuntime.cs
Uses pipeline time for observer timeouts, defers dropped-event diagnostics, and separates public completion from internal execution completion.
Dead-letter serialization
src/SmartPipe.Core/DeadLetterSerialization.cs
Freezes serializer options and supports array or single-value asynchronous deserialization from seekable and non-seekable streams.

Release automation

Layer / File(s) Summary
Reusable validation and publishing
.github/workflows/*, eng/validate-json-package-split.ps1, eng/validate-release-version.sh
Splits CI and publishing into reusable validation, version, Windows JSON, and publish jobs; validates package versions, manifests, local consumer resolution, and NuGet publication.
Workflow contract enforcement
eng/tests/workflow_contract_tests.py, eng/tests/*
Adds structural workflow checks, mutation tests, release-version tests, and package-split validator coverage.
Action pinning
.github/workflows/codeql.yml, .github/workflows/dependency-review.yml
Pins third-party workflow actions to commit SHAs and disables checkout credential persistence.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: dependabot[bot]

Poem

I hopped through JSON, neat and bright,
With packages split and workflows light.
Streams roll on, retries behave,
Pipelines finish—clean and brave.
A rabbit stamps: “Release approved!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.18% 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 accurately summarizes the main change: splitting JSON integrations into SmartPipe.Extensions.Json for release 2.1.2.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch upd

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/getting-started.md (1)

93-104: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Bullet list doesn't indicate which items belong to which package.

The new lead-in text explicitly splits ownership between SmartPipe.Extensions.Json and SmartPipe.Extensions, but the following selector/transform/sink bullets mix JSON and non-JSON components (JsonFileSource<T> next to CsvFileSource<T>, etc.) without labeling. This undercuts the point of the split for readers trying to figure out which package to reference.

🤖 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 `@docs/getting-started.md` around lines 93 - 104, The component lists in the
documentation do not identify package ownership despite the split lead-in.
Update the selector, transform, and sink bullets to clearly label or group each
item under SmartPipe.Extensions.Json versus SmartPipe.Extensions, preserving the
existing component names and classifications.
🧹 Nitpick comments (15)
src/SmartPipe.Extensions.Json/JsonDocumentLimitStream.cs (1)

58-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Flush() throws instead of no-op.

Overriding Flush() to throw on a read-only stream is a bit surprising (base Stream.Flush() is a no-op by default); if any consumer defensively calls Flush() on a read stream, this would blow up unnecessarily. Low risk given current call sites, but worth a defensive no-op instead.

🤖 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 `@src/SmartPipe.Extensions.Json/JsonDocumentLimitStream.cs` at line 58, Update
the Flush method in JsonDocumentLimitStream to be a no-op instead of throwing
NotSupportedException, while leaving the stream’s other read-only behavior
unchanged.
src/SmartPipe.Extensions.Json/Sinks/JsonFileSink.cs (2)

30-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make _leaveOpen readonly.

It's assigned only once, in the internal constructor.

🔧 Suggested fix
-    private bool _leaveOpen;
+    private readonly bool _leaveOpen;

Also applies to: 116-126

🤖 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 `@src/SmartPipe.Extensions.Json/Sinks/JsonFileSink.cs` at line 30, Update the
_leaveOpen field in JsonFileSink to be readonly, preserving its single
assignment in the internal constructor and any existing constructor behavior.

Source: Linters/SAST tools


240-252: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Nested if can be merged with the enclosing one.

Minor readability nit flagged by SonarCloud; doesn't affect behavior (traced the comma-insertion logic across batches and it is correct as written).

🤖 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 `@src/SmartPipe.Extensions.Json/Sinks/JsonFileSink.cs` around lines 240 - 252,
In the batch-writing loop, merge the nested _options.Format ==
JsonFileFormat.Array condition with the enclosing conditional so comma insertion
remains guarded by both the array format and (_arrayHasItems || output.Length >
1). Preserve the existing serialization and NDJSON newline behavior.

Source: Linters/SAST tools

src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs (3)

67-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Repeated "document" literal flagged by static analysis.

SonarCloud suggests extracting the "document" scope literal (used 4 times) into a constant.

Also applies to: 120-127

🤖 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 `@src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs` around lines 67 -
79, Extract the repeated "document" scope value into a class-level constant in
JsonFileSource, then replace all four usages in _deserializeItems and
_deserializeBatches, including the corresponding code around
_deserializeItemRecord and _deserializeBatchRecord, with that constant.

Source: Linters/SAST tools


323-348: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated ReadFirstJsonByteAsync between this file and DeadLetterSource.cs.

Same helper is duplicated in DeadLetterSource.cs (lines 270-295) with a small drift already (extra stream.Position = 0 step here that the other copy omits). Extracting to a shared internal helper would prevent further divergence.

🤖 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 `@src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs` around lines 323 -
348, Extract the duplicated ReadFirstJsonByteAsync logic from JsonFileSource and
DeadLetterSource into a shared internal helper, then update both callers to use
it. Preserve the existing BOM detection, whitespace skipping, cancellation
behavior, and stream-position semantics consistently, including the initial
reset before applying the BOM offset.

245-289: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Cognitive complexity flagged by static analysis.

ReadFramedRecordsAsync exceeds the configured complexity threshold (16 vs. 15). Worth a look when convenient, not blocking.

🤖 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 `@src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs` around lines 245 -
289, Reduce the cognitive complexity of ReadFramedRecordsAsync below the
configured threshold by extracting the oversized-record handling and JSON
validation/deserialization error handling into focused helper methods. Preserve
the existing record index tracking, HandleInvalidRecord behavior, null handling,
and yielded-record behavior.

Source: Linters/SAST tools

src/SmartPipe.Extensions.Json/Selectors/DeadLetterSource.cs (2)

111-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Cognitive complexity flagged by static analysis.

SonarCloud flags ReadEnvelopesAsync for exceeding the cognitive-complexity threshold (19 vs. 15 allowed) and recommends splitting parameter validation from the iterator body. Reasonable given it now branches across streaming/legacy and array/framed paths.

🤖 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 `@src/SmartPipe.Extensions.Json/Selectors/DeadLetterSource.cs` around lines 111
- 176, Reduce the cognitive complexity of ReadEnvelopesAsync by extracting
serializer/format validation and the separate streaming or legacy iteration
paths into focused helper methods. Keep ReadEnvelopesAsync responsible for
dispatching between those paths, and preserve the existing cancellation,
empty-file, array/framed, and null OriginalPayload behaviors.

Source: Linters/SAST tools


270-295: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated ReadFirstJsonByteAsync between this file and JsonFileSource.cs.

This ~25-line helper is duplicated nearly verbatim in JsonFileSource.cs (lines 323-348), with a subtle difference (this version skips the extra stream.Position = 0 reset before seeking to offset, which is harmless but shows the copies have already started drifting). Consider extracting to a shared internal static helper in the project to avoid future divergence.

🤖 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 `@src/SmartPipe.Extensions.Json/Selectors/DeadLetterSource.cs` around lines 270
- 295, Extract the duplicated ReadFirstJsonByteAsync logic from DeadLetterSource
and JsonFileSource into a shared internal static helper, then update both
callers to use it. Preserve the existing BOM handling, whitespace skipping,
stream positioning, cancellation, and nullable-byte behavior while removing the
duplicate implementations.
src/SmartPipe.Extensions.Json/JsonRecordValidator.cs (1)

9-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a comment to the intentionally empty read loop to satisfy the SonarCloud "empty block" finding.

The empty while (reader.Read()) { } loop is intentional — it fully exhausts the reader to surface any structural/trailing-content errors — but SonarCloud flags it as an empty block to remove or fill.

♻️ Proposed fix
-            while (reader.Read()) { }
+            while (reader.Read())
+            {
+                // Intentionally empty: exhausting the reader validates the entire record,
+                // including detecting trailing content past the first top-level value.
+            }
🤖 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 `@src/SmartPipe.Extensions.Json/JsonRecordValidator.cs` around lines 9 - 25,
Add an explanatory comment to the intentionally empty body of the while loop in
JsonRecordValidator, clarifying that exhausting Utf8JsonReader validates
structural and trailing-content errors. Keep the existing reader logic and
exception handling unchanged.

Source: Linters/SAST tools

src/SmartPipe.Extensions.Json/DeadLetterRecordReader.cs (1)

10-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract per-record parsing to reduce cognitive complexity.

SonarCloud flags ReadFramedAsync at complexity 21 (limit 15). Extracting the size-check + validate/deserialize try/catch block (lines 25-48) into a private helper (e.g. TryReadRecordAsync) returning a result/invalid-exception tuple would flatten the method and satisfy the analyzer without changing behavior.

🤖 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 `@src/SmartPipe.Extensions.Json/DeadLetterRecordReader.cs` around lines 10 -
61, Reduce the cognitive complexity of ReadFramedAsync by extracting the
per-record size check and validation/deserialization logic into a private
TryReadRecordAsync helper. Have the helper return the parsed
DeadLetterEnvelope<T> together with any JsonException, preserving existing
record-size checks, serializer validation, and invalid-record behavior while
leaving logging and iteration in ReadFramedAsync.

Source: Linters/SAST tools

src/SmartPipe.Core/CircuitBreaker.cs (1)

706-721: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

GetWindowStatistics locks for a full linear scan on the failure/success hot path.

ConcurrentQueue<T> enumeration is already safe without external locking. Holding _windowGate for the whole foreach in GetWindowStatistics (invoked on every RecordFailure/RecordSuccess via EvaluateSlidingWindow) adds lock contention proportional to window size without a clear correctness need, since nothing else requires stronger consistency than the queue's own snapshot semantics.

♻️ Suggested change
     private WindowStatistics GetWindowStatistics()
     {
-        lock (_windowGate)
-        {
-            var total = 0;
-            var failures = 0;
-            foreach (var (_, isSuccess) in _window)
-            {
-                total++;
-                if (!isSuccess)
-                    failures++;
-            }
-
-            return new WindowStatistics(total, failures);
-        }
+        var total = 0;
+        var failures = 0;
+        foreach (var (_, isSuccess) in _window)
+        {
+            total++;
+            if (!isSuccess)
+                failures++;
+        }
+
+        return new WindowStatistics(total, failures);
     }
🤖 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 `@src/SmartPipe.Core/CircuitBreaker.cs` around lines 706 - 721, Remove the
_windowGate lock from GetWindowStatistics and perform the existing
ConcurrentQueue enumeration directly, preserving the current total and failure
counting logic and WindowStatistics result.
src/SmartPipe.Extensions.Json/JsonOptions.cs (1)

37-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider deduplicating JsonFileSourceOptions and DeadLetterSourceOptions.

These two records have identical properties with identical defaults. If they're expected to evolve independently, keeping them separate is fine. If not, consider having DeadLetterSourceOptions reuse JsonFileSourceOptions or extracting a shared base to reduce maintenance burden.

Also applies to: 62-75

🤖 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 `@src/SmartPipe.Extensions.Json/JsonOptions.cs` around lines 37 - 49,
Deduplicate the identical option definitions represented by
JsonFileSourceOptions and DeadLetterSourceOptions. Prefer reusing
JsonFileSourceOptions or extracting a shared options base, while preserving the
existing properties and defaults and allowing the two source configurations to
retain any necessary source-specific behavior.
.github/workflows/publish-nuget.yml (2)

42-45: 🩺 Stability & Availability | 🔵 Trivial

Manual recoverable-rerun dispatches must target the release tag.

The tag-ref check ($GITHUB_REF != refs/tags/v*) still applies under workflow_dispatch. If an operator triggers the recovery run from the default branch selector without explicitly choosing the release tag, the job fails immediately with the generic message, which doesn't hint at "pick the tag when dispatching." Consider clarifying this in the error message or in run-book docs.

🤖 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 @.github/workflows/publish-nuget.yml around lines 42 - 45, Update the
validation message in the workflow’s tag-ref check to explicitly tell operators
using manual recoverable-rerun dispatches to select the release tag, while
preserving the existing rejection behavior for non-release refs.

76-89: 🔒 Security & Privacy | 🔵 Trivial | 🏗️ Heavy lift

Consider migrating to NuGet Trusted Publishing (OIDC) instead of a long-lived NUGET_API_KEY.

NuGet.org's Trusted Publishing exchanges a short-lived GitHub OIDC token for a temporary (~1 hour) API key, removing the need to store/rotate a long-lived secret. This directly addresses the zizmor hints on lines 86-88.

As per static analysis hints, zizmor recommends: "prefer trusted publishing for authentication (use-trusted-publishing)".

I verified this is a currently valid, GA capability on nuget.org (not deprecated/experimental) as of mid-2026 via web search.

🤖 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 @.github/workflows/publish-nuget.yml around lines 76 - 89, Replace the
long-lived NUGET_API_KEY authentication in the publish step with NuGet Trusted
Publishing via GitHub OIDC. Configure the job permissions and NuGet publishing
commands according to the supported trusted-publishing flow, remove the
secret-based environment variable and references, and preserve the existing
package versioning and RECOVERABLE_RERUN skip-duplicate behavior.

Source: Linters/SAST tools

.github/workflows/reusable-release-validation.yml (1)

25-25: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set persist-credentials: false on checkout.

Later steps generate throwaway consumer projects and run dotnet run/dotnet publish, which restore and execute arbitrary transitive NuGet packages. Leaving the GITHUB_TOKEN persisted in .git/config gives that code an exfiltration target. Disabling credential persistence closes it (the token isn't used for git operations in this job).

🔒 Proposed hardening
-    - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+    - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
+      with:
+        persist-credentials: false
🤖 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 @.github/workflows/reusable-release-validation.yml at line 25, Update the
actions/checkout step in the release validation workflow to set
persist-credentials to false. Leave the existing checkout action version and
surrounding workflow behavior unchanged.

Source: Linters/SAST tools

🤖 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 @.github/workflows/ci.yml:
- Line 24: Update the actions/checkout step to set persist-credentials to false,
while preserving the existing pinned action reference and version comment.
- Around line 34-64: Update the package workflow around the “Package split
direct, forwarding, and legacy consumers” step so SmartPipe.Extensions is built
in Release before its dotnet pack --no-build command runs. Add the build for
src/SmartPipe.Extensions/SmartPipe.Extensions.csproj alongside the existing
build flow, preserving the current no-build packaging behavior.

In @.github/workflows/publish-nuget.yml:
- Around line 29-31: Update the actions/checkout step in the version job to set
persist-credentials to false alongside fetch-depth, keeping the existing
checkout action and shallow/full-history configuration unchanged.

In `@docs/getting-started.md`:
- Around line 5-19: The “Choose the integration package” guidance should show
SmartPipe.Core alone as sufficient for the Delegate Pipeline and Component
Pipeline examples. Split the package instructions into a Core-only setup for
those examples and an additional integration-package setup for JSON, HTTP,
database, and other extensions, without presenting all three packages as
required upfront.

In `@eng/validate-json-package-split.ps1`:
- Line 224: Remove the --configfile argument from the dotnet run/build
invocations in the validation flow, including the command around Invoke-DotNet.
Rely on the parent NuGet.Config hierarchy, or explicitly restore with
--configfile first and then run/build with --no-restore.

In `@src/SmartPipe.Core/PipelineObserverDispatcher.cs`:
- Around line 216-244: The best-effort WriteAsync path currently lets
ChannelClosedException escape when the channel closes with a recorded pipeline
fault. Update the ChannelClosedException handling around WriteAsync to detect
closure while _completed is still zero, retrieve and rethrow the fault supplied
to _events.Writer.TryComplete, and preserve the existing teardown handling for
completed dispatchers.

In `@src/SmartPipe.Extensions.Json/README.md`:
- Around line 6-10: Update the repository’s lychee configuration to exclude
SmartPipe NuGet package URLs from link validation, or accept their expected
404/429 responses, so the unpublished SmartPipe.Extensions.Json package does not
block CI before release.

In `@src/SmartPipe.Extensions.Json/Selectors/DeadLetterSource.cs`:
- Around line 151-176: Change the legacy top-level-values deserialization flow
around JsonDocumentLimitStream so MaxDocumentSizeBytes is applied independently
to each JSON record rather than accumulating across the entire stream. Preserve
the existing whole-document behavior for array-rooted input, and ensure each
top-level value is processed through a fresh record-scoped limit before
ProcessElement is called.

In `@src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs`:
- Around line 188-213: The auto-detected deserialization path currently reuses
one JsonDocumentLimitStream across all top-level values, causing
MaxDocumentSizeBytes to accumulate globally. Update the flow around
_deserializeBatches and JsonDocumentLimitStream so each top-level JSON value is
processed with a fresh limit stream whose byte count resets, while preserving
batch iteration, invalid-null handling, and envelope creation.

In `@src/SmartPipe.Extensions.Json/Sinks/AppendFraming.cs`:
- Around line 11-38: Update RequiresLineSeparatorAsync’s three-byte BOM check to
use ReadExactlyAsync instead of ReadAsync, ensuring the full BOM is read before
comparing bom to the UTF-8 BOM. Preserve the existing cancellation and
stream-position restoration behavior.

In
`@tests/SmartPipe.Extensions.Json.Tests/Sources/DeadLetterSourceRecoveryTests.cs`:
- Line 373: Update both fake stream implementations in
DeadLetterSourceRecoveryTests, including the methods containing the line 373 and
line 463 copies, to use bytes.AsMemory().CopyTo(buffer) instead of
bytes.CopyTo(buffer).

---

Outside diff comments:
In `@docs/getting-started.md`:
- Around line 93-104: The component lists in the documentation do not identify
package ownership despite the split lead-in. Update the selector, transform, and
sink bullets to clearly label or group each item under SmartPipe.Extensions.Json
versus SmartPipe.Extensions, preserving the existing component names and
classifications.

---

Nitpick comments:
In @.github/workflows/publish-nuget.yml:
- Around line 42-45: Update the validation message in the workflow’s tag-ref
check to explicitly tell operators using manual recoverable-rerun dispatches to
select the release tag, while preserving the existing rejection behavior for
non-release refs.
- Around line 76-89: Replace the long-lived NUGET_API_KEY authentication in the
publish step with NuGet Trusted Publishing via GitHub OIDC. Configure the job
permissions and NuGet publishing commands according to the supported
trusted-publishing flow, remove the secret-based environment variable and
references, and preserve the existing package versioning and RECOVERABLE_RERUN
skip-duplicate behavior.

In @.github/workflows/reusable-release-validation.yml:
- Line 25: Update the actions/checkout step in the release validation workflow
to set persist-credentials to false. Leave the existing checkout action version
and surrounding workflow behavior unchanged.

In `@src/SmartPipe.Core/CircuitBreaker.cs`:
- Around line 706-721: Remove the _windowGate lock from GetWindowStatistics and
perform the existing ConcurrentQueue enumeration directly, preserving the
current total and failure counting logic and WindowStatistics result.

In `@src/SmartPipe.Extensions.Json/DeadLetterRecordReader.cs`:
- Around line 10-61: Reduce the cognitive complexity of ReadFramedAsync by
extracting the per-record size check and validation/deserialization logic into a
private TryReadRecordAsync helper. Have the helper return the parsed
DeadLetterEnvelope<T> together with any JsonException, preserving existing
record-size checks, serializer validation, and invalid-record behavior while
leaving logging and iteration in ReadFramedAsync.

In `@src/SmartPipe.Extensions.Json/JsonDocumentLimitStream.cs`:
- Line 58: Update the Flush method in JsonDocumentLimitStream to be a no-op
instead of throwing NotSupportedException, while leaving the stream’s other
read-only behavior unchanged.

In `@src/SmartPipe.Extensions.Json/JsonOptions.cs`:
- Around line 37-49: Deduplicate the identical option definitions represented by
JsonFileSourceOptions and DeadLetterSourceOptions. Prefer reusing
JsonFileSourceOptions or extracting a shared options base, while preserving the
existing properties and defaults and allowing the two source configurations to
retain any necessary source-specific behavior.

In `@src/SmartPipe.Extensions.Json/JsonRecordValidator.cs`:
- Around line 9-25: Add an explanatory comment to the intentionally empty body
of the while loop in JsonRecordValidator, clarifying that exhausting
Utf8JsonReader validates structural and trailing-content errors. Keep the
existing reader logic and exception handling unchanged.

In `@src/SmartPipe.Extensions.Json/Selectors/DeadLetterSource.cs`:
- Around line 111-176: Reduce the cognitive complexity of ReadEnvelopesAsync by
extracting serializer/format validation and the separate streaming or legacy
iteration paths into focused helper methods. Keep ReadEnvelopesAsync responsible
for dispatching between those paths, and preserve the existing cancellation,
empty-file, array/framed, and null OriginalPayload behaviors.
- Around line 270-295: Extract the duplicated ReadFirstJsonByteAsync logic from
DeadLetterSource and JsonFileSource into a shared internal static helper, then
update both callers to use it. Preserve the existing BOM handling, whitespace
skipping, stream positioning, cancellation, and nullable-byte behavior while
removing the duplicate implementations.

In `@src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs`:
- Around line 67-79: Extract the repeated "document" scope value into a
class-level constant in JsonFileSource, then replace all four usages in
_deserializeItems and _deserializeBatches, including the corresponding code
around _deserializeItemRecord and _deserializeBatchRecord, with that constant.
- Around line 323-348: Extract the duplicated ReadFirstJsonByteAsync logic from
JsonFileSource and DeadLetterSource into a shared internal helper, then update
both callers to use it. Preserve the existing BOM detection, whitespace
skipping, cancellation behavior, and stream-position semantics consistently,
including the initial reset before applying the BOM offset.
- Around line 245-289: Reduce the cognitive complexity of ReadFramedRecordsAsync
below the configured threshold by extracting the oversized-record handling and
JSON validation/deserialization error handling into focused helper methods.
Preserve the existing record index tracking, HandleInvalidRecord behavior, null
handling, and yielded-record behavior.

In `@src/SmartPipe.Extensions.Json/Sinks/JsonFileSink.cs`:
- Line 30: Update the _leaveOpen field in JsonFileSink to be readonly,
preserving its single assignment in the internal constructor and any existing
constructor behavior.
- Around line 240-252: In the batch-writing loop, merge the nested
_options.Format == JsonFileFormat.Array condition with the enclosing conditional
so comma insertion remains guarded by both the array format and (_arrayHasItems
|| output.Length > 1). Preserve the existing serialization and NDJSON newline
behavior.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: df57002f-90d5-4ed2-9949-ffe8731315af

📥 Commits

Reviewing files that changed from the base of the PR and between 4374a0d and 8f9a042.

📒 Files selected for processing (84)
  • .github/workflows/ci.yml
  • .github/workflows/publish-nuget.yml
  • .github/workflows/reusable-release-validation.yml
  • CHANGELOG.md
  • Directory.Build.props
  • README.md
  • SmartPipe.Core.slnx
  • docs/aot-compatibility.md
  • docs/api-reference.md
  • docs/architecture.md
  • docs/getting-started.md
  • docs/migration/2.1.2-json-package-split.md
  • docs/resilience.md
  • eng/tests/validate-json-package-split.Tests.ps1
  • eng/tests/validate-release-version.Tests.sh
  • eng/tests/workflow-contract.Tests.ps1
  • eng/tests/workflow_contract_tests.py
  • eng/validate-json-package-split.ps1
  • eng/validate-release-version.sh
  • src/SmartPipe.Core/CircuitBreaker.cs
  • src/SmartPipe.Core/DeadLetterSerialization.cs
  • src/SmartPipe.Core/PipelineObserverDispatcher.cs
  • src/SmartPipe.Core/PipelineRuntimeOptions.cs
  • src/SmartPipe.Core/PublicAPI.Shipped.txt
  • src/SmartPipe.Core/PublicAPI.Unshipped.txt
  • src/SmartPipe.Core/Runtime/Execution/PipelineTime.cs
  • src/SmartPipe.Core/SmartPipe.Core.csproj
  • src/SmartPipe.Core/TypedPipelineRuntime.cs
  • src/SmartPipe.Extensions.Json/DeadLetterRecordReader.cs
  • src/SmartPipe.Extensions.Json/JsonDocumentLimitStream.cs
  • src/SmartPipe.Extensions.Json/JsonDocumentValidator.cs
  • src/SmartPipe.Extensions.Json/JsonInfrastructureContext.cs
  • src/SmartPipe.Extensions.Json/JsonInputOptionsValidator.cs
  • src/SmartPipe.Extensions.Json/JsonOptions.cs
  • src/SmartPipe.Extensions.Json/JsonRecordValidator.cs
  • src/SmartPipe.Extensions.Json/PublicAPI.Shipped.txt
  • src/SmartPipe.Extensions.Json/PublicAPI.Unshipped.txt
  • src/SmartPipe.Extensions.Json/README.md
  • src/SmartPipe.Extensions.Json/Selectors/DeadLetterSource.cs
  • src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs
  • src/SmartPipe.Extensions.Json/SharedAsyncDisposeState.cs
  • src/SmartPipe.Extensions.Json/Sinks/AppendFraming.cs
  • src/SmartPipe.Extensions.Json/Sinks/DeadLetterSink.cs
  • src/SmartPipe.Extensions.Json/Sinks/JsonFileSink.cs
  • src/SmartPipe.Extensions.Json/SmartPipe.Extensions.Json.csproj
  • src/SmartPipe.Extensions.Json/Transforms/JsonTransform.cs
  • src/SmartPipe.Extensions.Json/Utf8LineRecordReader.cs
  • src/SmartPipe.Extensions.Json/packages.lock.json
  • src/SmartPipe.Extensions/JsonTypeForwarders.cs
  • src/SmartPipe.Extensions/PublicAPI.Shipped.txt
  • src/SmartPipe.Extensions/PublicAPI.Unshipped.txt
  • src/SmartPipe.Extensions/README.md
  • src/SmartPipe.Extensions/Selectors/DeadLetterSource.cs
  • src/SmartPipe.Extensions/Selectors/JsonFileSource.cs
  • src/SmartPipe.Extensions/Sinks/DeadLetterSink.cs
  • src/SmartPipe.Extensions/Sinks/JsonFileSink.cs
  • src/SmartPipe.Extensions/SmartPipe.Extensions.csproj
  • src/SmartPipe.Extensions/packages.lock.json
  • tests/SmartPipe.Core.Tests/Engine/ObserverDispatcherTests.cs
  • tests/SmartPipe.Core.Tests/Engine/TypedPipelineLifecycleTests.cs
  • tests/SmartPipe.Core.Tests/Resilience/CircuitBreakerTests.cs
  • tests/SmartPipe.Core.Tests/Types/DeadLetterSerializationTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/DeadLetterSinkAppendTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/DeadLetterSinkLifecycleTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/DeadLetterSinkTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Fixtures/JsonGoldenFixtureTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/JsonDocumentLimitStreamTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/JsonFileRoundTripTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/JsonOptionsTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/JsonTransformTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkAppendTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkLifecycleTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/SmartPipe.Extensions.Json.Tests.csproj
  • tests/SmartPipe.Extensions.Json.Tests/Sources/DeadLetterSourceRecoveryTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sources/DeadLetterSourceTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sources/JsonFileSourceLimitTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sources/JsonFileSourceMetadataTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sources/JsonFileSourceRecoveryTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sources/JsonFileSourceTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Utf8LineRecordReaderTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/packages.lock.json
  • tests/SmartPipe.Extensions.Tests/PackageOwnershipTests.cs
  • tests/SmartPipe.Extensions.Tests/packages.lock.json
💤 Files with no reviewable changes (5)
  • src/SmartPipe.Extensions/Selectors/JsonFileSource.cs
  • src/SmartPipe.Extensions/Sinks/JsonFileSink.cs
  • src/SmartPipe.Extensions/Selectors/DeadLetterSource.cs
  • src/SmartPipe.Extensions/Sinks/DeadLetterSink.cs
  • src/SmartPipe.Extensions/PublicAPI.Unshipped.txt

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/publish-nuget.yml
Comment thread docs/getting-started.md
Comment thread eng/validate-json-package-split.ps1 Outdated
Comment on lines +6 to +10
## Installation

```bash
dotnet add package SmartPipe.Extensions.Json --version 2.1.2
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

CI link check fails: NuGet package page is not published yet.

The markdown link check (lychee) fails with a 404 for https://www.nuget.org/packages/SmartPipe.Extensions.Json because the 2.1.2 package hasn't been published. Since this URL won't resolve until after release, exclude it from the link checker (lychee --exclude / .lycheeignore, or accept 404/429 for nuget.org/packages/SmartPipe.*) so the release PR isn't blocked pre-publish.

🤖 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 `@src/SmartPipe.Extensions.Json/README.md` around lines 6 - 10, Update the
repository’s lychee configuration to exclude SmartPipe NuGet package URLs from
link validation, or accept their expected 404/429 responses, so the unpublished
SmartPipe.Extensions.Json package does not block CI before release.

Source: Pipeline failures

Comment thread src/SmartPipe.Extensions.Json/Selectors/DeadLetterSource.cs
Comment thread src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs Outdated
Comment thread src/SmartPipe.Extensions.Json/Sinks/AppendFraming.cs
if (!_sent)
{
_sent = true;
bytes.CopyTo(buffer);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Enumerate the CopyTo call sites in the file to confirm the divergent patterns.
fd -a 'DeadLetterSourceRecoveryTests.cs' | while read -r f; do
  rg -nP 'bytes\.(CopyTo|AsMemory)\b' "$f"
done

Repository: MrFr3di/SmartPipe-Core

Length of output: 305


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="tests/SmartPipe.Extensions.Json.Tests/Sources/DeadLetterSourceRecoveryTests.cs"

echo "== usings =="
sed -n '1,80p' "$file"

echo
echo "== relevant regions =="
sed -n '350,480p' "$file"

echo
echo "== repo search for byte[] CopyTo extensions =="
rg -n "CopyTo\\s*\\(\\s*this\\s+byte\\s*\\[\\]" .
rg -n "static\\s+.*CopyTo\\s*\\(" tests src . | head -n 100

Repository: MrFr3di/SmartPipe-Core

Length of output: 10407


Replace bytes.CopyTo(buffer) with bytes.AsMemory().CopyTo(buffer)
bytes is a byte[], so the direct CopyTo(Memory<byte>) call doesn’t bind. This appears in both fake streams at lines 373 and 463.

🤖 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
`@tests/SmartPipe.Extensions.Json.Tests/Sources/DeadLetterSourceRecoveryTests.cs`
at line 373, Update both fake stream implementations in
DeadLetterSourceRecoveryTests, including the methods containing the line 373 and
line 463 copies, to use bytes.AsMemory().CopyTo(buffer) instead of
bytes.CopyTo(buffer).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (1)
eng/tests/workflow_contract_tests.py (1)

207-207: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract a constant for --minimum-expected-tests 1.

This literal appears at line 207 (validation check), line 268 (_strip_minimum_expected_from_windows), and line 314 (main mutation assertion). Duplicating it risks silent drift if the flag name changes.

♻️ Proposed refactor

Add a module-level constant near the other constants (e.g., after line 21):

+MINIMUM_EXPECTED_TESTS = "--minimum-expected-tests 1"

Then replace all three occurrences:

 # Line 207
-    require("--minimum-expected-tests 1" in command,
+    require(MINIMUM_EXPECTED_TESTS in command,
             f"Every filtered test command must set --minimum-expected-tests 1: {command}")

 # Line 268
-            step["run"] = str(step["run"]).replace("--minimum-expected-tests 1", "")
+            step["run"] = str(step["run"]).replace(MINIMUM_EXPECTED_TESTS, "")

 # Line 314
-        "--minimum-expected-tests 1",
+        MINIMUM_EXPECTED_TESTS,

Also applies to: 268-268, 314-314

🤖 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 `@eng/tests/workflow_contract_tests.py` at line 207, Extract the repeated
“--minimum-expected-tests 1” value into a module-level constant near the
existing constants, then reuse it in the validation check,
_strip_minimum_expected_from_windows, and main mutation assertion. Remove the
duplicated literals while preserving the current command-validation and mutation
behavior.

Source: Linters/SAST tools

🤖 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 `@eng/tests/workflow_contract_tests.py`:
- Line 204: Update the require call around filtered to pass an explicit boolean
by converting the list with bool(filtered) or checking len(filtered) > 0, while
preserving the existing assertion behavior.

---

Nitpick comments:
In `@eng/tests/workflow_contract_tests.py`:
- Line 207: Extract the repeated “--minimum-expected-tests 1” value into a
module-level constant near the existing constants, then reuse it in the
validation check, _strip_minimum_expected_from_windows, and main mutation
assertion. Remove the duplicated literals while preserving the current
command-validation and mutation behavior.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c609b52-4ce1-456f-bd47-709c61e5b5c6

📥 Commits

Reviewing files that changed from the base of the PR and between 8f9a042 and f5d87e0.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • .github/workflows/publish-nuget.yml
  • .github/workflows/reusable-release-validation.yml
  • eng/tests/workflow_contract_tests.py
  • lychee.toml
🚧 Files skipped from review as they are similar to previous changes (3)
  • .github/workflows/ci.yml
  • .github/workflows/publish-nuget.yml
  • .github/workflows/reusable-release-validation.yml

Comment thread eng/tests/workflow_contract_tests.py Outdated
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 1 unresolved review comment.

Files modified:

  • eng/tests/workflow_contract_tests.py

Commit: fb3f2c218a547185bb9efbacd77fa7403b13a63d

The changes have been pushed to the upd branch.

Time taken: 2m 34s

coderabbitai Bot and others added 6 commits July 12, 2026 19:07
Fixed 1 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
- Rename MaxDocumentSizeBytes to MaxUnframedInputSizeBytes (default 256 MiB).

- Rename JsonDocumentLimitStream to JsonUnframedInputLimitStream with an explicit unframed-input message.

- Apply MaxDepth uniformly to the legacy unframed dead-letter stream.

- Add DeadLetterSource(path, JsonTypeInfo<T>, DeadLetterSourceOptions) so the legacy unframed path is configurable.

- Update README, migration guide, and changelog; add contract tests and boundary cases.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/SmartPipe.Core.Tests/Engine/ObserverDispatcherTests.cs (1)

83-87: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Bound the concurrency coordination waits.

A regression preventing the callback or observer entry would currently hang the suite indefinitely rather than produce a useful failure.

Proposed fix
-        await timeProvider.TimerCallbackEntered.Task;
+        await timeProvider.TimerCallbackEntered.Task.WaitAsync(TimeSpan.FromSeconds(5));
         var dispose = fixture.Dispatcher.DisposeAsync().AsTask();
         timeProvider.ReleaseTimerCallback();
-        await Task.WhenAll(advance, emit, dispose);
+        await Task.WhenAll(advance, emit, dispose).WaitAsync(TimeSpan.FromSeconds(5));
         await dispatcher.EmitAsync(NewStartedEvent(), CancellationToken.None);
-        await observer.Entered.Task;
+        await observer.Entered.Task.WaitAsync(TimeSpan.FromSeconds(5));
         await dispatcher.EmitAsync(NewStartedEvent(), CancellationToken.None);

Also applies to: 292-295

🤖 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 `@tests/SmartPipe.Core.Tests/Engine/ObserverDispatcherTests.cs` around lines 83
- 87, Bound the coordination awaits in the test around
timeProvider.TimerCallbackEntered.Task and Task.WhenAll(advance, emit, dispose)
so callback or observer-entry regressions fail within a finite timeout instead
of hanging indefinitely. Apply the same bounded-wait behavior to the
corresponding coordination block noted in the comment, while preserving the
existing synchronization order and assertions.
🧹 Nitpick comments (1)
src/SmartPipe.Extensions.Json/JsonStreamProbe.cs (1)

35-40: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Scan leading whitespace in buffered chunks.

The current loop performs one asynchronous stream read per byte, making large whitespace prefixes unnecessarily expensive.

Proposed fix
-            var buffer = new byte[1];
-            while (await stream.ReadAsync(buffer, ct).ConfigureAwait(false) == 1)
+            var buffer = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = await stream.ReadAsync(buffer, ct).ConfigureAwait(false)) > 0)
             {
-                if (buffer[0] is not ((byte)' ' or (byte)'\t' or (byte)'\r' or (byte)'\n'))
-                    return new JsonStreamProbeResult(buffer[0], contentStartOffset);
+                foreach (var value in buffer.AsSpan(0, bytesRead))
+                {
+                    if (value is not ((byte)' ' or (byte)'\t' or (byte)'\r' or (byte)'\n'))
+                        return new JsonStreamProbeResult(value, contentStartOffset);
+                }
             }
🤖 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 `@src/SmartPipe.Extensions.Json/JsonStreamProbe.cs` around lines 35 - 40,
Update the leading-whitespace scan in the JsonStreamProbe logic to read buffered
chunks instead of allocating a one-byte buffer and awaiting once per byte.
Inspect each byte in the returned chunk, return the first non-whitespace byte
with the correct contentStartOffset, and preserve cancellation and end-of-stream
behavior.
🤖 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 `@tests/SmartPipe.Core.Tests/Engine/ObserverDispatcherTests.cs`:
- Around line 167-177: Update the disposal test around observer.Entered and
blockedEmit to saturate the channel first: emit and await an additional queued
event before creating the second emission that should remain blocked. Then
dispose and release the observer as currently arranged, preserving the assertion
that the genuinely blocked emission completes without throwing.

In `@tests/SmartPipe.Extensions.Json.Tests/Sources/JsonFileSourceLimitTests.cs`:
- Around line 158-174: Update the test method
LegacyTopLevelSequence_TotalAboveLimit_Throws to write an unwrapped payload such
as “1 2” instead of the root array “[1,2]”, so JsonFileFormat.Auto exercises
legacy top-level sequence detection while preserving the existing limit and
exception assertions.

---

Outside diff comments:
In `@tests/SmartPipe.Core.Tests/Engine/ObserverDispatcherTests.cs`:
- Around line 83-87: Bound the coordination awaits in the test around
timeProvider.TimerCallbackEntered.Task and Task.WhenAll(advance, emit, dispose)
so callback or observer-entry regressions fail within a finite timeout instead
of hanging indefinitely. Apply the same bounded-wait behavior to the
corresponding coordination block noted in the comment, while preserving the
existing synchronization order and assertions.

---

Nitpick comments:
In `@src/SmartPipe.Extensions.Json/JsonStreamProbe.cs`:
- Around line 35-40: Update the leading-whitespace scan in the JsonStreamProbe
logic to read buffered chunks instead of allocating a one-byte buffer and
awaiting once per byte. Inspect each byte in the returned chunk, return the
first non-whitespace byte with the correct contentStartOffset, and preserve
cancellation and end-of-stream behavior.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c43bc08d-ed8a-4d92-8a46-bf1efbc801cc

📥 Commits

Reviewing files that changed from the base of the PR and between f5d87e0 and f33a3f2.

📒 Files selected for processing (25)
  • CHANGELOG.md
  • docs/migration/2.1.2-json-package-split.md
  • eng/tests/validate-json-package-split.Tests.ps1
  • eng/tests/workflow_contract_tests.py
  • eng/validate-json-package-split.ps1
  • src/SmartPipe.Core/PipelineObserverDispatcher.cs
  • src/SmartPipe.Extensions.Json/JsonInputOptionsValidator.cs
  • src/SmartPipe.Extensions.Json/JsonOptions.cs
  • src/SmartPipe.Extensions.Json/JsonStreamProbe.cs
  • src/SmartPipe.Extensions.Json/JsonUnframedInputLimitStream.cs
  • src/SmartPipe.Extensions.Json/PublicAPI.Unshipped.txt
  • src/SmartPipe.Extensions.Json/README.md
  • src/SmartPipe.Extensions.Json/Selectors/DeadLetterSource.cs
  • src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs
  • src/SmartPipe.Extensions.Json/Sinks/AppendFraming.cs
  • src/SmartPipe.Extensions/PublicAPI.Unshipped.txt
  • tests/SmartPipe.Core.Tests/Engine/ObserverDispatcherTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/JsonOptionsTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/JsonStreamProbeTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/JsonUnframedInputLimitStreamTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkAppendTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sources/DeadLetterSourceRecoveryTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sources/DeadLetterSourceTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sources/JsonFileSourceLimitTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sources/JsonFileSourceTests.cs
🚧 Files skipped from review as they are similar to previous changes (11)
  • CHANGELOG.md
  • src/SmartPipe.Extensions.Json/Sinks/AppendFraming.cs
  • src/SmartPipe.Extensions/PublicAPI.Unshipped.txt
  • src/SmartPipe.Extensions.Json/README.md
  • docs/migration/2.1.2-json-package-split.md
  • tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkAppendTests.cs
  • src/SmartPipe.Extensions.Json/Selectors/DeadLetterSource.cs
  • src/SmartPipe.Extensions.Json/PublicAPI.Unshipped.txt
  • tests/SmartPipe.Extensions.Json.Tests/Sources/DeadLetterSourceTests.cs
  • eng/tests/workflow_contract_tests.py
  • src/SmartPipe.Core/PipelineObserverDispatcher.cs

Comment thread tests/SmartPipe.Core.Tests/Engine/ObserverDispatcherTests.cs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkLifecycleTests.cs (1)

184-238: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate fault-injecting MemoryStream test doubles across two lifecycle test files. ReentrantDisposeStream and FaultingDisposeStream both reimplement Flush/Dispose failure injection on a MemoryStream for near-identical dispose-lifecycle scenarios within the same test project.

  • tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkLifecycleTests.cs#L184-L238: promote ReentrantDisposeStream (already the superset — supports WriteFailure, blocking, reentrancy) into a shared internal test-utility class for the project.
  • tests/SmartPipe.Extensions.Json.Tests/DeadLetterSinkLifecycleTests.cs#L173-L194: replace FaultingDisposeStream with the shared fault-injecting stream double instead of maintaining a second, narrower copy.
🤖 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 `@tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkLifecycleTests.cs`
around lines 184 - 238, Promote ReentrantDisposeStream from
tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkLifecycleTests.cs:184-238
into a shared internal test utility, preserving its existing write-failure,
flush-blocking, reentrancy, and dispose-failure behavior. In
tests/SmartPipe.Extensions.Json.Tests/DeadLetterSinkLifecycleTests.cs:173-194,
remove FaultingDisposeStream and update its usages to the shared
ReentrantDisposeStream; no other lifecycle behavior should change.
🤖 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.

Nitpick comments:
In `@tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkLifecycleTests.cs`:
- Around line 184-238: Promote ReentrantDisposeStream from
tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkLifecycleTests.cs:184-238
into a shared internal test utility, preserving its existing write-failure,
flush-blocking, reentrancy, and dispose-failure behavior. In
tests/SmartPipe.Extensions.Json.Tests/DeadLetterSinkLifecycleTests.cs:173-194,
remove FaultingDisposeStream and update its usages to the shared
ReentrantDisposeStream; no other lifecycle behavior should change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c880a2e-89f3-48e9-b6e7-1e0f94822a87

📥 Commits

Reviewing files that changed from the base of the PR and between f33a3f2 and 204b5d3.

📒 Files selected for processing (21)
  • .github/workflows/publish-nuget.yml
  • .github/workflows/reusable-release-validation.yml
  • .work/reviews/pull27/plan 2.1.3.md
  • docs/getting-started.md
  • eng/tests/validate-json-package-split.Tests.ps1
  • eng/validate-json-package-split.ps1
  • src/SmartPipe.Extensions.Json/DeadLetterRecordReader.cs
  • src/SmartPipe.Extensions.Json/JsonRecordValidator.cs
  • src/SmartPipe.Extensions.Json/PublicAPI.Unshipped.txt
  • src/SmartPipe.Extensions.Json/Selectors/DeadLetterSource.cs
  • src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs
  • src/SmartPipe.Extensions.Json/SharedAsyncDisposeState.cs
  • src/SmartPipe.Extensions.Json/Sinks/DeadLetterSink.cs
  • src/SmartPipe.Extensions.Json/Sinks/JsonFileSink.cs
  • src/SmartPipe.Extensions/PublicAPI.Shipped.txt
  • tests/SmartPipe.Extensions.Json.Tests/DeadLetterSinkLifecycleTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/JsonFileRoundTripTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkAppendTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkLifecycleTests.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sources/DeadLetterSourceRecoveryTests.cs
  • tests/SmartPipe.Extensions.Tests/PackageOwnershipTests.cs
💤 Files with no reviewable changes (2)
  • src/SmartPipe.Extensions.Json/PublicAPI.Unshipped.txt
  • src/SmartPipe.Extensions/PublicAPI.Shipped.txt
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/SmartPipe.Extensions.Json/JsonRecordValidator.cs
  • src/SmartPipe.Extensions.Json/SharedAsyncDisposeState.cs
  • tests/SmartPipe.Extensions.Json.Tests/JsonFileRoundTripTests.cs
  • docs/getting-started.md
  • eng/tests/validate-json-package-split.Tests.ps1
  • .github/workflows/publish-nuget.yml
  • .github/workflows/reusable-release-validation.yml
  • src/SmartPipe.Extensions.Json/Sinks/JsonFileSink.cs
  • src/SmartPipe.Extensions.Json/Selectors/JsonFileSource.cs
  • tests/SmartPipe.Extensions.Json.Tests/Sinks/JsonFileSinkAppendTests.cs
  • src/SmartPipe.Extensions.Json/Sinks/DeadLetterSink.cs

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

@MrFr3di
MrFr3di merged commit 7dd65eb into main Jul 14, 2026
9 of 10 checks passed
@MrFr3di
MrFr3di had a problem deploying to nuget-production July 14, 2026 19:37 — with GitHub Actions Failure
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