Skip to content

Enable running Client integration specs across configurable runtimes/databases and stabilize SQL-backed integration CI#2269

Open
Copilot wants to merge 310 commits into
mainfrom
copilot/enable-integration-specs-different-setups
Open

Enable running Client integration specs across configurable runtimes/databases and stabilize SQL-backed integration CI#2269
Copilot wants to merge 310 commits into
mainfrom
copilot/enable-integration-specs-different-setups

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Nov 17, 2025

Summary

Client integration specs run from a single Integration/Client project with configurable runtime/database modes, and CI executes them through a reusable matrix. This update also corrects SQL-backed startup regressions introduced during merge drift so matrix runs are stable across SQL providers.

Added

  • Runtime mode selection for Client integration execution: inprocess or outofprocess
  • Database provider selection for integration execution: mongodb, sqlite, postgresql, or mssql
  • Reusable integration workflow (.github/workflows/integration.yml) invoked from dotnet-build.yml
  • Local run documentation for CLI and VS Code configurations in Documentation/contributing/integration-tests.md
  • Development-only kernel reset endpoint for integration test isolation
  • Migrated stream completion and nested projection integration specs into Integration/Client
  • Added PDL nested projection compilation specs in Integration/Client

Changed

  • Consolidated client integration specs into Integration/Client and renamed the project to Client
  • Removed separate DotNET.InProcess integration project from active test execution
  • Default runtime behavior with no arguments is outofprocess + mongodb
  • Runtime/database selection is passed through CI via CHRONICLE_RUNTIME_MODE and CHRONICLE_STORAGE_PROVIDER
  • Integration namespace discovery includes only namespaces containing runnable tests
  • CI integration jobs include explicit restore before test execution

Fixed

  • SQL-backed integration startup failures that caused broad matrix regressions after merges
  • SQL migration/schema mismatches affecting constraints and closed-stream storage on SQL providers
  • SQL cluster startup patch execution failure caused by concept-cast query projection during event store lookup
  • Build and analyzer breakage introduced by merge drift in affected SQL/auth-related files

Removed

  • Legacy Integration/DotNET.InProcess test paths that were no longer part of the consolidated client integration layout

Security

  • Development-only kernel reset endpoint remains restricted to Development builds/images to avoid production exposure

Deprecated

  • No additional deprecations in this PR

Copilot AI changed the title [WIP] Enable running integration specs for different setups Enable running integration specs across multiple test environments Nov 17, 2025
Copilot AI requested a review from einari November 17, 2025 07:46
@github-actions
Copy link
Copy Markdown
Contributor

Docker Image for this PR:
https://ghcr.io/cratis/chronicle:14.10.3-pr2269.30a1af2

docker run ghcr.io/cratis/chronicle:14.10.3-pr2269.30a1af2

@github-actions
Copy link
Copy Markdown
Contributor

NuGet packages for this PR, e.g. Chronicle:
https://github.com/Cratis/Chronicle/pkgs/nuget/Cratis.Chronicle

Use version: 14.10.3-pr2269.30a1af2

@github-actions
Copy link
Copy Markdown
Contributor

Docker Image for this PR:
https://ghcr.io/cratis/chronicle:14.10.3-pr2269.7acaf2a

docker run ghcr.io/cratis/chronicle:14.10.3-pr2269.7acaf2a

@github-actions
Copy link
Copy Markdown
Contributor

NuGet packages for this PR, e.g. Chronicle:
https://github.com/Cratis/Chronicle/pkgs/nuget/Cratis.Chronicle

Use version: 14.10.3-pr2269.7acaf2a

@github-actions
Copy link
Copy Markdown
Contributor

Docker Image for this PR:
https://ghcr.io/cratis/chronicle:14.10.3-pr2269.c40693a

docker run ghcr.io/cratis/chronicle:14.10.3-pr2269.c40693a

@github-actions
Copy link
Copy Markdown
Contributor

NuGet packages for this PR, e.g. Chronicle:
https://github.com/Cratis/Chronicle/pkgs/nuget/Cratis.Chronicle

Use version: 14.10.3-pr2269.c40693a

@einari
Copy link
Copy Markdown
Contributor

einari commented Feb 13, 2026

@copilot Pull main into this branch and fix any merge conflicts

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 13, 2026

@copilot Pull main into this branch and fix any merge conflicts

