Skip to content

Commit 7bc9035

Browse files
sfmskywalkerCopilotCopilot
authored
Implement null TenantId for tenant-agnostic entities (#7226)
* Add ADR for Null Tenant ID, implement tenant-agnostic logic Introduce ADR-0009 to document the use of `null` for tenant-agnostic entities, enhancing multitenancy handling. Update multitenancy features across the codebase, including EF Core query filters and ActivityRegistry, to handle null as a tenant ID, ensuring tenant-agnostic entities are accessible across all tenants. * Add multitenancy support in `ActivityTestFixture` by registering `ITenantAccessor`. * Update src/modules/Elsa.Common/Multitenancy/Implementations/DefaultTenantAccessor.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove unused `using Elsa.Common.Multitenancy;` from WorkflowDefinitionActivityDescriptorFactory (#7230) * Initial plan * Remove unused using Elsa.Common.Multitenancy statement Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> * Optimize ActivityRegistry.Find to prefer tenant-specific descriptors without performance regression (#7227) * Initial plan * Optimize Find(string type) to prefer tenant-specific descriptors with single-pass iteration Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> * Apply review feedback: combine if statements and add comprehensive unit tests Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> * Refactor tests for DRYness using theories and helper methods Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> * Clean up extra whitespace in test file Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> * Fix default tenant data visibility leak by removing NullIfEmpty conversion (#7229) * Initial plan * Remove NullIfEmpty conversion to align with ADR-0008 and ADR-0009 - Updated ElsaDbContextBase to use empty string for default tenant - Updated ApplyTenantId to stop converting empty string to null - Updated TenantAwareDbContextFactory to preserve empty string for default tenant - Updated Store.cs to preserve empty string for default tenant - This ensures: null = tenant-agnostic (visible to all), "" = default tenant Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> * Add database migration to convert null TenantId to empty string for SqlServer - Added Management migration to convert null to "" for WorkflowDefinitions and WorkflowInstances - Added Runtime migration to convert null to "" for all runtime entities - This ensures existing default tenant data is properly migrated per ADR-0008 - Note: Similar migrations needed for PostgreSql, MySql, Sqlite, and Oracle providers Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> * Clarify tenant handling logic in `ElsaDbContextBase` with new ADR references. * Add tenant ID convention analysis documents and quick reference guide * Implement tenant-agnostic functionality across modules - Introduce `AgnosticTenantId` constant to manage tenant-agnostic entities. - Modify entity handling logic to respect tenant-agnostic designations. - Adjust workflow processing to include tenant-agnostic workflows. - Update caching and activity descriptor logic to accommodate the `AgnosticTenantId`. * Refactor tenant management and registry logic in `ActivityRegistry` for improved clarity and separation of tenant-specific and tenant-agnostic activity descriptors. Remove `TestTenantResolver` and update workflow definition handling for tenant support. * Refactor `ActivityRegistry`: prioritize tenant-specific descriptors over tenant-agnostic and simplify descriptor retrieval logic. * Improve async handling in `CommandHandlerInvokerMiddleware` to await tasks without blocking * Update ADR to use asterisk as sentinel value for tenant-agnostic entities Replace the previous convention of using `null` for tenant-agnostic entities with an asterisk (`"*"`) for improved clarity and system architecture. Updated ADR documentation, TOC, and dependency graph accordingly. * Remove migration `ConvertNullTenantIdToEmptyString` and its associated designer file to clean up the codebase. * Refactor `ActivityRegistry`: streamline activity descriptor removal logic and simplify tenant ID checks. * Update Elsa.sln Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Simplify `RefreshDescriptorsAsync` by removing unnecessary local variable `currentTenantId`. * Remove unused `currentTenantId` variable from `ActivityRegistry`. * Add detailed semantic flow and key points to ADR 0009 Document the tenant ID flow from entity creation to query, emphasizing normalization and tenant-agnostic workflows. Update semantic flow diagrams and provide testing considerations for preserving `"*"` values in multi-tenant scenarios. * Remove outdated Tenant ID Analysis and associated documents * Add security-by-default design for tenant-agnostic entities in ADR Enhance Architecture Decision Record to detail explicit requirements for tenant-agnostic database entities, highlighting differences between in-memory activity descriptors and persistent entities. Emphasize importance of setting `TenantId = "*"` to prevent accidental data leakage. * Normalize tenant ID grouping in `ActivityRegistry` to unify null and agnostic IDs, reducing redundant processing. * Refactor `SignalManager`: improve timeout handling and streamline signal task cancellation. * Update src/modules/Elsa.Workflows.Core/Models/TenantRegistryData.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/modules/Elsa.Workflows.Core/Services/ActivityRegistry.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Refactor tests to use `Tenant.AgnosticTenantId` instead of `null` for tenant-agnostic descriptors. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com> Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Enhance logging in recurring tasks: add error handling and logger support to prevent crashes in scheduled timers. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: sfmskywalker <938393+sfmskywalker@users.noreply.github.com>
1 parent afe33ba commit 7bc9035

40 files changed

Lines changed: 1121 additions & 143 deletions

File tree

Elsa.sln

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{7D
2222
EndProjectSection
2323
EndProject
2424
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{0354F050-3992-4DD4-B0EE-5FBA04AC72B6}"
25+
ProjectSection(SolutionItems) = preProject
26+
EndProjectSection
2527
EndProject
2628
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}"
2729
EndProject
@@ -203,6 +205,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "adr", "adr", "{0A04B1FD-06C
203205
doc\adr\0006-tenant-deleted-event.md = doc\adr\0006-tenant-deleted-event.md
204206
doc\adr\0007-adoption-of-explicit-merge-modes-for-flowchart-joins.md = doc\adr\0007-adoption-of-explicit-merge-modes-for-flowchart-joins.md
205207
doc\adr\0008-empty-string-as-default-tenant-id.md = doc\adr\0008-empty-string-as-default-tenant-id.md
208+
doc\adr\0009-asterisk-sentinel-value-for-tenant-agnostic-entities.md = doc\adr\0009-asterisk-sentinel-value-for-tenant-agnostic-entities.md
206209
EndProjectSection
207210
EndProject
208211
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bounty", "bounty", "{9B80A705-2E31-4012-964A-83963DCDB384}"
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# 9. Asterisk Sentinel Value for Tenant-Agnostic Entities
2+
3+
Date: 2026-01-31
4+
5+
## Status
6+
7+
Accepted
8+
9+
## Context
10+
11+
The multitenancy system in Elsa supports both tenant-specific and tenant-agnostic entities. The convention established in ADR-0008 uses an empty string (`""`) as the tenant ID for the default tenant. However, we also need a way to represent entities that are **tenant-agnostic** - entities that should be visible and accessible across all tenants.
12+
13+
Previously, the system did not properly distinguish between:
14+
1. **Default tenant entities** (should only be visible to the default tenant with `TenantId = ""`)
15+
2. **Tenant-agnostic entities** (should be visible to all tenants regardless of their tenant context)
16+
17+
This caused issues where:
18+
- Activity descriptors for built-in activities were being wiped out when different tenants were activated
19+
- The `ActivityRegistry` used a single global dictionary that was replaced during tenant activation via `Interlocked.Exchange`, causing race conditions
20+
- EF Core query filters only matched records with an exact tenant ID match, excluding tenant-agnostic records
21+
- Workflows with no explicit tenant ID could not be found when a specific tenant context was active
22+
23+
### Why Use a Sentinel Value Instead of Null?
24+
25+
We considered using `null` as the marker for tenant-agnostic entities, but chose an explicit sentinel value (`"*"`) instead for several reasons:
26+
27+
1. **Explicit Intent**: A sentinel value makes it crystal clear in code and database queries that an entity is intentionally tenant-agnostic, not accidentally missing a tenant assignment
28+
2. **Simpler Composite Keys**: Avoids nullable handling complexity in composite keys like `(string TenantId, string Type, int Version)` which would become `(string? TenantId, ...)`
29+
3. **Clearer SQL Queries**: Database queries with `TenantId = '*'` are more explicit than `TenantId IS NULL`
30+
4. **Better Logging**: Seeing `"*"` in logs immediately signals tenant-agnostic behavior
31+
5. **Architecture Alignment**: Works seamlessly with the three-dictionary ActivityRegistry architecture where agnostic entities have their own dedicated registry
32+
33+
## Decision
34+
35+
We will use the asterisk character (`"*"`) as a sentinel value to represent **tenant-agnostic entities** - entities that should be accessible across all tenants. This decision includes:
36+
37+
### 1. Convention
38+
39+
- `"*"` (represented by constant `Tenant.AgnosticTenantId`) = tenant-agnostic (visible to all tenants)
40+
- `""` (represented by constant `Tenant.DefaultTenantId`) = default tenant (visible only to default tenant)
41+
- Any other non-null string = specific tenant (visible only to that tenant)
42+
- `null` = not yet assigned (will be normalized to either agnostic or current tenant by handlers)
43+
44+
### 2. Activity Registry Architecture
45+
46+
Implement a **three-dictionary architecture** in `ActivityRegistry` to properly isolate tenant-specific and tenant-agnostic descriptors:
47+
48+
- **`_tenantRegistries`**: `ConcurrentDictionary<string, TenantRegistryData>` - Per-tenant activity descriptors (e.g., workflow-as-activities)
49+
- **`_agnosticRegistry`**: `TenantRegistryData` - Shared tenant-agnostic descriptors (e.g., built-in activities)
50+
- **`_manualActivityDescriptors`**: `ISet<ActivityDescriptor>` - Legacy support for manually registered activities
51+
52+
Key behaviors:
53+
- Descriptors with `TenantId = null` or `TenantId = "*"` are stored in `_agnosticRegistry`
54+
- Descriptors with any other `TenantId` are stored in the corresponding tenant's registry in `_tenantRegistries`
55+
- `RefreshDescriptorsAsync()` updates only the affected tenant's registry, not the entire global dictionary
56+
- Find methods **always prefer tenant-specific descriptors over agnostic ones**, even if agnostic has a higher version number
57+
58+
### 3. EF Core Query Filter
59+
60+
Update `SetTenantIdFilter` to return records where:
61+
- `TenantId == dbContext.TenantId` (tenant-specific match), OR
62+
- `TenantId == "*"` (tenant-agnostic records)
63+
64+
### 4. Entity Handlers
65+
66+
Update `ApplyTenantId` handler to:
67+
- Preserve `TenantId = "*"` (don't overwrite tenant-agnostic entities)
68+
- Only apply current tenant ID to entities with `TenantId = null`
69+
70+
**Important: This is a security-by-default design.** Entities with `null` tenant ID are **never** automatically converted to tenant-agnostic (`"*"`). They are always assigned to the current tenant context. To create tenant-agnostic database entities, developers **must explicitly** set `TenantId = "*"`. This prevents accidental data leakage across tenants.
71+
72+
### 5. Reserved Character Constraint
73+
74+
The asterisk character `"*"` is **reserved** and cannot be used as an actual tenant ID. Tenant creation and validation logic should reject any attempt to create a tenant with ID `"*"`.
75+
76+
## Consequences
77+
78+
### Positive
79+
80+
- **Explicit tenant-agnostic marking**: The `"*"` sentinel makes intent clear in code, logs, and database
81+
- **Proper tenant isolation**: The three-dictionary architecture prevents tenant activation from wiping out other tenants' descriptors
82+
- **No nullable handling**: Composite keys remain `(string TenantId, ...)` instead of `(string? TenantId, ...)`
83+
- **Tenant precedence**: Tenant-specific descriptors always take precedence over agnostic ones, allowing tenants to override built-in activities
84+
- **Dynamic tenant management**: Tenants can be activated and deactivated at runtime without affecting each other
85+
- **Database efficiency**: Tenant-agnostic entities are stored once and accessible to all tenants
86+
- **Clear SQL queries**: `WHERE TenantId = current_tenant OR TenantId = '*'` is more explicit than null checks
87+
- **Thread safety**: Per-tenant dictionaries eliminate the need for `Interlocked.Exchange` and its race conditions
88+
89+
### Negative
90+
91+
- **Reserved character**: The `"*"` character cannot be used as an actual tenant ID (low impact, as tenant IDs are typically alphanumeric)
92+
- **Two conventions**: Developers must understand the distinction between `"*"` (agnostic) and `""` (default tenant)
93+
- **Migration complexity**: Existing systems using `null` for agnostic entities would need data migration
94+
95+
### Neutral
96+
97+
- Using a sentinel value for special cases is a common pattern in software architecture
98+
- The distinction between default tenant and tenant-agnostic is fundamental to proper multitenancy design
99+
- The three-dictionary architecture adds complexity but is necessary for correct tenant isolation
100+
101+
## Implementation Notes
102+
103+
### Semantic Flow: From Entity Creation to Query
104+
105+
Understanding how tenant IDs flow through the system is critical:
106+
107+
```
108+
┌─────────────────────────────────────────────────────────────┐
109+
│ Entity Creation / Deserialization │
110+
├─────────────────────────────────────────────────────────────┤
111+
│ TenantId = null → Not yet assigned │
112+
│ TenantId = "*" → Explicitly agnostic │
113+
│ TenantId = "" → Default tenant │
114+
│ TenantId = "foo" → Specific tenant "foo" │
115+
└─────────────────────────────────────────────────────────────┘
116+
117+
┌─────────────────────────────────────────────────────────────┐
118+
│ ApplyTenantId Handler (before DB save) │
119+
├─────────────────────────────────────────────────────────────┤
120+
│ TenantId = "*" → PRESERVED (agnostic) │
121+
│ TenantId = null → SET to current tenant from context │
122+
│ TenantId = "" → PRESERVED (default tenant) │
123+
│ TenantId = "foo" → PRESERVED (specific tenant) │
124+
└─────────────────────────────────────────────────────────────┘
125+
126+
┌─────────────────────────────────────────────────────────────┐
127+
│ Database Storage │
128+
├─────────────────────────────────────────────────────────────┤
129+
│ TenantId = "*" → Stored as "*" (agnostic) │
130+
│ TenantId = "" → Stored as "" (default tenant) │
131+
│ TenantId = "foo" → Stored as "foo" (specific tenant) │
132+
│ NOTE: No null values in DB after ApplyTenantId handler │
133+
└─────────────────────────────────────────────────────────────┘
134+
135+
┌─────────────────────────────────────────────────────────────┐
136+
│ SetTenantIdFilter (EF Core Query) │
137+
├─────────────────────────────────────────────────────────────┤
138+
│ Returns: TenantId == current_tenant OR TenantId == "*" │
139+
│ Result: Tenant-specific records + agnostic records │
140+
└─────────────────────────────────────────────────────────────┘
141+
142+
┌─────────────────────────────────────────────────────────────┐
143+
│ ActivityRegistry (In-Memory) │
144+
├─────────────────────────────────────────────────────────────┤
145+
│ null or "*" → _agnosticRegistry (shared) │
146+
│ TenantId="" → _tenantRegistries[""] (default) │
147+
│ TenantId=X → _tenantRegistries[X] (specific tenant X) │
148+
└─────────────────────────────────────────────────────────────┘
149+
```
150+
151+
**Key Points:**
152+
- **`null` is transient**: It only exists during entity creation/deserialization before `ApplyTenantId` runs
153+
- **`"*"` is permanent**: Once set, it's preserved and stored in the database as-is
154+
- **`NormalizeTenantId()` converts `null``""`**: This ensures `null` becomes the default tenant, NOT agnostic
155+
- **Database has no nulls**: After `ApplyTenantId` handler, all entities have non-null tenant IDs
156+
- **EF Core filters check for `"*"`**: The query filter explicitly compares against the string `"*"`, not null
157+
- **ActivityRegistry accepts both**: For flexibility, in-memory registry treats both `null` and `"*"` as agnostic
158+
159+
### ActivityRegistry Behavior
160+
161+
When `Find(string type)` is called:
162+
1. First check the current tenant's registry for matching descriptors
163+
2. If found, return the highest version from the tenant-specific registry
164+
3. Only if no tenant-specific descriptor exists, fall back to the agnostic registry
165+
4. This ensures tenant-specific customizations always take precedence
166+
167+
The `GetOrCreateRegistry()` method treats both `null` and `"*"` as agnostic:
168+
```csharp
169+
if (tenantId is null or Tenant.AgnosticTenantId)
170+
return _agnosticRegistry;
171+
```
172+
173+
This provides flexibility for in-memory operations where activity descriptors might temporarily have `null` tenant IDs before normalization.
174+
175+
### Activity Descriptors vs Database Entities: Different Rules
176+
177+
The system treats **in-memory activity descriptors** and **persistent database entities** differently for security and architectural reasons:
178+
179+
#### In-Memory Activity Descriptors (Ephemeral)
180+
- Created on startup by activity providers
181+
- **Built-in activities** (WriteLine, SetVariable, etc.): Created with `TenantId = null` by `ActivityDescriber`
182+
- **Workflow-as-activities**: Created with `TenantId = definition.TenantId` by `WorkflowDefinitionActivityDescriptorFactory`
183+
- `null` is acceptable here because descriptors are recreated on each startup and mapped to `_agnosticRegistry`
184+
- No security risk: descriptors don't contain sensitive data, just metadata about activity types
185+
186+
#### Persistent Database Entities (WorkflowDefinition, etc.)
187+
- Stored permanently in the database
188+
- **Must explicitly set `TenantId = "*"`** to be tenant-agnostic
189+
- `TenantId = null` is **never** converted to `"*"` - always assigned to current tenant
190+
- **Security-by-default**: Prevents accidental data leakage across tenants
191+
- A developer who forgets to set `TenantId` creates a tenant-specific entity, not a global one
192+
193+
**Example - Creating Tenant-Agnostic Workflow:**
194+
```json
195+
{
196+
"tenantId": "*",
197+
"name": "GlobalApprovalWorkflow",
198+
"description": "Shared across all tenants",
199+
"root": { ... }
200+
}
201+
```
202+
203+
**Why This Asymmetry Is Important:**
204+
1. **Safety**: Database entities with null tenant ID default to current tenant (safe)
205+
2. **Explicitness**: Tenant-agnostic entities must be intentional (require `"*"`)
206+
3. **Different lifecycles**: Descriptors are ephemeral, entities are persistent
207+
4. **Backward compatibility**: Built-in activities work without modification
208+
209+
### Workflow Import Behavior
210+
211+
When workflows are imported from providers (e.g., blob storage):
212+
- Workflows without an explicit `tenantId` field in their JSON have `TenantId = null`
213+
- During import, these are normalized to the current tenant ID via `NormalizeTenantId()` extension
214+
- When saved to database, `ApplyTenantId` handler assigns the current tenant from context
215+
- To create truly tenant-agnostic workflows, explicitly set `"tenantId": "*"` in the workflow JSON
216+
- The `"*"` value will be preserved through import, save, and query operations
217+
218+
### Testing Considerations
219+
220+
- Component tests use the default tenant (`""`)
221+
- Built-in activities use the agnostic marker (`"*"`)
222+
- Tenant-specific tests should create explicit tenant contexts to verify proper isolation
223+
- Unit tests should verify that `"*"` is preserved through save operations
224+
- Integration tests should verify that `"*"` entities are returned for all tenant contexts

doc/adr/graph.dot

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
digraph {
2-
node [shape=plaintext];
3-
subgraph {
4-
_1 [label="1. Record architecture decisions"; URL="0001-record-architecture-decisions.html"];
5-
_2 [label="2. Fault Propagation from Child to Parent Activities"; URL="0002-fault-propagation-from-child-to-parent-activities.html"];
6-
_1 -> _2 [style="dotted", weight=1];
7-
_3 [label="3. Direct Bookmark Management in WorkflowExecutionContext"; URL="0003-direct-bookmark-management-in-workflowexecutioncontext.html"];
8-
_2 -> _3 [style="dotted", weight=1];
9-
_4 [label="4. Activity Execution Snapshots"; URL="0004-activity-execution-snapshots.html"];
10-
_3 -> _4 [style="dotted", weight=1];
11-
_5 [label="5. Token-Centric Flowchart Execution Model"; URL="0005-token-centric-flowchart-execution-model.html"];
12-
_4 -> _5 [style="dotted", weight=1];
13-
_6 [label="6. Tenant Deleted Event"; URL="0006-tenant-deleted-event.html"];
14-
_5 -> _6 [style="dotted", weight=1];
15-
_7 [label="7. Adoption of Explicit Merge Modes for Flowchart Joins"; URL="0007-adoption-of-explicit-merge-modes-for-flowchart-joins.html"];
16-
_6 -> _7 [style="dotted", weight=1];
17-
_8 [label="8. Empty String as Default Tenant ID"; URL="0008-empty-string-as-default-tenant-id.html"];
18-
_7 -> _8 [style="dotted", weight=1];
19-
}
2+
node [shape=plaintext];
3+
subgraph {
4+
_1 [label="1. Record architecture decisions"; URL="0001-record-architecture-decisions.html"];
5+
_2 [label="2. Fault Propagation from Child to Parent Activities"; URL="0002-fault-propagation-from-child-to-parent-activities.html"];
6+
_1 -> _2 [style="dotted", weight=1];
7+
_3 [label="3. Direct Bookmark Management in WorkflowExecutionContext"; URL="0003-direct-bookmark-management-in-workflowexecutioncontext.html"];
8+
_2 -> _3 [style="dotted", weight=1];
9+
_4 [label="4. Activity Execution Snapshots"; URL="0004-activity-execution-snapshots.html"];
10+
_3 -> _4 [style="dotted", weight=1];
11+
_5 [label="5. Token-Centric Flowchart Execution Model"; URL="0005-token-centric-flowchart-execution-model.html"];
12+
_4 -> _5 [style="dotted", weight=1];
13+
_6 [label="6. Tenant Deleted Event"; URL="0006-tenant-deleted-event.html"];
14+
_5 -> _6 [style="dotted", weight=1];
15+
_7 [label="7. Adoption of Explicit Merge Modes for Flowchart Joins"; URL="0007-adoption-of-explicit-merge-modes-for-flowchart-joins.html"];
16+
_6 -> _7 [style="dotted", weight=1];
17+
_8 [label="8. Empty String as Default Tenant ID"; URL="0008-empty-string-as-default-tenant-id.html"];
18+
_7 -> _8 [style="dotted", weight=1];
19+
_9 [label="9. Asterisk Sentinel Value for Tenant-Agnostic Entities"; URL="0009-asterisk-sentinel-value-for-tenant-agnostic-entities.html"];
20+
_8 -> _9 [style="dotted", weight=1];
21+
}
2022
}

doc/adr/toc.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
* [5. Token-Centric Flowchart Execution Model](0005-token-centric-flowchart-execution-model.md)
88
* [6. Tenant Deleted Event](0006-tenant-deleted-event.md)
99
* [7. Adoption of Explicit Merge Modes for Flowchart Joins](0007-adoption-of-explicit-merge-modes-for-flowchart-joins.md)
10-
* [8. Empty String as Default Tenant ID](0008-empty-string-as-default-tenant-id.md)
10+
* [8. Empty String as Default Tenant ID](0008-empty-string-as-default-tenant-id.md)
11+
* [9. Asterisk Sentinel Value for Tenant-Agnostic Entities](0009-asterisk-sentinel-value-for-tenant-agnostic-entities.md)

agent-logs/7019/2025-11-20_configuration-binding-issue_enumerable-type-converter.md renamed to doc/agent-logs/7019/2025-11-20_configuration-binding-issue_enumerable-type-converter.md

File renamed without changes.

agent-logs/7077/2025-11-20_trigger-deletion-exception-handling.md renamed to doc/agent-logs/7077/2025-11-20_trigger-deletion-exception-handling.md

File renamed without changes.

agent-logs/7077/2025-11-20_workflow-instance-deletion_runtime-coordination.md renamed to doc/agent-logs/7077/2025-11-20_workflow-instance-deletion_runtime-coordination.md

File renamed without changes.

src/apps/Elsa.Server.Web/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
// ReSharper disable RedundantAssignment
3232
const bool useReadOnlyMode = false;
3333
const bool useSignalR = false; // Disabled until Elsa Studio sends authenticated requests.
34-
const bool useMultitenancy = false;
34+
const bool useMultitenancy = true;
3535
const bool disableVariableWrappers = false;
3636

3737
ObjectConverter.StrictMode = true;

0 commit comments

Comments
 (0)