Skip to content

Stratara 4.2.0

Choose a tag to compare

@github-actions github-actions released this 18 Sep 18:53
· 96 commits to main since this release
de9ba20

A hardening release for the Orleans execution model, and a new test-support package for it.
Stratara.Testing.Orleans runs the execution model inside the test's process. Beyond that, 4.2.0 closes
every gap known before release. A recorded command runs once and in its scope's order, and it is counted
against the bus's two bounds. A store reader paused by a rebuild comes back if its pauser dies, and the
rebuilt read model keeps every fact. A failing saga stops only itself. Every options section the framework
documents is read from configuration, and an anonymous caller is answered 401.

Upgrading:

  • Generate an EF Core migration. The intent record gains a conflict_count column.
  • Check appsettings.json for SessionContext:AllowTenantHeader and
    Stratara:BlobEncryption:LegacyBlobsCarryPurpose.
    Both were ignored until now and take effect with
    this release.
  • Upgrade the saga silos together.
  • Start a rebuild only once every silo runs 4.2.0.

Added

  • IProjectionCheckpointStore.FindAsync and CreateAsync: a checkpoint's position, or null where none exists, where
    GetAsync answers 0 for a missing checkpoint and one at the beginning alike; and a first checkpoint written only where none exists, never replacing one. The
    framework's store implements both; the defaults throw NotSupportedException naming the members, so a store of the
    consumer's own keeps compiling and fails only where a store-reading saga needs them.

  • New package Stratara.Testing.Orleans (test-support, the 28th package): ExecutionModelTestHost runs the Orleans
    execution model in a test's own process — one silo, in-memory reminders and grain directory, the real write stack,
    portable commit-order reader, checkpoint and intent stores on in-memory SQLite, and every period shortened to
    seconds. A test registers its roles with the production calls, dispatches, waits for the readers
    (WaitForReadersAsync) and asserts; Timers, SeedAtHeadAsync, ResetAsync and ExecutionModelTestHostOptions
    (with BeforeStart) complete it. The package carries the STRATARA1001 build check and refuses a stated non-development
    environment.

  • Sample Stratara.Sample.OrleansExecutionModel: a command in its aggregate's activation, a projection from the
    store and a process timeout on the test host, in one console run; smoke-tested.

  • AddStrataraTestingEventStore<TWriteDbContext>(connectionString, tenantId, configureContext): a connection per
    context to a shared database, and the caller's options — an interceptor — after the provider.

  • AddCommandServices() (Stratara.EventSourcing.WorkerDefaults): the command worker stack without the bus-fed
    mediator worker, beside AddEventProjectionServices and AddSagaServices. AddCommandWorkerServices is now
    AddCommandServices plus the worker. A silo composed with it, the execution model's command dispatcher and intent
    store, the aggregate grains and a store-reading role runs commands and applies their facts without a message broker,
    as does an API host that only dispatches through the execution model's dispatcher.

  • Orleans: AddStrataraSingletonWork<TWork>(string name, ...) and OutboxDrainWork.WorkName. A work registered
    with its name is published in the silo's metadata without being constructed, so it is first constructed when the
    silo is active; a work whose Name differs from the registered name fails the start naming both.

  • Orleans: log event 117_119 (LogEvents.Orleans.SingletonWorkFailed) for a run of a singleton work that threw;
    the work runs again at its next period, as before. 117_120 (LogEvents.Orleans.RolesUnpublished) for a silo
    that does not start because it hosts roles or work it does not publish.

  • Orleans: a recorded command is signed and verified. Where the host registered a bus-envelope signer, the record
    the execution model writes before a dispatch returns carries the signature a bus command carries, and the drain
    verifies it under BusEnvelopeIntegrityOptions.Mode before resuming it: under Strict an unsigned or invalid record
    is kept at once with the reason, under Permissive it is resumed and logged. Log events 117_115–117_118.

  • ICommandIntentStore.ClaimAsync claims a batch of due commands; the default claims row by row through
    TryClaimAsync, the shipped store in two statements.

  • Orleans: log event 117_008 (LogEvents.Orleans.HandlerStoppedWithSilo) for a handler on a grain path cancelled
    because its silo stopped.

  • IProjectionCheckpointStore.AdvanceAsync advances a checkpoint from the position its writer last saw. The
    default replaces the position through SetAsync, so a store of the consumer's own keeps compiling; the shipped
    store refuses a write that finds another position or another reader.

  • Orleans: log events 117_005–117_007 — StoreReaderRetired for a reader beyond the host's partition
    count, ResumeHeldBackByReplay and ResumeReleasedAfterReplay once each when a full replay holds recorded
    commands back and releases them.

  • Orleans: a host seeds its store readers at the head. IStoreReaderSeeding.SeedAtHeadAsync, registered
    with AddStrataraProjectionGrains and AddStrataraSagaGrains, writes a checkpoint at the store's current
    head for every registered projection and saga and partition that has none, and reports how many it wrote and
    how many existed. A host whose read models are current runs it once, while no silo runs, before its first
    start — without it the first start re-applied the whole history. A consumer registered later still starts
    at the beginning.

  • ICommittedPositionReader.HeadAsync reports a partition's head — the position after which nothing
    committed exists — with a default that walks the partition; both shipped readers answer in one query.

  • Orleans: CommitTransactionIdBackfill stamps the commit record on a populated PostgreSQL event table in
    append order and bounded batches, so the native reader's column is added without rewriting the table and
    without the whole history landing under one transaction id.

  • Orleans: AddStrataraExecutionModelReset takes the schema the runtime tables live in. schema
    defaults to public; a reminder or membership table absent under it fails the reset naming the table
    instead of reporting that nothing was removed, and the three runtime tables are cleared in one transaction.

  • Orleans: two log events. 117_112 (LogEvents.Orleans.IntentAttemptFailed) for every attempt of a
    recorded command that fails, with the command's id, type and aggregate; 117_113 (HandOverFailed) for a
    hand-over that fails. A failing handler is seen before its command is kept.

  • Orleans: log event 117_114 (LogEvents.Orleans.PermitReclaimRefused) for a running heavy unit that was
    refused its permit when it registered again after the permit keeper was lost, and runs outside the
    cluster-wide bound until a permit is free.

  • SessionRequiredException (Stratara.Abstractions.Session): the failure of an operation that must be
    attributed to a caller and runs with no session context — a save, a command audit, a dispatch on the bus path and
    on the execution model's path. It derives from InvalidOperationException and keeps the message these operations
    always used, so an existing catch (InvalidOperationException) and a log search on the text keep working; a host
    catches it without referencing any store or broker package.

  • ICommandIntentStore: RecordAsync(…, recordedAt, …), TryRenewAsync, TryRenewFromAsync,
    RecordConflictAsync and ReturnAttemptAsync
    , each with a default that does what a store written against 4.1
    did — the record's time is the store's own and its count starts at zero, every hand-over runs, a conflict is
    recorded as a failure, a stop gives nothing back.
    OutboxEntry.ConflictCount and RecordedIntent.ConflictCount (defaulted) carry the conflict count.

