{"record":{"id":"6f96452a814d7688","repo":"elsa-workflows/elsa-core","slug":"role-roleid-resolves-to-roles-length-roles-across-tenant","errorCode":null,"errorMessage":"Role '{roleId}' resolves to {roles.Length} roles across tenant scopes; the deletion target is ambiguous and its external-authentication dependencies cannot be determined.","messagePattern":"Role '(.+?)' resolves to (.+?) roles across tenant scopes; the deletion target is ambiguous and its external-authentication dependencies cannot be determined\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.ExternalAuthentication/Services/ExternalAuthenticationRoleDeletionDependencyContributor.cs","lineNumber":283,"sourceCode":"    /// tenant context is every tenant). In EF Core persistence a role ID is unique across all tenants (the\n    /// <c>Roles</c> table keys on <c>Id</c> alone), so this lookup resolves to at most one role there. Only\n    /// <c>MemoryRoleStore</c> can hold two roles that share an ID because its storage key includes the tenant;\n    /// if the ID resolves to more than one role, which tenant's role the coordinator's own delete actually\n    /// targets is already ambiguous, and this method cannot make the operation consistent by guessing in either\n    /// direction -- widening would expose another tenant's references for what may be a tenant-scoped deletion,\n    /// and narrowing would leave an agnostic role's references dangling. It fails closed instead.\n    /// A missing store or no matching role falls back to the ambient tenant on <see cref=\"ITenantAccessor\"/>,\n    /// which is the only case where the ambient tenant is trusted: the role cannot be resolved at all, so there\n    /// is no resolved tenant to prefer over it.\n    /// </summary>\n    private async ValueTask<string> ResolveRoleTenantIdAsync(string roleId, CancellationToken cancellationToken)\n    {\n        var roles = await FindRolesByIdAsync(roleId, cancellationToken);\n        return roles.Length switch\n        {\n            0 => tenantAccessor.TenantId.NormalizeTenantId(),\n            1 => roles[0].TenantId.NormalizeTenantId(),\n            _ => throw new InvalidOperationException(\n                $\"Role '{roleId}' resolves to {roles.Length} roles across tenant scopes; the deletion target is ambiguous and its external-authentication dependencies cannot be determined.\")\n        };\n    }\n\n    /// <summary>\n    /// Resolves whether a candidate role ID (typically a replacement role) is itself tenant-agnostic. Unlike\n    /// <see cref=\"ResolveRoleTenantIdAsync\"/>, an ambiguous candidate -- a role ID that resolves to more than one\n    /// role, which only <c>MemoryRoleStore</c> can produce -- is not the coordinator's own deletion target, so\n    /// there is no operation to fail closed on by throwing; it is instead treated the same as an unresolved\n    /// candidate and reported as not agnostic, since there is no single resolved role to trust as safe to write\n    /// into every tenant's connections.\n    /// </summary>\n    private async ValueTask<bool> IsAgnosticRoleAsync(string? roleId, CancellationToken cancellationToken)\n    {\n        if (string.IsNullOrWhiteSpace(roleId))\n            return false;\n        var roles = await FindRolesByIdAsync(roleId, cancellationToken);\n        return roles.Length == 1 && string.Equals(roles[0].TenantId.NormalizeTenantId(), Tenant.AgnosticTenantId, StringComparison.Ordinal);","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.ExternalAuthentication/Services/ExternalAuthenticationRoleDeletionDependencyContributor.cs#L265-L301","documentation":"Error \"Role '{roleId}' resolves to {roles.Length} roles across tenant scopes; the deletion target is ambiguous and its external-authentication dependencies cannot be determined.\" thrown in elsa-workflows/elsa-core.","triggerScenarios":"Thrown at src/modules/Elsa.ExternalAuthentication/Services/ExternalAuthenticationRoleDeletionDependencyContributor.cs:283 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Deduplicate or rename the roles so the role ID is unique across tenants in the store.","Delete the intended role through a tenant-scoped API so exactly one role matches the ID.","If using MemoryRoleStore in tests, give each tenant's role a distinct ID, mirroring the EF Core uniqueness constraint."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}