I've merged main into this branch and resolved the conflicts. The merge commit is 8cbf7ee.

Key changes from the merge:

  • Updated .NET versions in workflow (DOTNET8, DOTNET9, DOTNET10)
  • Removed framework-specific filters to support multi-targeting
  • Updated package versions from main
  • Removed AggregateRoots specs that were deleted in main
  • Resolved file location conflicts

There are some remaining namespace issues in the Projections specs that are causing build errors. These are from differences in how files were organized between our branch and main. I'll continue working on fixing these.

@einari
Copy link
Copy Markdown
Contributor

einari commented Feb 14, 2026

@copilot There are still some build errors due to projects that has been removed - just remove the reference to these.

@github-actions
Copy link
Copy Markdown
Contributor

Docker Image for this PR:
https://ghcr.io/cratis/chronicle:15.2.2-pr2269.294d3f2

docker run ghcr.io/cratis/chronicle:15.2.2-pr2269.294d3f2

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 14, 2026

@copilot There are still some build errors due to projects that has been removed - just remove the reference to these.

Fixed all build errors. The issues were:

  1. SomeReadModel had incorrect namespace (now uses Integration.Specifications)
  2. InProcess-specific projection specs were trying to reference non-existent Specifications namespaces (now correctly reference local InProcess.Integration namespaces)
  3. Missing using statements for shared given namespace
  4. Remaining ChronicleInProcessFixture references that should be ChronicleFixture

The DotNET.InProcess project now builds successfully. Commit: 5eeedba

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 8, 2026

Docker Image for this PR:
https://ghcr.io/cratis/chronicle:15.3.3-pr2269.366afa5

docker run ghcr.io/cratis/chronicle:15.3.3-pr2269.366afa5

@einari
Copy link
Copy Markdown
Contributor

einari commented Mar 23, 2026

@copilot Merge & rebase from main. Fix any conflicts.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 23, 2026

@copilot Merge & rebase from main. Fix any conflicts.

Merged and resolved conflicts from main. Commits: 3f99837 (merge), d6ec131 (namespace fixes).

Key changes:

  • Resolved workflow conflicts by keeping dynamic namespace discovery
  • Moved new projection types and specs from main to Specifications folder
  • Updated all namespaces to Integration.Specifications pattern
  • Fixed ChronicleFixture usage in new EventSequence specs

One known issue remains: DotNET.InProcess/for_JobsManager/when_cleaning_up_dead_jobs references a given context that exists in the Kernel project but not in DotNET.InProcess.

@github-actions
Copy link
Copy Markdown
Contributor

Docker Image for this PR:
https://ghcr.io/cratis/chronicle:15.11.0-pr2269.d6ec131

docker run ghcr.io/cratis/chronicle:15.11.0-pr2269.d6ec131

@einari
Copy link
Copy Markdown
Contributor

einari commented Apr 3, 2026

@copilot Merge main into this and fix any merge conflicts.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 3, 2026

@copilot Merge main into this and fix any merge conflicts.

Merged main into the branch and resolved all 146 conflicts. Key changes:

  • Kept our dynamic namespace discovery in dotnet-build.yml, added main's new mongodb job, removed the push trigger main dropped
  • Took main's newer package versions (OpenTelemetry 1.15.1, Meziantou.Analyzer 3.0.44, Swashbuckle 10.1.7, EntityFramework packages)
  • Resolved rename/delete conflicts: files moved to Specifications stay there, ChildWithId.cs corrected in Source
  • Fixed build errors from the merge: replaced obsolete Cratis.Applications.* packages with Cratis.Arc.* in Kernel.csproj, added Polly.Core to Directory.Packages.props, added using Cratis.Chronicle.Jobs to specs calling WaitForThereToBeNoJobs, fixed Kernel when_starting namespace references, and moved a_job_stuck_in_preparing_state.cs to the Kernel project (it uses Kernel-specific APIs)

Both DotNET.InProcess and Kernel integration projects build with 0 errors. Commits: 2a4f93c (merge), 6363a13 (build fixes).

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jun 2, 2026

@copilot Massive regressions after merging main into this branch: https://github.com/Cratis/Chronicle/actions/runs/26801258184/job/79009144429?pr=2269

Addressed in cd7230f2f and dde0b5bc0.

