Skip to content

feat(occasionally-connected): add first-release durable sync packages - #229

Open
ChrisPulman wants to merge 496 commits into
mainfrom
OccasionallyConnected
Open

ChrisPulman wants to merge 496 commits into
mainfrom
OccasionallyConnected

Conversation

@ChrisPulman

@ChrisPulman ChrisPulman commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

What kind of change does this PR introduce?

feat: the first public release of ReactiveUI.Primitives.OccasionallyConnected across seven NuGet packages. It adds durable local commits, synchronization, SQLite storage, HTTP transport, server coordination, dependency injection, and hosting integration.

What is the new behavior?

Applications can capture owned inputs within a bounded outbox, keep working offline, and reconcile with a remote server after reconnecting. The client and server SQLite stores start at complete V1 schemas and reject unsupported layouts. Encrypted client records authenticate protected values and durable operation state. This does not detect every record deletion or restoration of an older valid database; those threat models require an independently protected checkpoint outside the database. The sample applications cover durable outbox, collaboration, resilience, and a clean packed-package consumer.

What is the current behavior?

main has no OccasionallyConnected packages or end-to-end examples.

Checklist

  • Tests have been added or updated (for bug fixes / features)
  • Docs have been added or updated (for bug fixes / features)
  • Changes target the main branch
  • PR title follows Conventional Commits

Additional information

  • All 12 Release net10.0 TUnit suites passed after integrating the latest main: 4,886 passed, 0 failed, 8 capability-based skips; all 12 builds reported zero warnings and errors.
  • Every package exceeded 98% handwritten line and branch coverage. The lowest branch result was 98.01% for the runtime package; generated JSON code is reported separately without exclusions or suppressions.
  • Package release gate passed deterministic packing, Source Link and symbols, local-feed restore, and all 19 clean-consumer scenarios on net8.0, net9.0, net10.0, net11.0, net462, net472, net48, and net481.
  • Supply-chain gate passed for seven packages with a validated SPDX SBOM; four mutation campaigns were killed by their TUnit tests. NativeAOT, crash, soak, and release workflows run in CI.
  • The branch contains signed feature and merge commits. The feature is unreleased V1; no end-user database migration is needed.

…jects

Add both example applications and their TUnit suites to the main solution so solution builds and test discovery include them.
Preserve unrelated existing solution ordering edits in the working tree.

Validation: parsed solution XML; each of four unique project paths exists; project analyzer builds and full suites passed before registration.
…checks

Restore reviewed donor assertions through both configured activity resolvers and the domain handler under current accepted canonical semantics.
Compare contract metadata, schema, hash and bytes across resolved payload, state and event; verify version and trusted client/operation/timestamp provenance.
Preserve stale disjoint patch fields and audit provenance through the public hub.

Validation: clean net8 analyzer build; full server example suite138/138; resolver and domain handler100% line and branch coverage.
No production behavior changed; the legacy donor expectations were adapted to canonical accepted acknowledgements.
Integration status:
Record signed donor integration and retirement of all registered worktrees.

Remaining acceptance:
Update runtime and example test and coverage evidence; retain framework, packaging, analyzer, CI and demonstration gates.
Document blocked physical-directory cleanup and preserve local-only work.
Add an OccasionallyConnected compatibility fixture matrix and link it from the implementation doc. Update package versions, add launch settings for the collaboration server, switch example/test synchronization gates to `Lock`, thread cancellation through console writes, and add a scoped test suppression for forced finalizer GC.
…tions

Core models:
- Add the new Core package with NFC stream identity validation and bounded opaque subscription start positions.
- Track its public API on all eight supported library target frameworks.

Validation and integration:
- Add 40 TUnit tests with executable failing-stub evidence followed by passing implementations.
- Enforce 100% line and branch coverage without source, method or attribute exclusions.
- Add feature-branch CI and retain per-platform coverage reports.
- Preserve the design specification and document the remaining staged v1 work.

Verification:
- Release builds pass all eight library TFMs with zero warnings or errors.
- TUnit passes on net8.0, net9.0, net10.0 and net11.0; MCP confirms 66/66 lines and 58/58 branches.
- NuGet packing succeeds without new warning suppressions.
…tions

Core identities and options
- Add stable operation and subscription IDs, delivery/conflict/buffer enums, and immutable option records.
- Validate stream identities, bounded capacities, custom policy registration, configurable priority ranges, and synchronous observer constraints.
- Reject dropping admission for durable work and non-durable exactly-once requests.

Validation
- Expand the suite to 90 TUnit tests on each modern framework; disabling validation causes 29 failures.
- Verify 100% line and branch coverage with Mtpunittestmcp on all four modern frameworks.
- Build all eight library frameworks and refresh API baselines with no warnings or suppressions.
Batch configuration
- Add immutable operation count, encoded-byte, dwell-time and per-stream concurrency limits.
- Match the design defaults and reject non-positive limits before runtime initialization.

Validation
- Start with ten tests against a compilable stub; eight fail before implementation.
- Verify 100 passing TUnit tests on each modern framework and 100% line/branch coverage through Mtpunittestmcp.
- Refresh all eight public API baselines without suppressions.
Runtime policy
- Add the lean runtime project and thread-safe closed/open/half-open admission with TimeProvider injection.
- Apply configurable five-failure and thirty-second defaults, one recovery probe, successful reset and explicit abandoned-probe recovery.
- Preserve deadlines on late failures and bound failure counts and UTC deadline arithmetic.

Verification
- Root review completes the worker handoff and removes unreachable state branches without suppression.
- Pass 106 Core and 15 runtime TUnit tests per modern framework with 100% line and branch coverage inspected via Mtpunittestmcp.
- Verify threshold mutation causes seven failures and refresh all eight public API baselines.
Retry decisions:
- Persist attempt counts, previous delays, credential versions and UTC deadlines.
- Compute decorrelated jitter with injectable time and randomness and honor Retry-After lower bounds.
- Reject invalid state and stop at attempt, age and representable calendar limits.
- Require credential renewal for the one immediate authentication retry per version.

Verification:
- Root regression tests exposed twelve failures before the fixes.
- 120 Core and 49 runtime TUnit tests pass on net8/net9/net10/net11.
- Each matching package has 100% line and branch coverage without exclusions.
- All eight library targets build without warnings or analyzer suppressions.
Place exception documentation before remarks to satisfy SST1666 after the final documentation update.
Verified the combined Core and runtime net10.0 build with all analyzers enabled.
Payload contracts:
- Own immutable payload bytes and expose their encoded length without copying.
- Register source-generated JSON schemas and contiguous deterministic upcasters.
- Freeze schema metadata and snapshot registrations for each serializer.

Runtime validation:
- Enforce exact encoded-byte limits with bounded scratch allocation.
- Validate content type, schema, allowlisted type and stored SHA-256 hash.
- Revalidate upcast results and preserve cancellation and stable schema failures.
- Reject reference preservation and polymorphic root metadata.

