Skip to content

Forward-merge 3.8 and 3.9 release fixes into main - #285

Merged
sfmskywalker merged 62 commits into
mainfrom
codex/extensions-release-reconcile
Oct 5, 2026
Merged

sfmskywalker merged 62 commits into
mainfrom
codex/extensions-release-reconcile

Conversation

@sfmskywalker

Copy link
Copy Markdown
Member

Extensions main is missing functional fixes shipped on the 3.8 and 3.9 release lines. This forward merge brings release/3.9.0 (89d4eb9b) into main while preserving main's 3.10 preview dependencies and publishing configuration.

The net changes carry MongoDB JSON-node and variable serialization fixes, tenant-scoped ordered workflow summaries and interruption, and Dapper's BeforeLastUpdated filter with regressions. Existing main implementations of the PostgreSQL and atomic key-value deletion fixes are retained. All stable 3.8 tags and the current 3.9.0 release tip are ancestors of this branch.

Refs elsa-workflows/elsa-core#8623, elsa-workflows/elsa-core#8194.

Validation: independent source-merge review found no actionable integration issue; diff checks pass. Focused local Dapper/MongoDB/PostgreSQL tests and hosted CI are pending. This PR is not yet qualified for merge.

Merge with a merge commit to retain release ancestry. The merge message must include [skip ci] after current-head PR CI and review pass, because a normal main push triggers Feedz publishing; this task does not authorize publication. No package, release, or deployment is part of this change.

sfmskywalker and others added 30 commits September 11, 2026 16:44
* Fix MongoDB VariableSerializer using empty serialization type registry

VariableSerializer constructed VariableMapper against SerializationTypeRegistry.CreateDefault(), an isolated empty registry. After 3.8 type-resolution hardening, storage driver type names such as WorkflowInstanceStorageDriver no longer resolve, so variables lose StorageDriverType and are not persisted on resume.

Inject the DI-registered ISerializationTypeRegistry (populated from SerializationTypeOptions by Elsa features) and register VariableSerializer so MongoDB serializer setup can use it.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* Fix VariableSerializer tests resolving Options.Create against Elsa.Options

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
* Fix MongoDB JsonNode serializer for JsonObject/JsonArray/JsonValue.

PolymorphicSerializer looks up serializers by concrete runtime type, but
JsonNodeBsonConverter was registered only for the JsonNode base type.
JsonObject values in workflow state then serialized via class-map fallback
and failed to deserialize (no parameterless constructor). Register the
converter for derived types, accept the legacy map format, and cover
round-trips with unit tests.

Fixes elsa-workflows/elsa-core#8048

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* Tighten JsonNode tagged-envelope detection.

Require exactly two elements and a BSON string value for JsonObject/JsonArray
so a document that merely has type/value keys is deserialized as a map.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…tore (#187)

Companion to elsa-core#8059: mark Interrupted only while Status is still
non-terminal so DrainOrchestrator cannot clobber a Finished commit.

Fixes #187

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…183)

DapperWorkflowInstanceStore.ApplyFilter never applied
WorkflowInstanceFilter.BeforeLastUpdated. The property was accepted and then
silently dropped from the generated SQL, so callers got unfiltered results
with no error.

RestartInterruptedWorkflowsTask relies on that property to distinguish a
genuinely interrupted workflow from one that is simply executing right now.
On the Dapper provider its inactivity guard was therefore a no-op: the query
degenerated to IsExecuting = 1, and any workflow that happened to be
mid-execution when the task ticked was restarted from the beginning.

The query builder had no inequality support at all (only Is, IsNot, IsNull,
In, NotIn and StartsWith), which is why the property could not be expressed.
Add a LessThan clause and wire it up.


(cherry picked from commit 28fcb58)

Co-authored-by: Ronald Kroon <ronald.kroon@avivasolutions.nl>
* fix: allow TryMarkInterrupted to promote Finished/Cancelled

Align Mongo, Dapper, and Elasticsearch TryMarkInterruptedAsync with
elsa-core#8069 so drain force-cancel (Finished/Cancelled) can become
Running+Interrupted with IsExecuting=false. Naturally completed
Finished/Finished and Finished/Faulted remain refused (#8052).

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix: use UpdateByQuery for ES TryMarkInterrupted

Replace Find+SaveAsync with a conditional UpdateByQuery so Elasticsearch
matches core#8069's atomic write shape. Predicate is Status != Finished
OR SubStatus == Cancelled; success sets Running+Interrupted and
IsExecuting=false.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…191)

Mirror elsa-core#8069 (bc04b84): TryMarkInterruptedAsync refuses every
Finished row by default and only promotes Finished/Cancelled when the
drain-only flag is set. Keep Mongo UpdateOne, Dapper raw WHERE, and ES
UpdateByQuery atomic shapes.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Retarget central Elsa.* / Elsa.Studio.* package pins so extensions can republish against core and studio 3.8.1. Directory.Packages.props already consumes these properties.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Retarget central Elsa core and Studio package pins so extensions can republish Mongo/Dapper from the release/3.8.2 tip against published 3.8.2 packages.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Retarget central Elsa core and Studio package pins so extensions can
publish against nuget.org 3.8.4 after the aligned studio/core cascade.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Preview package versions on release/3.9.0 should use the 3.9 train. Leave ElsaVersion/ElsaStudioVersion unchanged until core/studio previews exist. Do not cut a stable 3.9.0 tag.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Unblock compile after #217: replace stale 3.8.0-preview.5557 / 3.8.0
with published 3.8.4. packages.yml base_version stays 3.9.0.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
PRs targeting release/* previously skipped the generated pr workflow
because OnPullRequestBranches listed only main. Add release/* and
regenerate GitHubActions_pr so build/test CI runs on release trains.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Compile release/3.9.0 against core 3.9.0-preview.5708 from feedz
instead of 3.8.4. Implement IWorkflowDefinitionStore.TryUpdateLatestAsync
on the Mongo and Dapper stores to match the core 3.9.0 API.
ElsaStudioVersion stays 3.8.4.

Fixes #220

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Replace the weaker release-branch compare-and-swap with main's Mongo
conditional writes and Dapper TenantId/DefinitionId/ToolVersion guards.
Add Dapper SQLite and Mongo Testcontainers tests modeled on core's
MemoryWorkflowDefinitionStoreCompareAndSwapTests.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…view-a227

Pin ElsaVersion to 3.9.0-preview.5708 so release/3.9.0 compiles
Bring 3.8.4 Mongo/Dapper persistence fixes onto the 3.9 train.

Conflict resolution:
- Directory.Build.props: kept release/3.9.0 ElsaVersion 3.9.0-preview.5708
  (and existing ElsaStudioVersion 3.8.4). The 3.8.1/3.8.2/3.8.4 version
  bumps must not change 3.9 package pins.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…3.9.0

chore: forward-merge release/3.8.4 into release/3.9.0
Dapper, Elasticsearch and Mongo now ignore allowFinishedCancelled and
only mark a Running row Interrupted, matching core#8419/#8421.

Fixes #225

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…d-refuse-finished

fix(persistence): TryMarkInterruptedAsync refuses Finished rows in Dapper/Elastic/Mongo (#225)
…izeManyAsync

Route the drain update and the ordered, paged instance summary through
MongoDbStore tenant filters so a tenant-B caller cannot mark or list
tenant A's workflow instances.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…lds)

Give the new Update(table, fields) member a default interface
implementation so third-party dialects that implement ISqlDialect
directly keep compiling. SqlDialectBase keeps the same public method
and both call a shared helper.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…-default-impl

fix(dapper): default implementation for ISqlDialect.Update(table, fields)
…ore-tenant-scope

fix(mongodb): tenant-scope TryMarkInterruptedAsync and ordered SummarizeManyAsync (#228)
…tic flags

Apply the store tenant read scope to both paged FindSummariesAsync overloads
so Studio cannot list or count another tenant's workflow definitions. Pass
filter.TenantAgnostic through the Mongo trigger paged query and BookmarkQueueStore
methods, and reuse the scoped List/Count path in LabelStore.ListAsync.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
… take over rows

MongoDbStore upserts now match key/Id AND TenantId == document.TenantId AND
TenantId in {writer, *}, so a write cannot change a row's owner. A mismatch
hits the unique _id index (E11000), matching EF's PK violation. ApplyTenantId
normalises "" to null so default-tenant role/user re-saves keep working.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…psert tests

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…y category

Add SaveAsync/SaveManyAsync cases where tenant B sends TenantId=tenant-a
with A's Id. Those are the only path that needs the writer/* clause.
Assert Mongo ServerErrorCategory.DuplicateKey / code 11000 instead of
the E11000 string.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…summaries-tenant-scope

fix(mongodb): tenant-scope paged definition summaries (#239)
…nt-owned-filter

fix(mongodb): tenant-owned upsert filter so cross-tenant saves cannot take over rows
…tion

V3_5 (20006) already adds ActivityExecutionRecords.AggregateFaultCount.
V3_7 (20007) added it again, so a fresh SQLite MigrateUp failed and rolled
back, leaving SerializedMetadata (and later Identity tables) missing.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
cursoragent and others added 26 commits September 28, 2026 07:32
Pin extensions to the core preview that added IWorkflowInstanceStore.TryMarkInterruptedAsync so Dapper/Mongo/Elastic stores compile as interface implementations.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…t test

The predicate overload only sees DatabaseType, so AddSqlServer()
(SqlServer2016), AddMySql() (MySql8) and AddOracleManaged() no longer
matched. Switch back to IfDatabase(params string[]) with a shared
DateTimeOffsetProviders list: SqlServer, Oracle, MySql, Postgres,
PostgreSQL, PostgreSQL92.

Assert the real processors from AddSqlServer/AddSQLite/AddPostgres/
AddMySql/AddOracleManaged take exactly one create branch.

The fresh-PG Testcontainers test now asserts migrate + tables only.
Quoting identifiers plus including the PK in PostgreSqlDialect.Upsert
is a store-wide dialect change, not a focused migration fix.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Studio 1757 dropped Blazored.FluentValidation from Elsa.Studio.Core, so Agents/Secrets/WorkflowContexts now reference it directly (2.2.0).

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
FluentMigrator force-quotes PG table/column names. Re-implement ISqlDialect
on PostgreSqlDialect so Dapper emits matching quoted identifiers, and include
the primary key in ON CONFLICT upsert. Restore the fresh-PG Testcontainers
test to migrate, persist, and run a WriteLine workflow to completion.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
Avoid interpolated-string quote escaping so the helper stays compile-safe.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
SqliteDbConnectionProvider registers a string-only DateTimeOffset handler
on SqlMapper. After persist, PG Find failed with InvalidCastException.
Register a PG handler that accepts DateTime, DateTimeOffset, and string.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
IWorkflowHost SaveMany concatenates upserts. Without a trailing semicolon
PostgreSQL reports 42601 at the second insert.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…review-5724-c29f

chore(deps): bump ElsaVersion to 3.9.0-preview.5724 and ElsaStudioVersion to 3.9.0-preview.1757
Route every identifier ParameterizedQueryBuilderExtensions inlines
through ISqlDialect.QuoteIdentifier. The default is a no-op so SQLite,
SQL Server, MySQL and Oracle SQL stays byte-identical; only
PostgreSqlDialect double-quotes. Snapshot tests lock the pre-hook
non-PG SQL. The fresh-PG Testcontainers test now also covers list,
search, OrderBy, VersionOptions and paged delete.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
VersionOptions compared boolean columns to 1/0, which PostgreSQL
rejects (42883). ISqlDialect.BooleanLiteral defaults to 1/0 so
non-PG SQL stays byte-identical; only PostgreSqlDialect emits
true/false. The fresh-PG test now covers publish, run-by-definition,
VersionOptions, Studio lists, journal and activity-execution reads,
bookmark-queue paging, and paged delete.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
3.9-only BeforeLastUpdated uses LessThan, which still inlined the
column name. Route it through QuoteIdentifier so PG quotes and
SQLite/SQL Server SQL stays byte-identical.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
The slim Testcontainers host never called AddActivitiesFrom or
IActivityRegistryPopulator, so PublishAsync NRE'd in CreateActivity
when rematerializing the draft. Store SQL is unchanged.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
AndWorkflowInstanceSearchTerm historically inlined unquoted ID, which
is byte-identical on SQLite/SQL Server. Quoted on PG it is 42703
against FluentMigrator's "Id". Non-PG SQL is unchanged.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
B4: AndWorkflowInstanceSearchTerm now quotes Id (same spelling as
definition search). The non-PG snapshot changes one token (ID → Id);
SQLite and default-collation SQL Server are case-insensitive.

B5: rec-pg-1 uses ActivityStatus.Completed, and the paged delete is
built in the test so inner parameters are not dropped.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
The instance-search snapshot token is accepted. It also fixes
instance search on case-sensitive SQL Server collations, where
the column is Id.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…s-provider-names-cdd8

fix(dapper): match PostgreSQL provider names and quote identifiers via dialect hook
…sync (#266)

* fix(dapper): create KeyValues and add BookmarkQueueItems.SerializedOptions

Guarded V3.9 runtime migration (20008) so a migration-built DB can persist
IKeyValueStore rows and bookmark-queue Options. Existing hand-created
KeyValues tables and SerializedOptions columns are left alone.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix(dapper): apply KV prefix or exact key, not both, and bind StartsWith

Prefix FindMany threw on every provider because ApplyFilter combined
Is(Id) with StartsWith, and StartsWith emitted @SearchTermLike while
binding @{field}. Down() for 20008 is now a no-op so a rollback cannot
drop a hand-created KeyValues table or SerializedOptions column.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* chore(deps): pin ElsaVersion to 3.9.0-preview.5726

Final 3.9 cut pin. Core release/3.9.0 @ fa68369a includes #8539
(IKeyValueStore.TryDeleteAsync). ElsaStudioVersion stays 3.9.0-preview.1757.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix(persistence): atomic TryDeleteAsync on Dapper and Mongo (#260)

Override IKeyValueStore.TryDeleteAsync so legacy-pause adoption is a
single count-checked delete. Dapper uses Store.DeleteAsync row count;
Mongo uses DeleteOneAsync(ApplyTenantScope(...)).DeletedCount.

Default-tenant reads and deletes now match NULL or '' TenantId so a
legacy NULL row is visible to Tenant.Default (#245 gap for adoption).
The rest of #245 stays on 3.10.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* ci: retrigger pr workflow for #260

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* ci: touch TryDelete tests so pr.yml paths match

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix(dapper): keep QuoteIdent with StartsWith binding after #258 rebase

#258 quoted identifiers but still bound @{field} while emitting
@SearchTermLike. Keep QuoteIdent and @{field}StartsWith together.
Add PostgreSQL Testcontainers TryDelete coverage now that the infra
is on the branch. Update the non-PG SQL snapshot for StartsWith.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* test(dapper): fail if StartsWith drops QuoteIdent on PostgreSQL

CR recommended: pin the quoted @NameStartsWith shape so taking this
PR's unquoted StartsWith side fails a test, not only a local PG harness.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
…sion to 3.9.0-preview.1794

Pin core to release/3.9.0 tip 9fbbef94 and studio to bd443662 so extensions compile against the identity/access work that landed since 5726/1757.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…sion to 3.9.0-preview.1797

Retarget the 3.9 pin onto core 61aa0f7a (#8566 zero-grant claim, #8570 docs, #8573 refresh-by-sub) and studio 2842adf0 (#1105 open-redirect).

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…5744-16ea

chore(deps): bump ElsaVersion to 3.9.0-preview.5753 and ElsaStudioVersion to 3.9.0-preview.1797
Pin core to 5ef65395 so extensions compile against dashboard per-section permission filtering (#8589). Studio stays at 3.9.0-preview.1797.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>
…review-5757-16ea

chore(deps): bump ElsaVersion to 3.9.0-preview.5757
…e-pins

chore: pin Elsa / Elsa Studio 3.9.0 stable on release/3.9.0

@sfmskywalker sfmskywalker left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Elsa 3 Code Review: APPROVE + HIGH @ b76f8fd

Independent review by a reviewer agent separate from the Extensions implementation found no actionable defects or lost release fixes. Main's 3.10 preview dependency pins and publishing guards are preserved; the Dapper and MongoDB fixes and tests are retained.

Verified PR #285 has this exact head and all checks are green. CI run https://github.com/elsa-workflows/elsa-extensions/actions/runs/37381631473 passed Compile, Test and Pack: 498 passed, two unrelated Slack/Azure Service Bus skips. Relevant suites passed 64/64 (persistence Dapper), 20/20 (Dapper), and 67/67 (MongoDB). PostgreSQL fixtures start PostgreSQL 16 and propagate startup failures.

Copilot was requested as the repository's advisory reviewer; no Copilot review was available at this checkpoint. This independent verdict applies only to the stated commit. Preserve ancestry with a merge commit and suppress the main-push publisher using the agreed [skip ci] merge message.

@sfmskywalker
sfmskywalker merged commit 70ea301 into main Oct 5, 2026
7 checks passed
@sfmskywalker
sfmskywalker deleted the codex/extensions-release-reconcile branch October 5, 2026 22:26
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