I pulled the failing job logs and fixed the merge-drift regressions that were breaking SQL-backed integration startup:

  • restored cluster/auth SQL shape to align with mainline merge expectations
  • fixed SQL migration schema mismatches (Constraints and ClosedStreams) that caused startup failures across SQLite/PostgreSQL/MSSQL
  • fixed ClusterStorage.GetEventStores() to avoid EF concept-rewrite failure during RenameReactors patch startup (EventStoreName cast is now done after materialization)

Validation run:

  • dotnet test Source/Kernel/Storage.Sql.Specs/Storage.Sql.Specs.csproj -c Release
  • dotnet build -c Release -p:DisableProxyGenerator=true -p:DisableDockerBuild=true

Copilot AI changed the title Enable running Client integration specs across configurable runtimes, databases, and resilient CI matrices Enable running Client integration specs across configurable runtimes/databases and stabilize SQL-backed integration CI Jun 2, 2026
Copilot finished work on behalf of einari June 2, 2026 07:10
@einari
Copy link
Copy Markdown
Contributor

einari commented Jun 2, 2026

@copilot Things are still failing miserably - mega regression - don't report back until you got this sorted: https://github.com/Cratis/Chronicle/actions/runs/26804323290/job/79019027698?pr=2269

Copilot stopped work on behalf of einari due to an error June 2, 2026 08:18
@einari einari force-pushed the copilot/enable-integration-specs-different-setups branch from dde0b5b to 4170459 Compare June 2, 2026 08:31
einari added 21 commits June 2, 2026 10:43
EventStores.Name, Reminders.Id, DataProtectionKeys.Id, and Patches.Name
were declared without a maxLength. On SQL Server that emits NVARCHAR(MAX),
which cannot be used as a key column in an index — the primary key
constraint creation failed with SQL error 1919, taking down silo startup
for every SQL Server integration job.

Apply maxLength: 200 and nullable: false to those columns. The fix
existed on the branch before the merge of main and was lost during
conflict resolution; this restores it on the current merge result.
SQL Server support is not yet publicly released, so there are no real
deployments running v15_0_0 in production that would need a separate
v15_1_0 to evolve from. Collapse all cluster table creation into one
migration to reduce maintenance overhead and make the initial schema
trivial to read.

Tables merged into v15_0_0: Users, Applications, DataProtectionKeys,
Patches, SystemInformation. Down() drops them in reverse order.
Add a discover-implementations skill and a DI rule that codify the
pattern from the IReactorSideEffectHandlers fix: inject
IInstancesOf<TInterface> to enumerate all implementations of an
abstraction, mark each implementation with [Singleton] (or rely on the
IFoo → Foo convention for transients), and delete the
services.AddSingleton<TInterface, Impl>() registrations.

IEnumerable<TInterface> for discovery requires hand-maintained
registrations everywhere — new implementations silently do nothing
until someone remembers to register them, removed types leave dead
registrations, and spec setups must mirror production. IInstancesOf<T>
pushes that knowledge into the implementation itself.
The previous merge of origin/main inadvertently reverted the OpenIddict
SQL storage layer to its pre-branch state. Restore it: token,
authorization, and scope entities; matching IOpenIddictTokenStore /
AuthorizationStore / ScopeStore implementations; DbSet wiring on
ClusterDbContext; and Tokens / Authorizations / Scopes table creation
folded into the consolidated Cluster v15_0_0 migration.

This sits alongside main's own user/application/data-protection-key
work — they are complementary auth storage concerns, not duplicates.
…ict keys

The compile-time #if DEVELOPMENT branch tied ephemeral key generation
to the build configuration. That made it impossible to run a Release
build against a local Development environment for testing, and it
fell back to throwing when no certificate was configured even on a
genuine development machine.

Replace the preprocessor branch with a runtime check against the
standard DOTNET_ENVIRONMENT / ASPNETCORE_ENVIRONMENT variables. Same
production behavior (throw without a certificate), better local
ergonomics.
…e change

ReactorSideEffectHandlers' constructor now takes IInstancesOf<T>
instead of IEnumerable<T>. Wrap the test-side handler arrays in
KnownInstancesOf<IReactorSideEffectHandler> at every call site —
EventStoreForTesting plus the seven ReactionInvoker invocation specs.

Also drop the now-unused
'using Cratis.Chronicle.Reactors.SideEffects;'
from ChronicleClientServiceCollectionExtensions left over from the
previous registration removal.
…ation-specs-different-setups