Verification:
- Root executable regression tests preceded fixes for size boundaries, malformed hashes and cancellation.
- 128 Core and 110 runtime TUnit tests pass on each modern target with 100% line and branch coverage.
- All eight library targets build without warnings or suppressions.
… safety

Admission behavior:
- Enforce count and byte bounds plus a finite blocked-producer budget.
- Preserve FIFO within data and control classes while reserving control capacity.
- Support block, reject, eligible drops and custom blocking decisions.
- Revalidate unlocked custom decisions before admission.
- Preserve cancellation tokens and committed receipts, register callbacks outside locks and release buffers on disposal.
- Avoid overflow and user-defined equality under queue locks.

Configuration:
- Add immutable positive outbox and inbox capacity options with finite defaults.

Verification:
- Worker and root executable regression tests exposed the corrected behavior.
- Disabling capacity validation caused ten failures.
- 142 Core and 161 runtime TUnit tests pass on all four modern frameworks.
- Matching production packages have 100% line and branch coverage; all eight library builds pass.
…iagnostics

Configuration
- Add required nested context options with complete immutable defaults.
- Validate positive retention, payload/message/decompression limits, replay intervals,
  diagnostic sampling and both fault-count and fault-byte queue capacities.
- Keep raw identifiers absent by default and expose explicit hashed-identifier opt-in.

Verification
- Add type-specific TUnit tests for valid defaults, malformed values and copied options.
- Independently verify 182 Core tests on each modern target with 100% line and branch coverage.
- Confirm a high-water boundary mutation causes a real assertion failure, then restore it.
- Build all eight Core target frameworks without warnings, errors or new suppressions.

Documentation
- Record implementation defaults, completed gates and remaining runtime enforcement work.
…ojection contracts

Protocol and persistence
- Add immutable operation, event, batch, recovery, lease and operation-status models.
- Persist effective operation policy in the contract and expose durable retry/status lookup.
- Define lease-owned attempt barriers, optimistic snapshot revisions and atomic result application.
- Validate batch correlation, exact operation membership, stream/sequence order and retry hints.

Projection and conflict resolution
- Pass decoded TInput to synchronous remote projections alongside the immutable event envelope.
- Add deterministic conflict resolver contracts and defensive copies of all decision collections.
- Provide explicit cancellation-free extension overloads with forwarding verification.

Verification and documentation
- Add type-specific TUnit tests and behavioral regressions for malformed results and collection ownership.
- Verify 257 Core tests per modern framework with 100% line and branch coverage.
- Build all eight Core frameworks without warnings, errors or new suppressions.
- Document completed contract guarantees and pending runtime/storage enforcement.
…chronization

Capability requirements
- Intersect known transport and authenticated peer features and batch limits.
- Reject unsupported durability, cursor, lease, multiprocess and encryption requirements.
- Require idempotency for at-least-once and transactional inbox/effect/acknowledgement support for exactly-once.

Retention and protocol
- Select supported protocol 1.0 and reject incompatible major versions.
- Expose the effective exactly-once window bounded by actual client/server retention.
- Reject unsupported inbox retention promises and clear peer-provided effective guarantee claims.

Validation
- TDD: 35 failing stub cases plus a failing durable-inbox regression before correction.
- 257 Core and 198 runtime TUnit tests per modern target; 100% line and branch coverage via MTP.
- All eight library targets build without warnings, errors or new suppressions.
…ueues

Observer behavior
- Bound each subscription by count and estimated bytes with independently scheduled serial drains.
- Coalesce optional latest-state overflow while always disconnecting overflowing event observers.
- Preserve accepted data before terminal callbacks and clear unclaimed work on disposal.

Failure isolation
- Return scheduler rejection after clearing the subscription without calling consumers or diagnostics inline.
- Contain observer and fault-reporter failures; retain a fixed-size reporter health flag.
- Handle thread-pool queue rejection and avoid nullable suppressions or ineffective exception-observation code.

Validation
- Agent regression RED plus root failing reporter-health regression before fixes.
- 226 runtime TUnit tests per modern framework with 100% lines and branches via MTP.
- All eight library targets build without warnings or errors; tests follow production-type naming.
…faults

Fault contracts
- Add stable component categories and information/warning/error/critical severity.
- Add immutable category, severity and transient-status properties while preserving existing constructor calls.
- Validate nonblank codes, diagnostic messages, defined classifications and optional identifiers.

Validation
- Seven executable negative cases failed before implementing classification and identity checks.
- 268 Core TUnit tests per modern framework with 100% lines and branches via MTP.
- All eight Core library targets build without warnings, errors or new suppressions.
Configuration
- Add required-init typed stream definitions for projection, stream identity and input/state contracts.
- Validate schema and snapshot versions, nested identities, priority bounds and custom policy support.
- Publish matching API baselines for all eight library targets.

Verification
- Add behavioral TUnit tests for defaults, invalid contracts, durable subscription identities and nested policy validation.
- Verify 289 Core tests on each modern runtime with 100% line and branch coverage.
- Build all eight Core library targets without warnings or suppressions.
- Document the verified stage and remaining context identity integration.
…ker (#198)

* feat(occasionally-connected): add validated identities and start positions

Core models:
- Add the new Core package with NFC stream identity validation and bounded opaque subscription start positions.
- Track its public API on all eight supported library target frameworks.

Validation and integration:
- Add 40 TUnit tests with executable failing-stub evidence followed by passing implementations.
- Enforce 100% line and branch coverage without source, method or attribute exclusions.
- Add feature-branch CI and retain per-platform coverage reports.
- Preserve the design specification and document the remaining staged v1 work.

Verification:
- Release builds pass all eight library TFMs with zero warnings or errors.
- TUnit passes on net8.0, net9.0, net10.0 and net11.0; MCP confirms 66/66 lines and 58/58 branches.
- NuGet packing succeeds without new warning suppressions.

* feat(occasionally-connected): validate publishing and subscription options

Core identities and options
- Add stable operation and subscription IDs, delivery/conflict/buffer enums, and immutable option records.
- Validate stream identities, bounded capacities, custom policy registration, configurable priority ranges, and synchronous observer constraints.
- Reject dropping admission for durable work and non-durable exactly-once requests.

Validation
- Expand the suite to 90 TUnit tests on each modern framework; disabling validation causes 29 failures.
- Verify 100% line and branch coverage with Mtpunittestmcp on all four modern frameworks.
- Build all eight library frameworks and refresh API baselines with no warnings or suppressions.

* feat(occasionally-connected): define validated batch limits

Batch configuration
- Add immutable operation count, encoded-byte, dwell-time and per-stream concurrency limits.
- Match the design defaults and reject non-positive limits before runtime initialization.

Validation
- Start with ten tests against a compilable stub; eight fail before implementation.
- Verify 100 passing TUnit tests on each modern framework and 100% line/branch coverage through Mtpunittestmcp.
- Refresh all eight public API baselines without suppressions.

* feat(occasionally-connected): add deterministic endpoint circuit breaker

Runtime policy
- Add the lean runtime project and thread-safe closed/open/half-open admission with TimeProvider injection.
- Apply configurable five-failure and thirty-second defaults, one recovery probe, successful reset and explicit abandoned-probe recovery.
- Preserve deadlines on late failures and bound failure counts and UTC deadline arithmetic.

Verification
- Root review completes the worker handoff and removes unreachable state branches without suppression.
- Pass 106 Core and 15 runtime TUnit tests per modern framework with 100% line and branch coverage inspected via Mtpunittestmcp.
- Verify threshold mutation causes seven failures and refresh all eight public API baselines.
Coverage tooling
- Upgrade Microsoft.Testing.Extensions.CodeCoverage from 18.11.0 to 18.11.2, the upstream fix for SharedBufferReconciler AbandonedMutexException (microsoft/codecoverage#245).
- Cancel superseded feature coverage runs while retaining the complete OS/framework matrix and strict coverage gate.

Deterministic verification
- Add a fixed-clock resumed-operation test proving elapsed age reduces the remaining retry budget; replacing elapsed-age handling produced an executable failure.
- Give the existing async-enumeration completion guard 30 seconds on instrumented CI runners and dispose its subscription; preserve all value/completion assertions.

Validation
- Strict builds pass for Core, runtime and existing test projects on net8/net9/net10/net11 without warnings.
- All 289 Core and 227 runtime tests pass on every modern TFM with collector18.11.2; MTP confirms 100% matching-package line and branch coverage.
- The affected existing SignalOperatorMixins test passes on all four modern TFMs.
- No suppressions, test skips, coverage exclusions or relaxed coverage thresholds added.
Transaction kernel
- Require validated durable recovery before local publishing and reject overlapping operations without an unbounded waiter queue.
- Serialize and decode persisted input before pure optimistic projection, then atomically store operation and snapshot with expected revision.
- Advance visible state only after validated commit receipts; retain successful receipts after late cancellation and poison malformed store results.
- Recover stable identity, snapshot, cursor and sequence without replaying already-projected pending operations.

Validation
- Exercise cancellation, failed storage, corrupt recovery, malformed receipts, overflow and policy validation using TUnit.
- Add restart and stale-writer tests with atomic revision/sequence checks and explicitly complete concurrency test operations.
- Verify 263 runtime tests on each modern framework with 100% matching-package line and branch coverage using collector18.11.2.
- Build all eight runtime library targets with zero warnings/errors and no suppressions.

Scope
- Keep queue/lifecycle/remote synchronization integration and concrete durable adapters as subsequent stages.
… resolution

Contract
- Resolve one durable subscription identifier per initialized store and stream.
- Specify first committed mapping wins, explicit preference mismatch rejection, and cancellation without deleting other committed mappings.
- Add the explicit no-cancellation overload and all eight public API baselines.

Validation
- Verify exact forwarding and failure propagation with TUnit; mutation of the preferred identifier produced an executable regression failure.
- Pass 291 Core and 263 runtime tests on each modern framework with 100% matching-package line and branch coverage.
- Build all eight Core library targets without warnings or errors.
- Document that concrete durable-store identity conformance remains separate implementation work.
Behavior
- Add QueueCapacityExceededException with an immutable hint indicating whether an operation may fit after draining.
- Keep standard exception constructors conservative: failures without a hint never automatically wait for capacity.
- Preserve messages and wrapped causes and reject null messages before use.

Validation
- Add TUnit cases for full queues, permanently oversized items, standard constructors, wrapped failures and null messages.
- Verify an inverted hint fails three executable tests before restoring the implementation.
- Pass all 296 Core tests on each modern framework with 100% line and branch coverage.
- Build all eight Core targets without warnings or errors and update their public API baselines.

Scope
- Define the failure contract; store capacity enforcement and producer waiting remain subsequent integration stages.
API: Forward operation application and remote subscriptions with explicit CancellationToken.None while preserving argument and result identity.

Validation: Add four TUnit forwarding, ordered enumeration and exception propagation tests. Root mutation fails before restoration. All 300 Core tests pass on each modern target at 100 percent line and branch coverage; all eight library targets build cleanly.
Transactions: Share exclusive ownership with local commits and recovery. Filter durable inbox duplicates before decoding and projection, then persist cursor and snapshot under the expected revision. Validate receipts before state publication and fail closed on adapter contract violations.

Recovery: Preserve cursor monotonicity for duplicate replays, commit duplicate cursor advances, reject stale revisions and retain successful receipts after cancellation.

Validation: Add remote protocol, retry and restart, malformed receipt, cancellation and overlap TUnit cases. Root dedup mutation caused eleven failures before restoration. All 298 runtime tests pass on four modern targets with 100 percent matching line and branch coverage; all eight library targets build cleanly.
Storage: Add a file-backed identity component with atomic first-write-wins mappings per store partition and stream. Validate schema ownership and definitions, verify WAL and FULL synchronous durability, reject malformed records and incompatible initialization, and preserve committed mappings through cancellation and reopen.

Packaging: Add SQLite library and TUnit projects to the solution. Use Microsoft.Data.Sqlite 10.0.12 and the corrected SQLitePCLRaw bundle 2.1.13 with normal runtime assets and public API tracking enabled.

Validation: Add 37 real-file TUnit tests per modern target for reopen, competing writers, schema corruption and lifecycle/cancellation failures. Root mutation failed the cross-stream mapping regression before restoration. Matching package coverage is 100 percent lines and branches across four targets; all eight library targets and package creation pass.
Behavior: add decoded remote messages, subscription and publish interfaces, and explicit cancellation/default-input convenience overloads.

Validation: root-reviewed 308 TUnit tests per modern target, 100% matching line and branch coverage, all eight library builds, and an executable input-forwarding mutation regression. Concrete facade implementation remains pending.
… APIs

Replace the oversized positional operation example with the approved required-init shape and persisted policy. Show decoded TInput in ApplyRemote and document owned metadata semantics to match the verified Core API.
API
- Add typed stream interfaces and explicit publish/start/stop overloads.
- Document local replay, committed remote delivery and producer-local input semantics.
- Record the API across all eight supported library frameworks.

Validation
- Add TUnit forwarding, type-surface, reference-identity and failure propagation tests.
- Root cancellation mutation fails as expected before restoration.
- All 315 Core tests pass on each modern TFM with 100% matching line and branch coverage.
- All eight Core library targets build without warnings or errors.

Scope
- Contracts only; concrete stream runtime and durability integration remain pending.
Context API
- Add the context-owned sync engine, lifecycle observable and typed stream factory interface.
- Provide explicit no-cancellation start and stop convenience overloads and all eight API baselines.

Verification
- Verify incomplete lifecycle forwarding, exact calls and unwrapped failures with TUnit.
- Root mutation redirecting start to stop compiled and failed three tests before restoration.
- All 319 Core tests pass per net8-net11 with 100% matching line and branch coverage; all eight library targets build cleanly.

Documentation
- Align context cancellation signatures with the approved API shape and record component-only scope.
ChrisPulman and others added 3 commits October 1, 2026 02:45
Replace cancelable Task.Delay waits in the disposal reentrancy and callback-fault fixtures with explicit cancellation-release signals registered before the callbacks under test.

Cancellation callbacks run LIFO. The former delay callback could release the handler first, allowing its async scope to unregister the earlier reentrant/faulting callback before cancellation reached it. The new ordering invokes the callback under test before releasing the handler scope, then preserves the expected OperationCanceledException.

Keep disposal admission, reentrant acknowledgement, callback-fault cleanup assertions and original five-second guard unchanged. No production change, retry or suppression.

Validation: all 120 analyzer-enabled Release net11 loopback tests pass with fresh MTP coverage under a two-CPU budget.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Evaluate project frameworks from the caller's stable working directory rather than the project's temporary directory, and disable MSBuild node reuse for the property-query subprocess.

The Windows full-solution run found a framework-discovery fixture directory still held by a reused MSBuild node after the parent dotnet process exited. Absolute project paths preserve normal import and conditional-property evaluation without leaving background nodes attached to temporary fixture directories.

Validation: all 32 analyzer-enabled Release net8 coverage/discovery tests pass in 5.3 seconds, including imported properties, conditional assignments, exact framework matching and real temporary-directory disposal. No gate, timeout or production API changed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Share the CPU-sized TUnit budget across collaboration application tests while preserving concurrent clients within each fixture. Run the intentionally blocking observer on a dedicated thread and always release it if setup fails. Isolate strict per-case fuzz deadlines from unrelated blocking fixtures without changing seeds, case counts, assertions, or timeouts.

Validated the complete net9 runtime suite and net9/net10 collaboration suites with the CI SDK and a two-CPU budget. Fresh TUnit coverage collected for net9 runtime and net10 collaboration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@@ -10,6 +10,15 @@
"ReactiveUI.Primitives.Async\\ReactiveUI.Primitives.Async.csproj",
"ReactiveUI.Primitives.Blazor\\ReactiveUI.Primitives.Blazor.csproj",
"ReactiveUI.Primitives.Maui\\ReactiveUI.Primitives.Maui.csproj",
"ReactiveUI.Primitives.OccasionallyConnected.Reactive\\ReactiveUI.Primitives.OccasionallyConnected.Reactive.csproj",

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.

Four packages are in the .slnx but in neither filter: OccasionallyConnected, .Core, .Server and .Storage.Sqlite. The release packs the filter, so these four never get packed or published. The packages that are in the filter depend on them, so they would ship with dependencies that do not exist on NuGet.

{
"platform": {
"execution": {
"parallel": false

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.

TUnit does not read platform.execution.parallel. Neither TUnit nor Microsoft.Testing.Platform has that key, so this file does not make the tests run one at a time. The test projects use [NotInParallel] and [ParallelLimiter], which only matter because the tests do run in parallel.

]
},
"Attributes": {
"Exclude": []

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.

This file is a copy of src/testconfig.json with every coverage exclusion removed. The only real difference is the coverage section, so a second config file and a new TestConfigurationFile property exist only to change coverage filters.

Comment thread src/Directory.Build.props
</ItemGroup>

<!-- OccasionallyConnected packages ship Source Link-enabled portable PDBs in a .snupkg symbol package
(spec section 18.1) instead of embedding them, and use CI build metadata on build servers so

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.

"spec section 18.1" points to a design document. A build file is the wrong place for that reference.

The .snupkg switch means no symbols ship. The shared release uploads only *.nupkg, so the symbol package is dropped and these packages lose the embedded PDBs every other package has.

Comment thread src/Directory.Build.props
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)..\LICENSE" Pack="true" PackagePath="LICENSE"/>
<None Include="$(MSBuildThisFileDirectory)..\README.md" Pack="true" PackagePath="README.md"/>
<None Include="$(MSBuildThisFileDirectory)..\README.md" Pack="true" PackagePath="README.md"

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.

This lists 16 project names by hand. Every other condition in this PR uses StartsWith('ReactiveUI.Primitives.OccasionallyConnected'). The next package added will pack two README.md files.

Nonce = nonce,
SentAtUtc = observedUtc,
ReplaySessionId = _replaySessionId,
ReplayMac = "placeholder",

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.

A literal "placeholder" MAC in shipped code.

{
_linked.Token.ThrowIfCancellationRequested();
var batches = await _owner.ReceiveSubscribeResponseAsync(_request, _cursor, _linked.Token).ConfigureAwait(false);
if (batches.Length == 0)

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.

An empty response loops straight into the next request with no delay. A server that answers fast with no data turns the client into a busy loop. On a battery device that keeps the radio awake.

/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task that completes when the record is durably flushed.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ValueTask PersistAsync(CancellationToken cancellationToken) => AppendAsync(new(_state), cancellationToken);

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.

Every change appends a full copy of the whole store to the journal. Each write costs the size of all state, and the journal grows quickly until compaction. That is heavy flash wear on a device.

break;
}

if (!FixedTimeEquals(ComputeHash(payload), checksum))

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.

A final record with the right length and a bad checksum throws, even when the record before it is valid. A crash mid-write can produce that record, and the store then cannot open.

var database = _database!;
var collection = database.GetCollection<BsonDocument>(StoreCollectionName);
var json = JsonSerializer.Serialize(new(CurrentFormatVersion, next), JsonContext.StoreDocument);
var document = new BsonDocument { ["_id"] = StoreDocumentId, ["StateJson"] = json, };

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.

The whole store goes into one JSON string in one document. LiteDB caps a document at about 16 MB, and the default outbox allows 64 MB, so the store fails once the queue passes about 12 MB. LiteDB is a good fit for devices, but this gets none of its indexes or per-record writes. The LiteDb, BliteDb and FileSystem adapters are close copies of each other.

ChrisPulman and others added 2 commits October 1, 2026 05:46
…w bindings

Remove Microsoft.Data.Sqlite.Core completely. Use shared SQLitePCLRaw database, statement, row, transaction and incremental BLOB owners with the SQLite3MC bundle. Preserve adapter APIs, record encryption, key rotation, WAL durability, cancellation, busy deadlines and crash recovery. Add a provider-neutral SQLite exception and public API baselines.

Reproduce and cover post-commit encryption cancellation and bounded backup contention. Committed encryption changes retain success; exhausted backup lock waits fail instead of restarting their deadline.

Partition CI tests without omitting any suite or increasing test deadlines. The required three-OS/four-framework feature matrix runs on every PR and main push; the shared Build job runs the other 20 suites. Sonar retains its existing policy, builds all targets, runs the other suites across targets, and imports complete feature source coverage from the 21-suite .NET 10 gate.

Update package documentation and remove the completed provider migration from RemainingTasks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add an explicit --no-build option to the full coverage invocation. Sonar already builds every target successfully; reuse those Release assemblies rather than repeat analyzer-instrumented builds for every feature suite. Keep required suite discovery, assembly checks, fresh reports, test execution and coverage thresholds unchanged. Reject the option for standalone report validation or incomplete CI arguments.

Inject the CLI error writer for argument tests instead of replacing TUnit's global Console writer. Restore the shared workflow's original overall job budget; all test deadlines remain unchanged.

Validated 34 coverage CLI regression cases and the complete 21-suite feature gate with zero build steps and all coverage thresholds passing.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@glennawatson glennawatson 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.

General review

This review covers the whole PR at f625d79c. My earlier review covered performance and correctness. The inline comments on this review cover:

  • version 7 GUIDs
  • the raw SQLite layer
  • ahead-of-time compilation
  • each platform
  • SonarCloud
  • naming

Comments that start with "Devil's advocate:" argue for common .NET packages and standards in place of custom code. Some of them conclude the PR's choice is defensible.

Thanks for the quick move to raw SQLitePCLRaw. Microsoft.Data.Sqlite is gone, and so is its reflection lookup of the provider.

Summary

  • Offline-first sync is a feature many users ask for, and the core shape is good. The builder has no container dependency, and the transport and store interfaces are small.
  • The PR is too large to review or support as one change. It has 16 packages, about 320,000 lines, and 179 public types in Core alone.
  • Several claims in the description do not hold:
    • The description claims 98% coverage. SonarCloud reports 66.8%.
    • The description says "seven packages". The PR has 16.
    • Encrypted records "fail closed on tampering", but restoring an older copy of the file is not detected.
    • Exactly-once delivery does not hold after five minutes offline.

Fix before merge

  1. Four packages are missing from ReactiveUI.Primitives.slnf, so the release never packs them.
  2. Merges of the conflict-free replicated data types:
    • They depend on argument order when stamps tie.
    • A hidden 4,096-element limit stops them from converging.
    • The list of removed tags is never pruned.
  3. The server forgets operations after five minutes, so a device that is offline longer can have an operation applied twice.
  4. The Mobile package never builds its Android, iOS, Mac Catalyst or Windows targets.
  5. The SQLite store rejects the normal iOS app-data path, because /var is a symbolic link.
  6. A stable release pack fails on the net11 preview dependencies.

Direction

  • SQLite: keep one long-lived connection per store and prepare each statement once, the way Akavache does. Use whole-database encryption through sqlite3_key in place of the per-column code.
  • Ids: use version 7 GUIDs by default on .NET 9 and later.
  • Transports: put one neutral abstraction on the server side as well as the client. SignalR is not the favoured transport. gRPC and Message Queuing Telemetry Transport are common.
  • Reuse: use this repository's signals and sequencers, System.Threading.Channels and TimeProvider. Do not add custom subjects, queues and timers.
  • Resilience: keep the retry and circuit breaker types internal. That avoids a second public resilience framework.
  • Names: use plain English everywhere, with no shorthand. Details are inline.
  • Size: land the PR in smaller pieces. Start with Core, the runtime, one store and one transport.

Platforms

Platform Problem
iOS, macOS, Mac Catalyst The store rejects symbolic-link paths. It never sets the full disk flush that Apple hardware needs. Nothing asks for background time. Keychain items survive a reinstall, so the client sequence resets and element tags can clash. tvOS has no native SQLite build.
Android After a backup restore, the database exists without its key, so the app cannot open it. Nothing handles Doze. The path symbolic link needs checking on a device. The native libraries are 16 KB aligned, which is fine.
Linux and IoT The network-drive check always sees /. Nothing calls fsync on the folder after a rename. Wall-clock jumps on devices without a real-time clock expire or stall work. Nothing listens for network changes.
Windows File.Replace fails with no retry when antivirus or the indexer holds the file. The .NET Framework builds are never tested. The per-column encryption throws on .NET Framework.
Browser Storage is never made persistent, so the browser can evict unsynced changes. A hidden tab stops all sync. The SQLite store would hang the single WebAssembly thread.
All A client clock more than five minutes off fails for good when replay protection is on. WebSocket sessions have no liveness check.

Ahead-of-time compilation and reflection

  • The PR's own code is clean. Every JSON call uses generated type information. There is no Activator, MakeGenericType, Expression.Compile or Type.GetType(string).
  • LiteDB produces 21 trimming and ahead-of-time warnings in an app that publishes with native ahead-of-time compilation. The LiteDB adapter still marks itself as compatible.
  • Removing Microsoft.Data.Sqlite also removed its reflection lookup of the provider. It also makes a native ahead-of-time binary about 1.1 MB smaller.

SonarCloud

The quality gate fails. SonarCloud last analysed b34c6939. The analysis for f625d79c is still running, and that commit changes how coverage is imported, so the coverage figure may change.

  • Reliability rating E, from 5 open issues:
    • 3 calls that do not pass the available cancellation token: MobileSyncSession.cs:207, and BrowserLifecycleAdapter.cs lines 238 and 356.
    • 1 blocker in browserLifecycle.js:21. It is a false positive, but it still fails the gate.
    • 1 minor issue for await inside a loop.
  • Coverage on new code is 66.8%. Line coverage is 69.2% and branch coverage is 59.6%, with 11,536 lines uncovered.
    • These packages show 0%: FileSystem, LiteDb, BliteDb, IndexedDB, SignalR, WebSockets and Web.
    • The main package shows 52% and the SQLite store 63%.
  • Duplication is 2.3%, under the 3% limit.

Devil's advocate summary

  • The case for change is about the edges, not the engine.
  • Wire layer:
    • Use HTTP Message Signatures, OAuth Demonstrating Proof of Possession and Idempotency-Key in place of the custom replay headers.
    • Use CloudEvents for the envelope.
    • Use gRPC streaming or Server-Sent Events in place of long polling.
    • Add a Message Queuing Telemetry Transport adapter for IoT, and drop the SignalR carrier that tunnels HTTP.
  • Metered links: base64 inside JSON with no compression is the biggest avoidable cost. A binary content type plus Brotli or GZip fixes it without touching the engine.
  • Observability and options:
    • Leave sampling to OpenTelemetry, and use IMeterFactory and semantic-convention names.
    • Log exceptions with [LoggerMessage].
    • Bind options from configuration and validate them on start.
  • Package shape:
    • Ship SQLite as the one supported store, plus a packable store conformance kit for third parties.
    • Let Mobile and Web accept a store instead of forcing one.

#endif

/// <summary>Creates operation identifiers from random GUIDs.</summary>
internal sealed class GuidOperationIdSource : IOperationIdSource

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.

Operation ids use random version 4 GUIDs. On .NET 9 and later, please use Guid.CreateVersion7() and make it the default.

A version 7 GUID starts with a timestamp, so new ids sort in the order they were created. SQLite then adds each new key at the end of the index instead of at a random page. That means fewer page writes per commit and less flash wear on devices. Ids that sort by time also make logs easier to read.

On .NET 8 and .NET Framework, keep version 4 or add a small version 7 generator. Keep UseOperationIdSource so users can opt out. The same applies to SubscriptionId.New() and to the event, batch and lease ids that call Guid.NewGuid().

while (_tail.Length > 0)
{
var tail = string.Empty;
var result = Database.Run(() => raw.sqlite3_prepare_v2(Database.Handle, _tail, out _handle, out tail));

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.

Thanks for moving to raw SQLitePCLRaw. Removing Microsoft.Data.Sqlite also removes its reflection lookup of the provider, which is good for ahead-of-time compilation.

The new layer prepares every statement on every call. There are 126 CreateStatement() call sites, each followed by SetSql, and each statement is finalized after one use. The connection is also opened per operation: the server does new SqliteDatabase(_databasePath) on every journal call (SqliteServerCommitJournal.cs:737). So no prepared statement survives long enough to be reused.

Akavache's pattern in src/Akavache.Sqlite3/SqlitePclRawConnection.cs is the target:

  • One long-lived connection per store, owned by the existing single worker thread.
  • Prepare each statement once into a fixed slot. All 176 SQL strings in this PR are constants, so this is straightforward.
  • After each use, call sqlite3_reset and sqlite3_clear_bindings. Finalize every statement on close.
  • Use sqlite3_exec for the strings that hold more than one statement.

The Database.Run(() => ...) lambda on this line also allocates a closure per prepare.

Check(raw.sqlite3_extended_result_codes(_handle, 1));
SetCancellation(cancellationToken);
SetBusyTimeout(_busyTimeoutMilliseconds);
if (password is not null)

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.

This layer supports sqlite3_key and sqlite3_rekey, but no store passes a password. The per-column encryption code stays in place: SqliteRecordProtection, SqliteRecordCipher, SqliteRecordContext and the SqliteProtected* files.

With SQLite3 Multiple Ciphers, the key encrypts the whole database file, and its ciphers authenticate every page. That covers what the per-column code does. It also covers the table names, ids and timestamps that the per-column code leaves as plain text. About 2,250 lines of record protection code can then go. Key rotation becomes sqlite3_rekey.

It also fixes .NET Framework. The per-column code throws PlatformNotSupportedException there (SqliteRecordProtection.cs:96), but the package ships net462 to net481.

Neither design detects someone restoring an older copy of the whole file. That needs a counter kept outside the database, for example by the key provider.


/// <summary>Reports a native SQLite failure without exposing a database provider.</summary>
[DebuggerDisplay("{Message,nq}; SQLite={SqliteExtendedErrorCode}")]
public sealed class SqliteDatabaseException : DbException

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.

Core is storage-neutral, but this public type is named for SQLite and derives from the ADO.NET DbException. Every user of Core gets a SQLite exception in their API, even with the LiteDB, file or IndexedDB store. It fits better in the SQLite storage package, or as a storage-neutral LocalStoreException in Core with the SQLite result code as a property.

</ItemGroup>

<ItemGroup>
<PackageReference Include="LiteDB" />

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.

LiteDB 5.0.21 produces 21 trimming and ahead-of-time warnings when an app publishes with native ahead-of-time compilation. LiteDB builds property accessors with Expression.Compile and MakeGenericType. On iOS and in native ahead-of-time apps, compiled expressions fall back to a slow interpreter, and generic types over value types can fail at run time.

This package still marks itself as trimmable and ahead-of-time compatible. The library build cannot see inside LiteDB, so it reports no warnings. The adapter only uses BsonDocument and FindById, so it works today. Users who publish with ahead-of-time compilation get all 21 warnings anyway.

public bool IncludeHashedIdentifiers { get; init; }

/// <summary>Gets the proportion of activities selected for sampling.</summary>
public double ActivitySamplingRatio { get; init; } = DefaultActivitySamplingRatio;

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.

Devil's advocate: A library should create activities and leave sampling to the app's OpenTelemetry sampler. This option drops 90 percent of traces by default, even when the app wants them all. The metrics use System.Diagnostics.Metrics, which is right. Even so, new Meter(...) in OccasionallyConnectedMetrics.cs line 35 should take an IMeterFactory when one is available. Instrument names and units should follow OpenTelemetry semantic conventions: seconds, not "milliseconds", and a prefix that matches the meter name.

.AddSingleton(configuration)
.AddSingleton<IValidateOptions<OccasionallyConnectedServiceOptions>>(
static _ => new OccasionallyConnectedServiceOptionsValidator())
.Configure<OccasionallyConnectedServiceOptions>(options =>

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.

Devil's advocate: The core option records validate themselves without Microsoft.Extensions.Options. That is correct for a core with no container. This adapter only copies options from code. It has no IConfiguration binding and no ValidateOnStart, so a bad value fails when the context is first resolved, not when the app starts. The validator turns the first error into one message, so you fix one error per run. AddOptions<T>().Bind(section).ValidateOnStart() with the [OptionsValidator] source generator would report every error at startup and let you set limits in appsettings.json.

namespace ReactiveUI.Primitives.OccasionallyConnected.Mobile;

/// <summary>Reports network availability hints, not proof that a remote peer is reachable.</summary>
public interface IMobileConnectivityHint

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.

Devil's advocate: Connectivity has a different shape on each platform. Mobile uses an EventHandler interface, Web uses an enum, and desktop and IoT have nothing. One IObservable connectivity signal in Core, with adapters for MAUI IConnectivity, System.Net.NetworkInformation.NetworkChange and the browser's online and offline events, would serve every target the same way. The PR targets battery-powered, metered links, but no code reads IConnectivity.ConnectionProfiles (Wi-Fi or cellular) or Battery.EnergySaverStatus. A metered flag on that signal would let the engine hold back large uploads.


namespace ReactiveUI.Primitives.OccasionallyConnected;

/// <summary>Specifies local store capabilities that may be advertised after conformance testing.</summary>

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.

Devil's advocate: This enum says stores advertise capabilities after conformance testing. The repository has a conformance suite for transports only, not for stores. The SQLite store has about 16,800 lines of code and 21,600 lines of tests. The LiteDB, BLite and IndexedDB stores have 1,500 to 2,000 lines each, with 560 to 710 lines of tests. They still advertise the same AtomicLocalCommit and DurableLocalCommit guarantees. For a "no message may be missed" goal, I would ship one strong SQLite store plus a packable ILocalStoreAdapter conformance kit. Third parties, or later PRs, can then write and prove the LiteDB, BLite and IndexedDB adapters.

<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls.Core" />
<PackageReference Include="Microsoft.Maui.Essentials" />
<ProjectReference Include="..\ReactiveUI.Primitives.OccasionallyConnected.Storage.Sqlite\ReactiveUI.Primitives.OccasionallyConnected.Storage.Sqlite.csproj" />

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.

Devil's advocate: The Mobile package forces the SQLite store, and the Web package forces the IndexedDB store. A platform adapter should not choose your storage. Sixteen packages is a lot to version and support from day one. A leaner first release could be Core, the runtime, Storage.Sqlite, Transport.Http, Server, and thin dependency injection, Splat, Mobile and Web adapters that take any ILocalStoreAdapter. The .Reactive twin recompiles all 168 runtime files to change the namespace and one disposable call. That follows this repository's twin pattern, but a small System.Reactive adapter could replace a second full copy.

ChrisPulman and others added 22 commits October 1, 2026 07:39
…explicit drains

Keep lifecycle workers and shutdown drains deliberately uncancellable with explicit CancellationToken.None arguments. Replace the JavaScript await loop with one owned delivery pulse and one coalesced pending hint, preserving serialized delivery, disconnect recovery and disposal without retaining an expanding promise chain.

Isolate the existing real-thread ReplaySignal deadlock probe from competing fixtures while retaining both dedicated threads and its five-second guard. Sonar now collects feature coverage after a successful build even when an unrelated test suite fails; any failed test still fails the job.

Validated all 38 Web and 106 Mobile TUnit cases plus the complete net10 foundation suite with 1652 passing tests, normal analyzers and fresh coverage. No suppressions or quality-gate changes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…rash guards

Run the two failing real SQLite admission fixtures and producer crash families apart from unrelated database work. The existing crash group prevented concurrent crash cases but still competed with other cold SQLite fixtures. Keep original five-second admission guards, child deadlines, all strategies and durable reopen assertions unchanged.

Verified the complete Release net10 runtime suite with a two-CPU budget and fresh TUnit coverage: 1692 passed and four existing capability skips.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the manual clock advancing while the lost-ACK observer waits for remote and local convergence. A parked HTTP subscription can time out before the writer releases the host gate; its receive retry may be registered after the one-time writer retry advance. Freezing manual time then prevents observer convergence indefinitely.

Preserve the existing whole-scenario deadline, HTTP timeout, retry policy, durable proofs and cleanup error aggregation. Add deterministic regressions for a late receive timer and cancellation. Complete net10 resilience suite: 113 tests passed with normal analyzers and fresh coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the cross-stream SQLite acknowledgement/admission test apart from unrelated database fixtures during profiled runs. Preserve its two streams, blocked publisher, real durable acknowledgement and five-second guards. Full Release net10 runtime validation passes: 1692 tests and four existing capability skips with fresh coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ble lanes

Wait until the virtual retry timer is registered before advancing manual time in the disposed-session retry test. Persisting retry state does not prove that the scheduler has installed its timer.

Run the strict real SQLite capacity-release and disposal-drain fixtures apart from unrelated database work. Keep their blocked producers, durable receipts and original guard deadlines unchanged.

Full Release net8 runtime validation with two CPUs and fresh TUnit coverage: 1692 passed, four existing capability skips, no warnings.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Run the real Node event bridge fixture apart from competing lifecycle fixtures during profiled execution. Keep its 15-second process guard, shipped JavaScript assertions, both target frameworks and complete listener/disposal checks unchanged. Both Web suites pass: 38 tests, normal analyzers and fresh coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…elease blockers

Restore complete release-filter membership and distinguish stable package assets from experimental net11 assets without suppressing package warnings. Verify actual stable and prerelease release-filter packing and complete consumer dependency closure.

Make equal-stamp CRDT merges deterministic, align collection bounds, and add explicit causal OR-set checkpoints that retain permanent caller-proven retired frontiers. Preserve legacy payload bytes for states without checkpoints.

Retain server operation replay results for thirty days by default and let subscriber history expire independently without deleting idempotency proofs. Persisted exactly-once first-attempt anchors stop lost-ACK operations before resending after their window expires, including restart after days offline.

Build and stage all supported Mobile native heads before final release signing. Bind database ownership to canonical paths and secure installation identities; fail closed for missing keys or installation markers. Preserve .NET Framework path safety and compatibility.

Scope authenticated record encryption claims accurately: malicious whole-file rollback or arbitrary deletion requires an independent checkpoint outside the database. Add regression evidence for that boundary instead of promising unsupported freshness protection.

Keep WebSocket event lanes bounded while allowing concurrent ACK progress, and propagate sticky receive failures to current and future requests.

Independent reviews found and verified corrections for existing-subscription receive expiry and missing installation marker recovery. Complete feature coverage gate, normal referenced suites, stable and prerelease twenty-package gates, determinism, Source Link, and all eight clean-consumer targets pass locally. Apple-inclusive native package verification remains a required CI gate.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…very

Native workload jobs use the supported .NET 10 SDK with explicit preview language syntax required by the repository's existing collection-expression arguments. Replace inline PowerShell orchestration with Bash dotnet commands and a tested dotnet-run SDK selector.

Serialize mount enumeration across SQLite owners. The .NET 8 macOS native implementation uses getmntinfo's shared buffer; concurrent calls caused an AccessViolation in the required conformance run. Preserve longest-mount network checks and add a concurrent independent-owner regression.

Use structured net11 process exit status in new junction fixtures, including cancellation and signal assertions, while retaining older target APIs.

Model the existing late-ACK registration test as an irreversible send whose response arrives after cancellation, rather than a cancellable before-send pause. Drive observed virtual upload wakes after the recovered retry due time so asynchronous merge callbacks cannot leave the test clock frozen. Preserve guard deadlines and all ordering/retention assertions.

Verified Windows/Android native packing with SDK 10.0.301, all 64 Mobile tests against the exact native package/version/commit, net11 ownership helpers, and the complete net8 runtime suite (1704 passed, four capability skips). SDK selector fixture and workflow Bash syntax checks pass.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Build Windows resources on Windows rather than executing MakePri.exe on macOS. The Apple job builds Android, iOS and Mac Catalyst assets; a dependent composition job imports the matching Windows native assets and symbols into one unsigned complete package before the existing final signing/publication gate.

Validate exact package identity, version and repository commit, reject signed or missing inputs, bound archive counts/bytes, retain normalized NuGet framework folders and Windows PRI content-type metadata, and produce deterministic archives. No partial or fallback artifact is published.

Add six TUnit regression cases for four-head asset/symbol preservation, Windows dependencies/resources, mismatched version/commit, missing symbols, signed inputs and deterministic output. All pass with normal analyzers. Workflow Bash syntax and supported-host dependency checks pass. Native CI still proves the actual Apple-inclusive complete artifact.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Move file-app local functions into explicit static PackageInspector methods so the compiler-generated entry point no longer aggregates the full inspector into a zero-maintainability method. Preserve every inspection, output and nonzero failure result; do not suppress CA1505.

Verified the exact CI composition and verify-native commands against real Windows and macOS artifacts from run36913014819. The complete package contains neutral net10 plus Android, iOS, Mac Catalyst and Windows assets, matching symbols, exact source/version, deterministic debug identity and resolvable Source Link. All checks passed with normal analyzers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Run the three custom overflow fixtures with TUnit NotInParallel, matching the existing durable overflow fixture. Windows net8 coverage exposed simultaneous five-second publication guards while unrelated synchronous SQLite fixtures occupied the worker pool. Keep every assertion and guard unchanged and document the resource isolation.

The complete net8 runtime suite passes with coverage and two logical processors: 1710 passed, four capability skips, zero failures. Runtime coverage remains 98.95% lines and 97.87% branches.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…olls

Release the completed publish's active-call slot before signaling subscription polls. Signal under the lifecycle gate before completing the disposal drain so the semaphore cannot be closed between release and wakeup. Failed publishes still do not signal.

Linux net11 CI exposed the previous race: an idle poll woke while the successful publish still held the sole slot and terminated with QueueCapacityExceededException. Keep the original regression and capacity limits unchanged.

Full net11 Server coverage suite passes: 567 tests, zero skips or failures. ReleaseActiveCall has 100% line and branch coverage across publish, failure and disposal paths.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep all four independent owners concurrent but isolate the mount-discovery regression from unrelated synchronous database fixtures using TUnit NotInParallel. Cancel and drain its workers before removing the temporary directory, including when the unchanged guard fails, so Windows cleanup cannot mask the original timeout with a sharing violation.

The exact native SDK10 full SQLite coverage suite passes with two logical processors: 576 tests, three unavailable symlink-privilege skips, zero failures. Native CI uses the same supported ownership checks without retries or increased guards.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Isolate the virtual-clock deferred-recovery fixture from unrelated synchronous SQLite work, as for the existing recovered retry/merge fixture. Sonar's instrumented Windows run timed out only after the recovered batch had been sent, while waiting for its durable acknowledgement commit. Keep pre-start deferral, operation ordering, synchronized-state assertion and the original guard unchanged.

Full runtime net10 coverage passes with two logical processors: 1710 tests, four capability skips, zero failures.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Replace the builder fixture's CPU-count limiter with TUnit NotInParallel. Limiting thread-pool concurrency does not bound competition between the dedicated synchronous SQLite workers used by these cold initialization and durable commit fixtures. Windows coverage demonstrated simultaneous first-publication and startup timeouts with no pending thread-pool work, so per-method isolation was insufficient.

Preserve every guard, assertion and internally concurrent producer, cancellation, reconnect and recovery scenario. Isolate the fixture at its actual resource boundary rather than continuing to special-case individual failing tests.

The full net8 runtime coverage suite passes with two logical processors: 1710 tests, four capability skips, zero failures, under38 seconds.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove the reserve-and-release TCP port race exposed by macOS net10 coverage. Both the in-process runner and real executable now request loopback port0 and observe the actual bound endpoint instead of reopening a supposedly free port.

Share application creation/ownership in the existing runner without changing public signatures or asynchronous exception behavior. Observe in-process ApplicationStarted and child host lifetime output, retain the health, database, cancellation and disposal assertions, and keep one original ten-second readiness deadline across endpoint discovery and health probing. Refresh all four example API baselines for the required inlining attribute.

Complete net10 Collaboration.Server TUnit coverage: 138 tests passed, zero skips or failures. No retries, suppressions or increased deadlines.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove unrelated automatically timed cancellation from four replay completion fixtures. Windows net8 instrumentation delayed their continuations until the one-second caller token expired, so the duplicate completed from cancellation before the session-registration or owner-disposal action being tested.

Use uncancelled duplicate admission, matching the adjacent lifecycle fixtures, while retaining the exact50ms pending observation and1000ms completion guards. The owned coordinator still drains waiters on failure; explicit caller-cancellation tests are unchanged. This makes the assertion prove owner/session behavior instead of racing an unrelated token timer.

Full net8 HTTP coverage:970 tests passed. HttpReplayCoordinator remains100% line and branch coverage, including CancelWaiter and atomic-owner-close failure paths. No increased guard, suppression or retry.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the browser bridge test's original15-second exit deadline. Do not cancel its output drains at the same moment as the process wait: kill the owned process if necessary and drain both streams before disposing its handle. Report a timeout with process-exit and stream-task states instead of losing evidence in a generic TaskCanceledException.

Windows net10 CI exposed a Node process wait exceeding the deadline; the JavaScript assertions and shipped bridge remain unchanged. This improves cleanup and diagnosis without hiding the timeout, retrying Node or increasing its test deadline.

Full Web net10 TUnit coverage:19 tests passed. BrowserLifecycleAdapter remains100% line and branch coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Run the durability cancellation signal on a joined dedicated thread, matching the adjacent real writer-lock fixture. Signal entry, retain the original100ms cancellation delay, and cancel without depending on a thread-pool timer while the test synchronously waits for SQLite.

Windows net8 instrumentation delayed the old timer until the unchanged five-second writer deadline expired, producing TimeoutException instead of the expected OperationCanceledException. Preserve that exact cancellation assertion and every production deadline; join the signal worker even when the assertion fails.

Freshly rebuilt full net8 SQLite TUnit coverage passes:576 tests, three unavailable symlink-privilege skips, zero failures or build warnings. No retries, suppressions or deadline increases.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ates

Run the durable SQLite performance measurement in runner.temp rather than the operating-system profile's default temp directory. Set TMP, TEMP and TMPDIR only for the performance step, and include the actual database path in the result so hosted storage differences are visible.

Keep FULL synchronous writes, all512 offline operations, the10 commits/second floor, allocation limits and15-second recovery/compaction budgets unchanged. Document the measurement location. Do not introduce the separately deferred connection-pooling work.

Windows hosted performance reported5.0 commits/second in the default location. The explicit measurement passes locally in temporary work storage with the unchanged budgets. All other completed current-head gates, including Sonar analysis/quality gate, passed; new CI must verify the hosted work-volume result.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Reuse the existing manual receive-clock driver while waiting for the reopened writer's durable synchronization proof, not only while waiting for the independent observer. An upload ACK can synchronize its operation before a parked receive poll or retry stores the cursor; freezing shared time at that point strands receive progress until the scenario deadline.

Keep all persisted cursor, snapshot, pending-count, idempotency and recovery predicates and the original scenario deadline. Preserve proof exceptions through the clock driver and document both clock-driven convergence phases.

Complete net9 ResilienceLab TUnit coverage:114 tests passed, including both real lost-ACK recovery and cleanup-error propagation. Added a failed durable-proof propagation regression. IsDurablySynchronized remains100% line and branch coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@sonarqubecloud

sonarqubecloud Bot commented Oct 3, 2026

Copy link
Copy Markdown

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants