Instruction file imported from NEventStore/NEventStore (
.github/instructions/core-stream-semantics.instructions.md). Copyright stays with the author.
Core Stream Semantics
Thread-Safety Contract
IStoreEventsandIPersistStreamsmust remain multi-thread safe. Never introduce instance state that is mutated during read or commit operations without synchronisation.IEventStream(OptimisticEventStream) is single-threaded. Each unit of work must callIStoreEvents.OpenStreamorCreateStreamindependently; do not pass streams between threads or re-use them across scopes.
Stream Revision Invariants
StreamRevisiontracks the highest event-level counter. It must always equal the sum of event counts across all commits on a stream.CommitSequencetracks the number of commits. It must increment by exactly 1 per successful commit.- A gap in either counter is a
StorageException(beyond-end-of-stream). A collision is aConcurrencyException. Both are enforced inOptimisticPipelineHook.PreCommit; do not bypass those checks. - When opening a partial stream (
minRevision > 0),CommittedEventswill only contain events within the requested range. Do not assume the full history is loaded.
Pipeline Hooks
- Hooks are called in registration order for
PreCommitandPostCommit. Order matters for features like deduplication and concurrency checking. PreCommitreturningfalsesilently cancels the commit and skipsPostCommit. Hooks must be idempotent and must not assumePostCommitwill always run after aPreCommitapproval.OptimisticEventStore.Commitcalls async hooks via.GetAwaiter().GetResult()on the synchronous path — this is intentional for backward compat. Do not introduce additional blocking.GetAwaiter().GetResult()calls on the hot path.- New hooks should extend
PipelineHookBase(sync) orPipelineHookAsyncBase(async) and only override the methods that need custom behaviour. Both base classes follow the virtual/override pattern.
Wireup / Composition
- All configuration surfaces go through the fluent wireup chain:
Wireup → PersistenceWireup → SerializationWireup. New extension points must return the appropriateWireupsubclass so the chain remains composable. - Register services via
wireup.Register<TInterface>(implementation)intoNanoContainer; never bypass the container. - Logging uses
LogFactory.BuildLogger(typeof(MyClass))backed byMicrosoft.Extensions.Logging. Do not useConsole.Write*or introduce a new logging abstraction.
Naming & Null Safety
- Nullable reference types are enabled. Use
?for optional returns (e.g.,ICommit?fromCommitChanges). Annotate method contracts correctly — do not suppress#nullabledirectives. - Guard clauses use expression-based parameter names:
Guard.NotNull(() => attempt, attempt). Follow the same pattern for any new public boundary checks.