Changed

  • Orleans: each store-reading saga reads with a checkpoint of its own. A saga's reader is keyed and checkpointed
    under sagas:<SagaName> — the saga's type name — instead of the consumer sagas all sagas of a deployment shared,
    so orleans.reader.stalled, the stall log 117_101 and the attempt log 117_102 carry the failing saga's consumer
    name; a dashboard or alert that filters on sagas must be widened to the sagas: consumers. Seeding, the
    execution-model reset and the wake-up after a commit name every saga's consumer, and the reset also removes the
    shared sagas checkpoint of 4.1.x. A saga without a checkpoint starts where the deployment's sagas read in that
    partition — the shared checkpoint of 4.1.x, or else the furthest checkpoint another saga holds — so a saga added
    to a running deployment runs no side effect for the store's history. Upgrade note: nothing is migrated; on the
    first start every saga starts at the shared checkpoint, and the shared reader, brought back by its 4.1.x keep-alive,
    retires and logs 117_125 (information). While a 4.1.x saga silo runs beside a 4.2.0 one both apply the facts
    above the shared checkpoint, so upgrade the saga silos together. A rollback to 4.1.x resumes the shared reader
    from the shared checkpoint, which 4.2.0 never advanced, and applies what the per-saga readers applied since
    again. Seeding a store with the shared checkpoint starts each saga there rather than at the head, so the shared
    reader's backlog is not skipped. A host that registers two saga types of the same type name — in different
    namespaces — no longer starts, naming both, because they would share one checkpoint; rename one before upgrading.
    A saga's reader brought back on a silo that registers no saga of its name — a removed or renamed saga — retires,
    unregisters its keep-alive and logs 117_126 (information) instead of stalling on every entry. A host with a
    checkpoint store of its own must implement IProjectionCheckpointStore.FindAsync and CreateAsync (see Added)
    to run store-reading sagas.

  • Security: "SessionContext": { "AllowTenantHeader": true } in the host's configuration now turns the tenant-header
    fallback on.
    AddSessionContext() did not read the SessionContext section, so such an entry did nothing unless
    the host bound the section itself; it is read now (see Fixed). Before upgrading, search every environment's
    configuration for AllowTenantHeader and remove the entry or set it to false unless the host means to accept the
    X-Tenant-Id header. Likewise, a Stratara:BlobEncryption:LegacyBlobsCarryPurpose entry now takes effect for a host
    that registers the blob encryptor through AddSecurity() or AddStrataraBlobEncryption() alone.

  • AddStrataraProblemDetails() answers a caller that is not authenticated with 401. An authorization refusal, a
    tenant-access denial and a SessionRequiredException alike: what such a caller lacks is an identity, not a
    permission. Where the host has a default authentication scheme the mapping challenges through it — a bearer client
    receives WWW-Authenticate, a cookie client is redirected, as [Authorize] would do — and writes the problem body
    where the challenge leaves a 401; without a scheme it writes a 401 problem response. A client that treated 403 as
    "log in" now receives 401
    , which is the status that says so. An authenticated caller's denial stays 403, and a
    SessionRequiredException for an authenticated caller is not converted — it means the host did not run the
    session middleware. A host that does not register the mapping sees nothing new but the exception type.

  • Orleans: hybrid: true without a bus dispatcher fails at registration naming AddOutboxDispatcher; it ran
    grain-only without a word.

  • The execution model's operating limits are documented: a forwarded command whose handler outlasts the response
    timeout fails its caller and commits (do not retry on a timeout); a resumed command is authorized from its recorded
    session, so the authorization provider answers from the session, not the web request; the record of a command is
    committed on its own, not with the caller's writes; a full replay applies the store twice to store-reading
    projections; singleton work may overlap on two silos for one membership refresh after a death declaration, and the
    failover latency follows from the membership settings. ISingletonWork and IAuthorizationProvider say so in their
    XML documentation.

  • Orleans: a silo places grains on itself by the roles and work it publishes, before its own metadata reaches its
    cache.
    A silo that became active and started its store readers at once could fail its start with No silo of the
    cluster registered the projections role
    , because its own metadata was not yet known to it.

  • Orleans: a wake-up nudge that cannot be sent no longer fails the dispatch after a commit. A commit made before the
    silo started, or while it stopped, threw from the bundle dispatcher although the facts were stored; the nudge is now
    lost like any other and the readers' poll applies the facts.

  • The portable commit-order reader is verified on SQLite as well as PostgreSQL, through the test host.

  • Migration guide: the command silo and the bus queues. The command role's row names AddCommandServices, the
    dispatcher and the intent store; When the broker can go states which hosts need the broker; After the cut-over:
    the bus queues
    names the queues the bus workers own and the order in which to retire them.

  • Orleans: every registration of the execution model is idempotent. A second call of
    AddStrataraProjectionGrains, AddStrataraSagaGrains or AddStrataraSingletonWork<TWork> registered its wake-up
    target or its work again, so every projection was woken twice per bundle and the seeding and reset listed each
    consumer twice.

  • Orleans: a silo that hosts a role or singleton work without publishing it fails at start. A silo that registered
    the grain directory under the model's name itself rather than with AddStrataraOrleans started and was placed on as
    if it hosted every role; it now fails naming the roles and works it found and AddStrataraOrleans. A silo that
    hosts nothing placed by role is unaffected.

  • Orleans: the drain resumes a backlog as fast as the handlers take it. While a pass finds a full batch due, the
    next pass follows at once, for at most OutboxDrainOptions.PollingInterval; a backlog was resumed one batch per
    period.

  • Orleans: a stopping silo tells the handlers on its grain paths. Command handlers in an aggregate's activation or
    a runner, heavy work and timer handlers receive a CancellationToken that is cancelled once the silo has been
    stopping for GrainCollectionOptions.DeactivationTimeout; it was CancellationToken.None. A recorded command
    stopped this way is resumed elsewhere with no attempt counted, a forwarded command fails back with a message saying
    the silo stopped, and a timer stays registered and fires on the next silo.

  • Orleans: a timer registered from its own handler is kept. A handler that registered its owner and purpose again
    with the same due time lost the new timer when its tick unregistered itself.

  • Orleans: IDurableTimers refuses an owner id longer than the reminder table holds (139 characters) on every
    member, with a message naming the limit, instead of failing at the first tick.

  • Orleans: the heavy-work bound holds across the loss of the silo keeping the permits. A permit keeper
    that is activated admits no new heavy unit for one PermitLease and takes back every running unit that
    registers again, so the units the lost keeper had admitted count against the bound before new ones start;
    previously a new keeper handed out the whole bound at once beside them. A running unit whose permit was lost
    now reads the answer when it registers again and keeps asking until it holds a permit. The first heavy
    commands after a keeper's activation — including a cluster's first — start one lease later.

  • The migration guide says how a populated store adopts the execution model. The transaction-id column
    is added nullable, backfilled, then given its default and constraint — while nothing appends; the guide
    states the upgrade order (schema before the first 4.1 host, 4.0 hosts keep running, bus outbox worker
    before the first drain silo, seed, silos, API host, bus workers, queues), that a checkpoint is keyed by the
    projection's simple class name, and the prerequisites of the Orleans packages.

  • Orleans: every role is placed on the silos that registered it. A silo publishes the roles its
    composition registers — commands with AddStrataraAggregateGrains, projections with
    AddStrataraProjectionGrains, sagas with AddStrataraSagaGrains, timers with AddStrataraDurableTimers
    and an ITimerOwners — and aggregates, command runners, heavy-work pools, projection, saga, process and
    timer-owner grains are placed only on silos publishing their role, as singleton work already was. Roles
    may be split across silos; a call for a role no silo registered fails naming the role and the registration.
    The heavy-work pool is no longer a stateless worker on the calling silo: it is one pool per eight permits
    of HeavyWorkOptions.ClusterWideLimit, placed on silos of the command role. An API host that only
    dispatches may join as an Orleans client.

  • Orleans: a send that would close a cycle between aggregates is refused at once. A handler running for
    aggregate A that sends to B, whose handler sends back to A, used to wait for the runtime's response
    timeout; the send back is now refused with a message naming both aggregates, and both commands fail at
    once. Sends between aggregates form a directed acyclic graph.

  • Orleans: a read that fails counts as a stall. A store that cannot be read, or a checkpoint the reader
    refuses, is logged as 117_103 and counted in orleans.reader.stalled whichever wake-up or poll started
    the read, not only on a nudge.

  • Orleans: the portable commit-order reader states its preconditions. It is verified on PostgreSQL,
    and through the test host on SQLite; every process that appends to a store it reads needs PartitionCounterInterceptor — the framework
    does not add it, and CommitOrderOptions.MaintainPartitionCounter is only the value a write context reads
    when it does; and the partition count cannot change once the store holds positions. On PostgreSQL the
    native reader remains the one to use.

  • PartitionCounterBackfill positions a batch with one statement on PostgreSQL instead of one per entry;
    other providers keep the per-entry update, and the positions it hands out are the same.

  • Orleans: a recorded command is counted the bus's way — schema change. The record counts the hand-over of the
    dispatch as the first of MessageRetryOptions.MaxDeliveryAttempts when it is written, so a handler that keeps
    failing runs as often as the bound says (before, once more), and a host that dies before that hand-over has used
    the attempt, as a crashed delivery does on the bus — under a bound of 1 such a command is kept for an operator
    without running. A concurrency conflict — ConcurrencyException, what the bus transports count as one — gives its
    attempt back and counts against MessageRetryOptions.MaxConflictRequeues instead (before, against the delivery
    bound of 3). A stop gives its attempt back, for the running handler and for the commands queued behind it. The
    count lives in the new column outbox_entry.conflict_count: generate and apply an EF Core migration before the
    first 4.2 host starts, as for 4.1 (see the migration guide). A command recorded under 4.1 carries no counted first
    attempt and runs as 4.1 ran it. An operator returning a kept command also resets conflict_count and
    last_failure.

Deprecated

  • CommitOrderOptions.MaintainPartitionCounter is obsolete: the framework never read it. A write context that
    maintains the partition counter adds PartitionCounterInterceptor. The member is removed with the next major.

Fixed

  • Orleans: a resumed command runs once however its resumptions meet. Two claimers stamping the same millisecond
    — the singleton drain during a failover, a bus outbox worker beside the drain during adoption — both handed the
    command over, and a command naming no aggregate, or one whose first run had ended, ran twice; so did a hand-over
    arriving after the command completed. The hand-over now carries the claim's stamp and runs only if its receiver
    takes the record over from that stamp; one that finds it moved or the record gone is dropped and logged as
    117_124 (IntentHandOverDropped, Debug).

  • Orleans: commands one scope dispatches to one aggregate are resumed in dispatch order. The record's time was
    taken after the payload was serialized, so a slower first record resumed after the second — SetPrice 10 after
    SetPrice 20. It is now taken when the dispatch starts, strictly increasing per aggregate within the scope, and due
    commands are resumed by that time, then by id.

  • Orleans: a silo that stops no longer takes an attempt from the commands it was running or had queued. The
    stopped handler, and every recorded command queued behind it that the stop gives up, gives back its attempt, so
    rolling deploys during a long handler no longer keep commands for an operator.

  • Orleans: a hand-over the fence dropped no longer holds its command. An aggregate's activation refused a later,
    valid hand-over of the same command while the dropped one waited in its queue.

  • Orleans: a dispatch's hand-over that arrives after a resumption completed the command is dropped. A hand-over
    arriving later than a sixth of the grace checks that its command is still recorded and not kept.

  • Stratara.Testing.Orleans: a recorded command is resumed on the test host. Its SQLite write store kept points
    in time as text, which the intent store's due query cannot compare; they are kept as numbers now.

  • Orleans: a recorded command's time comes from the registered TimeProvider. It was the wall clock while the
    drain compared it with the registered clock, so a host or test with its own clock saw commands never become due, or
    become due at once.

  • Orleans: a failing saga no longer repeats its siblings or stops them. All store-reading sagas of a partition
    shared one reader and one checkpoint, so an entry one saga failed on was retried for every saga on each poll and
    wake-up, without a bound — a saga that had succeeded on it, sending an email or issuing a command, did so again
    every few seconds until the failing one was fixed — and every later fact of the partition waited for it. A saga
    that fails now stops only its own reading of the partition; every other saga applies the entry once and goes on.

  • An anonymous caller reaching an unguarded save or dispatch is no longer a server error. The event source, the
    command audit, the bus dispatcher and the execution model's dispatcher threw a plain InvalidOperationException
    that the problem-details mapping let through as a 500 reading like a framework bug; they now throw
    SessionRequiredException, which the mapping answers 401 for a caller that is not authenticated.
    RequireAuthorization() on every endpoint is no longer needed to avoid that.

  • Orleans: a singleton work's settings apply to that work alone. The callback given to
    AddStrataraSingletonWork<TWork>(configure) and AddStrataraSingletonWork<TWork>(name, configure) configured one
    settings object shared by every work, so two works registered with keep-alive periods of two and five minutes both
    ran with whichever callback ran last. Each work now runs with the host's SingletonWorkOptions — what
    services.Configure<SingletonWorkOptions>(...) sets — with its own callback applied on top, and a work registered
    twice runs with the later registration's callback, once, rather than with both. Each work's settings are validated
    when the host starts. A host that relied on one work's callback to configure the others sets the value for all
    of them with services.Configure<SingletonWorkOptions>(...) instead; a host that already configures
    SingletonWorkOptions directly is unaffected.

  • Every options type that names a configuration section is read from it. SessionContextOptions (SessionContext),
    ProjectionReplayOptions (ProjectionReplay) and — for a host without AddStrataraFileKeyStore —
    StrataraBlobEncryptionOptions (Stratara:BlobEncryption) were registered and bound by nothing, so a value in the
    documented section did nothing. AddSessionContext(), AddProjectionReplayState() and AddStrataraBlobEncryption()
    now read the section from the host's configuration, directly or through AddOutboxDispatcher(), AddSecurity() and
    the worker composites. A service collection without an IConfiguration keeps the defaults; a value configured in
    code after the registration takes precedence, and calling a registration again does not re-apply the section over
    it. AddStrataraOrleansCommandDispatcher() likewise reads MessageRetryOptions (MessageRetry) and validates it at
    start when no bus transport is registered before it — a host whose commands run only through the execution model
    set the resume bound in that section to no effect; where a transport registered first already reads the section, it
    stays the only reading. A test now holds every public options type with a section name to being read from it.

  • A projection replay lease of zero or less is refused at start. ProjectionReplayOptions.LeaseSeconds <= 0 fails
    the host with an OptionsValidationException naming ProjectionReplay:LeaseSeconds; it was accepted, and on the
    in-process replay state the marking lapsed at once and publication resumed in the middle of a rebuild.

  • Orleans: hybrid: true keeps a bus dispatcher registered by factory or as an instance. AddStrataraProjectionGrains
    and AddStrataraSagaGrains kept publishing bundles to the bus only when the dispatcher had been registered by type; a
    factory- or instance-registered one was removed and bundles stopped reaching the bus.

  • Orleans: PartitionCounterBackfill no longer renumbers positioned entries. Unpositioned entries take the
    positions after the partition's counter, so a checkpoint written before a backfill stays true; previously the
    backfill shifted every positioned entry and a resumed reader re-applied one entry and lost another.

  • Orleans: the portable reader stops at its partition's unpositioned entry however many other partitions hold.
    The probe was cut at 64 unordered rows of any partition.

  • Orleans: positions within one save follow each stream's version order, by contract rather than by the change
    tracker's enumeration.

  • Orleans: a store reader applies each entry under its own tenant. A projection, a saga and the services they
    depend on are resolved after the entry's session is set — one scope per run of entries recorded under one
    session — so a dependency that takes the tenant when constructed no longer sees the first entry's tenant, or
    none, for a whole read. A process reads its state under the session of the fact it is handed.

  • Orleans: a checkpoint is no longer rewound by a stale activation, nor written under another reader's name;
    the refused reader reads the checkpoint again. SetAsync of the shipped store refuses a row held by another
    reader.

  • Orleans: a reader of a partition beyond a lowered partition count retires instead of returning every
    keep-alive period to be refused and counted as stalled.

  • Orleans: an aggregate's order runs to the end however long it takes. The grain ran its accepted
    commands through an ordinary call to itself, which the runtime timed out after MessagingOptions.ResponseTimeout
    (thirty seconds); the grain read the timeout as "the run never started" and failed every command still
    waiting back to its caller, dropping the leases of the recorded ones. The call is one-way now; the
    response timeout bounds a caller's wait for one forwarded command only, which the operations guide says.

  • Orleans: a running command is renewed whether or not its handler yields. The lease and permit
    renewals ran on the activation's scheduler, so a heavy handler that computed without awaiting past
    IntentGrace was never renewed, was claimed by the drain and ran a second time. Both renewals run from
    timers of their own.

  • Orleans: a heavy hand-over is leased while it waits for a worker. A burst that queued units for
    longer than the grace handed the queued ones over twice; the pool accepts a hand-over at once and its
    lease covers the wait for a slot and a permit. A hand-over the pool already holds is not accepted twice.

  • Orleans: two rebuilds of one projection no longer interleave. The pause was a flag: the first rebuild's
    resume let the readers re-read and advance their checkpoints before the second rebuild truncated, which
    left the read model empty for good. Pauses are counted, the readers resume when the last pauser resumes,
    and a rebuild requested during a full replay is refused naming the replay.

  • Orleans: the reset is resolved from a scope in the documentation and its example. It is a scoped
    service; resolving it from the root provider throws wherever scope validation is on.

  • Orleans: a batch cut short by the reader's shutdown still records the checkpoint for the entries it applied.

  • Orleans: the migration guide's package list compiles. It names Microsoft.Orleans.Clustering.AdoNet,
    Microsoft.Orleans.Reminders.AdoNet and Microsoft.Orleans.GrainDirectory.Redis, where their SQL scripts
    are, that the bus outbox worker is retired before the first drain silo with an intent store (the intent
    store still claims stored bus commands older than the grace), that the saga role needs a reminder service,
    and which settings the drain silo reads from its own configuration. The Stratara.Orleans README quick
    start no longer keeps the bus-fed projection worker beside the grains; the log events 117_001–117_113
    and the orleans.* instruments are on the reference page and in the operations guide with what to alert
    on; two pages that still said there is no checkpoint store say where one is.

  • Orleans: the portable reader stops at an entry without a position instead of skipping it. An entry
    appended by a process without the interceptor was never read, silently. A read of its partition now fails
    naming the entry, the interceptor and PartitionCounterBackfill; the partition stops like any failing
    entry and continues once the entry is positioned.

  • Orleans: a host refuses to start with a partition count lower than the store's counters. Lowering the
    count merged partitions whose positions overlap, and new appends were skipped even after the checkpoints
    were reset.

  • Orleans: the partition counter interceptor releases its transaction when positioning fails. The
    transaction it opened stayed open on the context until the next save or the context's disposal.

  • Orleans: both backfills of a populated store page by key. Each batch of the transaction-id backfill asked
    for the oldest entries without a commit record, which no index answers, so every batch walked everything the
    batches before it had stamped — on a store with a long history the migration window it belongs to was measured in
    hours. The partition counter's backfill held a partition's whole history in memory and its counter lock for the
    whole run; it now takes one batch per transaction.

  • Orleans: the head says what it is. A head is taken while nothing appends: the native reader's head is held
    back by an open write transaction, so a deployment that seeds while its old hosts still append applies what they
    commit in that window a second time. The contract, the migration guide and a test now say so.

  • Orleans: a singleton work's failure is logged whatever it failed with — a cancellation the work was not
    asked for, an HTTP client's timeout, went unlogged — except where its silo is stopping.

  • Orleans: two singleton works under one name are refused — at registration where the name is given, and when
    the silo starts where two registered works return the same one. The name keys the grain that runs the work, so
    the second never ran and nothing said so.

  • Testing: ExecutionModelTestHost.ResetAsync works on a running host. It deleted the checkpoints while the
    readers kept their cached positions, so a shared host reset between two tests either never finished
    WaitForReadersAsync — the checkpoint said nothing was applied, the store's head said otherwise — or applied the
    previous test's facts a second time. It now stops every registered reader, puts it at the store's head and starts
    it again, and says how many it moved. The pause that made this possible moved into the store reader every
    consumer shares, so a saga's reader stops for a reset as a projection's does.

  • Testing: a host whose start fails stops what it started, instead of leaving a half-started silo holding its
    ports and timers for the rest of the test run; a start refused because another host took the port is tried again.

  • Orleans: a placement filter writes the silo's own metadata entries before it reads them, so a first placement
    can no longer judge the silo by a table the runtime has not filled yet.

  • Orleans: a resumed command runs where its signed envelope says. The resume routed by the record's own
    columns, which the signature never covered: a stored heavy flag flipped in the store sent a signed command past
    the heavy pool's bound or outside its aggregate's order, and the signature still verified. The row's identity and
    heavy claim are now held to the envelope — a record that disagrees is kept under strict mode and resumed by the
    signed claim under permissive mode, logged as 117_122 — and the HMAC guide says what the signature covers and
    what it does not. A command the bus outbox stored during a rolling adoption carries no routing beside its
    envelope and is resumed as before.

  • Orleans: an aggregate gives up its queue when its silo stops. The queue was given up in the deactivation,
    which the runtime starts only once the requests waiting for those commands have ended: the callers waited for
    the runtime to give up instead of being told, and the silo's stop waited with them.

  • Orleans: a timer's tick no longer deletes the reminder that renewed it. The renewal check and the
    unregister now run under the grain's own gate, so a registration for the same purpose and due time from another
    turn — a saga re-applying a fact — cannot land between them.

  • Orleans: a read model can be rebuilt after its reader or partition count changed. The checkpoint guard
    refused every write under another reader's name — including the two verbs that recover from it, a projection's
    rebuild and the full replay's reset, which write the beginning under the host's new name. Returning a checkpoint
    to the beginning is now a verb of its own (IProjectionCheckpointStore.ResetAsync, with a default implementation
    for stores outside the framework) and takes the row over; reading and advancing are guarded as before.

  • Orleans: a rebuild or replay whose pause fails leaves no reader paused. The readers were all paused before
    the work began, so a pause that failed left the ones that had paused with a pauser nobody released — and since
    rebuilds count their pausers, no later rebuild cleared it: the partition read nothing until its silo restarted.
    A failed pause now resumes what it paused and fails naming how many it could not pause.

  • Orleans: hybrid: true on a second store-reading registration is no longer ignored. A host that registered
    the projection role and then the saga role with hybrid: true silently stopped publishing bundles to the bus.
    Every registration that asks is now answered: the bus dispatcher is kept, or the registration fails naming what
    is missing. A kept dispatcher registered by its type is also owned by the container again, so it is disposed
    with its scope.

  • Orleans: a paused store reader always comes back, and a rebuilt read model holds every fact. A projection's
    rebuild, the full replay's reset and the test host's reset paused the readers with a count that knew none of its
    pausers and lived only in the activation. A pauser that died between pause and resume left the readers paused
    until their silo restarted, with no stall counted; a resume that was retried released another rebuild's pause;
    and a reader resumed before the truncation — early, or in an activation that had moved and forgotten the pause —
    applied facts, advanced past them and watched the truncation delete them, leaving the read model short until the
    next rebuild. A pause now belongs to its pauser and lasts while the pauser renews it (a lease of sixty seconds,
    renewed every twenty); a pause that is not renewed lapses, the reader resumes and logs 117_123 (warning). A
    resume releases only its own pauser's pause however often it is repeated. A rebuild and a replay return the
    checkpoints to the beginning again after the read model is emptied — waiting first for any batch a reader still
    had in flight — so what a reader applied before is read
    again after it — a fact applied in between is applied twice, which a rebuildable projection must tolerate as a
    full replay already requires. Upgrade note: during a rolling upgrade, a rebuild or replay started from a
    4.1.x silo pauses an upgraded silo's readers for at most ten minutes, because it cannot renew, and resets the
    checkpoints only once, and one started from an upgraded silo fails naming a reader still hosted on 4.1.x; rebuild
    once every silo runs the new version.

  • Orleans: a wake-up that cannot be sent is logged (117_121, debug) and no longer costs the other consumers
    of that commit theirs.

  • Orleans: the heavy-work pool runs its units beside each other again. Since the pool became a placed grain
    its eight slots lived in one activation, which runs one turn at a time: heavy units ran one after another, and a
    handler that computed without awaiting anything blocked the pool's front door, so the next heavy command was not
    accepted, not leased, and handed over again after the grace — counting an attempt each time, up to being kept for
    an operator although it never ran. A unit now runs on one of the silo's own heavy workers, off the pool's
    scheduler; the pool still accepts, leases and de-duplicates the hand-over.

  • Orleans: a freed permit goes to a running heavy unit before a waiting one. A running unit whose permit was
    lost with its keeper and refused when it registered again asks at every renewal, while units waiting to start ask
    every hundred milliseconds — under a queue of heavy work the running unit never counted against the bound again.
    The permit table now reserves the room for it, so the bound is exceeded only until a running unit ends.

  • Orleans: corrections in the execution model's guides. The response timeout is named
    SiloMessagingOptions.ResponseTimeout (ClientMessagingOptions.ResponseTimeout from outside the cluster) —
    configuring the shared MessagingOptions base changed nothing; a death declaration needs the lesser of
    NumVotesForDeathDeclaration and half the active silos, so a two-silo cluster does declare one; the overlap of
    singleton work during a failover is bounded by the table refresh only while the declared silo can still read the
    membership table; the cut-over draws the seeded head in one window with nothing appending, and the guide now says
    what the alternative costs — applying twice where the bus workers stay up, applying not at all where they stop
    while appends continue; and a full re-read is a reset without a seeding, because a checkpoint at the beginning
    counts as absent and is seeded again.