# Conflicts:
#	Integration/Client/for_EventSequence/when_appending_event_with_pii/EventWithSubjectAndPII.cs
#	Integration/Client/for_EventSequence/when_appending_event_with_pii/many_events_with_subjects.cs
#	Source/Kernel/Core/EventSequences/EventSequence.cs
#	Source/Kernel/Storage.MongoDB/EventSequences/EventSequenceStorage.cs
#	Source/Kernel/Storage.MongoDB/Sinks/ChangesetConverter.cs
#	Source/Kernel/Storage.Sql/EventStores/Namespaces/EventSequences/EventCursor.cs
#	Source/Kernel/Storage.Sql/EventStores/Namespaces/EventSequences/EventEntryConverter.cs
#	Source/Kernel/Storage.Sql/EventStores/Namespaces/EventSequences/EventSequenceStorage.cs
#	Source/Kernel/Storage.Sql/Sinks/Sink.cs
Two files came in via the merge of main that referenced types or call
signatures the branch no longer has:

- many_events_with_subjects.cs (PII / subject append spec) was added in
  main at Integration/DotNET.InProcess/...; git moved it into the
  Integration/Client/... folder during the merge but the namespace
  still pointed at Cratis.Chronicle.InProcess.Integration and the
  fixture was ChronicleInProcessFixture, neither of which exists in
  Integration/Client. Switch namespace to Cratis.Chronicle.Integration
  and fixture to ChronicleFixture, matching the sibling an_event spec.
- EventWithSubjectAndPII.cs likewise used the InProcess namespace —
  align it with the Client folder's namespace.
- Storage.Sql.Specs Sink spec's CreateContext and ReadModelTable mock
  setup were missing the IReadOnlyList<ProjectedColumn> columns
  parameter the branch added to ReadModelDbContext and IDatabase
  earlier on the branch. Pass an empty list.
…onvention

EventStore.GetRequiredService<IReactorSideEffectHandlers>() failed at
runtime with 'Cannot dynamically create an instance of type ...
IReactorSideEffectHandlers. Reason: Cannot create an instance of an
interface' for every ChronicleClient created without a host service
provider — integration fixtures included, where each test reset went
through this path.

The previous code path worked because the type was registered
explicitly with services.AddSingleton<IReactorSideEffectHandlers,
ReactorSideEffectHandlers>(). The branch removed that registration as
part of switching to IInstancesOf<T> for implementation discovery, so
when no host DI container is in play DefaultServiceProvider had
nothing to fall back to and Activator.CreateInstance on an interface
threw.

Teach DefaultServiceProvider the same conventions a real container
honors:

- IInstancesOf<TInterface> → InstancesOf<TInterface>(Types.Instance, this)
- IFoo → Foo by namespace, instantiated via its widest constructor
  with parameters recursively resolved through the same provider

This restores the discovery model the IInstancesOf rule promises:
adding a new implementation no longer requires editing a composition
root, and falling back to DefaultServiceProvider gives the same
behavior as the host DI container would.
The previous attempt put convention-based resolution into
DefaultServiceProvider — that solved the symptom but leaked
DI-container behavior into a stub provider and would have to grow
every time another consumer hit the same shape. Revert it.

Instead, mirror the existing ComplianceMetadataResolver pattern in
ChronicleClient: construct ReactorSideEffectHandlers eagerly with
new InstancesOf<IReactorSideEffectHandler>(Types.Instance,
_serviceProvider) and pass it to EventStore as an explicit
constructor parameter. The EventStore no longer pulls
IReactorSideEffectHandlers off the service provider at all, so the
DefaultServiceProvider fallback path never has to know about it.
…I spec

Two unrelated regressions came in with the merge of main and only
surfaced once the spec matrix actually exercised them on the branch.

ConstraintsStorage.GetDefinitions / SaveDefinition translated
.OrderByDescending(_ => _.Version) down to the database — SQLite's
EF Core provider rejects ulong in ORDER BY ('SQLite does not support
expressions of type ulong in ORDER BY clauses'). Materialize the
candidate rows first, then sort client-side. Per-name version history
is small enough that this is fine, and it keeps Version a ulong in
the kernel (narrowing to long would change semantics, casting per
provider would diverge).

many_events_with_subjects PII spec was authored on main against the
DotNET.InProcess project (MongoDB only) and ran fine there. When the
merge moved it into Integration/Client it started running across the
full provider × runtime-mode matrix, and its Mongo-specific reads
(GetCollection<BsonDocument>, MongoDBContainer.GetMappedPublicPort)
fail on every non-Mongo leg — the kernel container has no embedded
mongod. Gate the at-rest verification (storage layout, encryption
keys) on the MongoDB backend; keep the storage-agnostic facts (tail
sequence number, round-trip decryption via EventLog) running
everywhere.
The spec came in via main against the pre-typed-column ReadModelDbContext
(no ProjectedColumn list). When I rebuilt it for the branch's signature
in 2c22fc5 I passed an empty list, which compiles but blows up at
runtime: 'The entity type test_read_models (DynamicReadModelEntity)
requires a primary key' — without any column, EF has no IsKey marker to
hang the PK on.

Build a JsonSchema declaring the two properties the spec writes through
(name string, roles array of object), derive the column list via the
same ProjectedColumns.ForSchema helper the production sink uses, and
feed it into both the sink (through CreateReadModelDefinition) and
ReadModelDbContext. They now agree on shape and EF is happy.

Verified locally: 4 facts pass.
ObserverFilters (EventSourceType, EventStreamType, Tags) were checked
at batch granularity: 'skip the batch only when NO event in it matches
the filter'. Whenever a batch carried at least one matching event the
remaining non-matching events flowed straight through to the
subscriber, which then dispatched them to the handler — observable as
'reactor with [EventSourceType("order")] sees the non-matching event
too' on SQL out-of-process integration runs (MongoDB tends to deliver
events one at a time, so the per-batch check happened to coincide with
the intended semantics there). The structural event-type subscription
check at the top of Handle had the same shape and the same problem.

Compute the matching subset of events up front via a new
EventMatchesSubscription helper that combines the structural
event-type check with all three ObserverFilters, and feed only the
matching events into the dispatch path. eventsToHandle now derives
from matchingEvents instead of the raw batch, so the subscriber sees
exactly the events the observer subscribed to.

To avoid re-fetching trailing filtered-out events on every roundtrip,
advance NextEventSequenceNumber past observedTailEventSequenceNumber
when every matching event in the batch succeeded; partial success
keeps the conservative result.LastSuccessfulObservation.Next() so the
next attempt retries from the first unhandled matching event.

Validated locally: all 320 for_Observer specs pass.
Replay races sibling partitions through their own events independently
of each other. When a root projection's HandleEvent step calls
AddPropertiesFrom(InitialModelState) it produced a PropertiesChanged
covering every property in the read model — including children
collections like Features. A subsequent root event whose ChildAdded
landed first would already have populated the parent document's
Features array; the root's PropertiesChanged then $set Features back
to [], wiping the children.

The sequential append path got lucky because partitions were drained
in order (root before children), but replay drives each partition
through its own job step so the writes interleave. The visible
symptom is and_projection_is_replayed of the deep-hierarchy spec:
Module.Name materialises, Module.Features is empty.

Walk the projection tree once when filling the initial-state diff
and skip every children property path the root owns. Those paths are
managed exclusively by ChildAdded / ChildRemoved across the read
model's lifetime, never by a root $set.

Verified locally: 410 Core specs + 396 Infrastructure specs pass.
f4316ab switched the dispatch path from a batch-level skip to a
per-event match check, but split the filter from the sequence-number
filter into two stages. When a batch happened to land with
matching events whose sequence numbers were all already <
NextEventSequenceNumber — re-fetches after restarts, partition-cursor
gymnastics during catch-up etc. — the new shape produced a
non-empty matchingEvents but an empty eventsToHandle, fell through
the dispatch block without entering it, and left State unchanged.
The next poll fetched the same batch and looped, never letting the
observer transition to subscribed/active. for_EventStoreSubscriptions
tenant-outbox-forwarding stalled at WaitTillSubscribed under both
inprocess+mongodb and outofprocess+postgresql because of that loop.

Compose the two filters into a single Where() so eventsToHandle is
defined as exactly the events we intend to dispatch, then early-return
with an explicit cursor advance past observedTailEventSequenceNumber
when that set is empty. The early-return covers both "no matching
event types in the batch" and "matching events are already past" with
the same forward-progress bookkeeping.

Verified locally: 320 for_Observer specs pass.
36693a6 hoisted both filter passes into a single Where() and added
an unconditional cursor advance when the resulting set was empty.
That regressed two existing for_Observer specs
(and_events_has_already_been_handled, and_subscription_is_disconnected):
when the entire batch lies before NextEventSequenceNumber the cursor
should stay put and no state write should be emitted, but the early
return was rewinding NextEventSequenceNumber to the batch tail's
Next() and writing it.

Guard the advance on observedTailEventSequenceNumber >=
tailEventSequenceNumber. The forward-of-cursor path (the case the
previous commit was protecting against — a batch whose only
forward-of-cursor events are non-matching) still advances and writes
state. A batch that lies entirely behind the cursor now exits without
touching state, matching the long-standing spec contract.

Verified locally: all 1531 Core specs pass.
f4316ab + 36693a6 + 7d47ebd fixed the partial-batch filter leak
(reactor with [EventSourceType] saw events from the wrong source) by
hoisting the filter into a single Where(), but the rewrite also
changed the state-advancement semantics — chasing the batch tail when
every matching event was handled, asymmetric handling between failure
and success — and that knocked out for_Reducers retry tests (3
scenarios x 6 facts) plus a chunk of for_EventStoreSubscriptions.
Pre-existing for_Reducers passes regressed from 67 to 56.

Strip the rewrite back to a one-line behavior change: keep all four
existing batch-level "skip if nothing matches" early-exits and the
original NextEventSequenceNumber = result.LastSuccessfulObservation.Next()
on success, but apply the same EventMatchesSubscription helper to
filter eventsToHandle before dispatching to the subscriber. Reactors
with EventSourceType / EventStreamType / Tags filters now only see
matching events; the bookkeeping for failed partitions, retries, and
catch-up paths is byte-identical to d979347.

Verified locally: 1531 Core specs pass.
…r tests

ed5a815 (Exclude children paths from root projection's initial-state
diff) was speculatively fixing the deep-hierarchy replay scenario, but
the latest CI shows the deep-hierarchy spec stayed at 6 failures and
the change picked up new regressions: for_Reducers
when_handling_event.and_it_fails (but_not_second_time + but_not_third_time)
went from passing to failing (-11 tests on outofprocess+sqlite), and
two new flake-shaped failures appeared on Projections inprocess/oop
mongodb (when_projecting_with_watcher.should_receive_same_model,
when_projecting_with_join_for_children.with_from_every.should_set...).

Pulling that change back to d979347's behavior keeps the original
deep-hierarchy bug — to be revisited separately — but stops the net
regression in for_Reducers and the Projections watcher/join paths.

Verified locally: 1531 Core specs pass.
bde819e kept what looked like a minimal Where() clause change, but
even that one-line addition was enough to regress for_Reducers
when_handling_event.and_it_fails (but_not_second_time +
but_not_third_time) on outofprocess+sqlite and to flip two passing
Projections tests (waiting_for_each_event.with_two_event_types)
into NullReferenceException territory on inprocess+mongodb. The
common shape — retry / catchup paths that depend on a specific
sequence of failures and recoveries — won't tolerate the per-event
filter without a deeper rethink that I don't have time to land
safely tonight.

Roll Observer.Handling all the way back to d979347 to clear the
regression surface for the rest of the CI matrix. The original
per-batch filter leak (reactor with [EventSourceType] still sees
non-matching events when the batch carries at least one match) is
back; that's the bug we set out to fix in f4316ab and the right
follow-up is a targeted fix at the subscriber's dispatch layer
rather than in the observer's batch handling.

Verified locally: 1531 Core specs pass.
Replay races sibling partitions through their own events independently
of each other. When a root projection's HandleEvent step calls
AddPropertiesFrom(InitialModelState) it produced a PropertiesChanged
covering every property in the read model — including children
collections like Features. A subsequent root event whose ChildAdded
landed first would already have populated the parent document's
Features array; the root's PropertiesChanged then $set Features back
to [], wiping the children.

The sequential append path got lucky because partitions were drained
in order (root before children), but replay drives each partition
through its own job step so the writes interleave. The visible
symptom is and_projection_is_replayed of the deep-hierarchy spec:
Module.Name materialises, Module.Features is empty.

Walk the projection tree once when filling the initial-state diff
and skip every children property path the root owns. Those paths are
managed exclusively by ChildAdded / ChildRemoved across the read
model's lifetime, never by a root $set.

Verified locally: 410 Core specs + 396 Infrastructure specs pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants