Repository: Redocly/redocly-cli
Pull request #1620 (2024-07-10 10:29:32)

feat: add asyncapi 3.0
## What/Why/How?
Add supporting asyncAPI 3.0
## Reference

## Testing

## Screenshots (optional)

## Check yourself

- [ ] Code changed? - Tested with redoc/reference-docs/workflows (internal)
- [ ] All new/updated code is covered with tests
- [ ] New package installed? - Tested in different environments (browser/node)

## Security

- [x] Security impact of change has been considered
- [x] Code follows company security practices and guidelines


changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: e40fe1ce48c54d7c528f0552d15a9fc1a9056ba9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/Redocly/redocly-cli/new/feat/add-asyncapi-3.0?filename=.changeset/tall-carrots-count.md&value=---%0A%22%40redocly%2Fcli%22%3A%20patch%0A%22%40redocly%2Fopenapi-core%22%3A%20patch%0A---%0A%0AFeat%2Fadd%20asyncapi%203.0%0A)


github-actions[bot]: | Command | Mean [s] | Min [s] | Max [s] | Relative |
|:---|---:|---:|---:|---:|
| `redocly lint packages/core/src/benchmark/benches/rebilly.yaml` | 1.006 ± 0.077 | 0.960 | 1.218 | 1.01 ± 0.08 |
| `redocly-next lint packages/core/src/benchmark/benches/rebilly.yaml` | 0.997 ± 0.015 | 0.974 | 1.023 | 1.00 |

<!-- thollander/actions-comment-pull-request "latest-vs-next-comparison" -->
github-actions[bot]: <!-- jest coverage report action for options with hash 3339ae53a38c44382c0fb4491bdb282d -->

## Coverage report 

❌ An unexpected error occurred. For more details, [check console](https://github.com/Redocly/redocly-cli/actions/runs/9872560769) 
```
Error: The process '/opt/hostedtoolcache/node/20.15.0/x64/bin/npm' failed with exit code 1
```

| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | Category   | Percentage | Covered / Total |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------- | :--------- | :-------------: |
|                                                                           🟡                                                                           | Statements | 77.3%      |    4772/6173    |
|                                                                           🟡                                                                           | Branches   | 66.45%     |    2561/3854    |
|                                                                           🟡                                                                           | Functions  | 69.72%     |     760/1090    |
|                                                                           🟡                                                                           | Lines      | 77.53%     |    4502/5807    |

<details><summary>Show new covered files 🐣</summary>
<br/>

| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | File                                                                                                       | Statements | Branches | Functions | Lines  |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------------------------------- | :--------- | :------- | :-------- | :----- |
|                                                                           🟡                                                                           | <div title="core/src/types/asyncapi2.ts">`...` / asyncapi2.ts</div>                                        | 79.58%     | 0%       | 0%        | 79.58% |
|                                                                           🟡                                                                           | <div title="core/src/types/asyncapi3.ts">`...` / asyncapi3.ts</div>                                        | 77.95%     | 0%       | 0%        | 77.95% |
|                                                                           🟢                                                                           | <div title="core/src/rules/async3/index.ts">`...` / index.ts</div>                                         | 100%       | 100%     | 100%      | 100%   |
|                                                                           🟢                                                                           | <div title="core/src/rules/async3/channels-kebab-case.ts">`...` / channels-kebab-case.ts</div>             | 100%       | 80%      | 100%      | 100%   |
|                                                                           🟢                                                                           | <div title="core/src/rules/async3/no-channel-trailing-slash.ts">`...` / no-channel-trailing-slash.ts</div> | 100%       | 100%     | 100%      | 100%   |

</details>


<details><summary>Show files with reduced coverage 🔻</summary>
<br/>

| <div title="Status of coverage:&#10; 🟢 - ok&#10; 🟡 - slightly more than threshold&#10; 🔴 - under the threshold">St.<sup>:grey_question:</sup></div> | File                                                                               | Statements                                                  | Branches                                                       | Functions | Lines                                                       |
| :----------------------------------------------------------------------------------------------------------------------------------------------------: | :--------------------------------------------------------------------------------- | :---------------------------------------------------------- | :------------------------------------------------------------- | :-------- | :---------------------------------------------------------- |
|                                                                           🟢                                                                           | <div title="core/src/config/config-resolvers.ts">`...` / config-resolvers.ts</div> | 84.53%                                                      | 75.42%                                                         | 100%      | 85.14%                                                      |
|                                                                           🟡                                                                           | core/src/bundle.ts                                                                 | <div title="Base coverage is: 75%">72.22% (-2.78% 🔻)</div> | <div title="Base coverage is: 62.71%">58.73% (-3.98% 🔻)</div> | 94.12%    | <div title="Base coverage is: 75%">72.22% (-2.78% 🔻)</div> |
|                                                                           🟢                                                                           | core/src/index.ts                                                                  | 100%                                                        | 100%                                                           | 8.77%     | 100%                                                        |

</details>

## Test suite run failed

<details><summary>Failed tests: 1/759. Failed suites: 1/108.</summary>
<br/>

```
  ● resolveStyleguideConfig › should resolve `recommended-strict` ruleset correctly

    expect(received).toMatchObject(expected)

    - Expected  - 2
    + Received  + 2

    @@ -10,13 +10,13 @@
          "spec": "error",
          "tag-description": "error",
          "tags-alphabetical": "off",
        },
        "async3Rules": Object {
    -     "channels-kebab-case": "error",
    +     "channels-kebab-case": "off",
          "info-contact": "off",
    -     "no-channel-trailing-slash": "error",
    +     "no-channel-trailing-slash": "off",
          "operation-operationId": "error",
          "spec": "error",
          "tag-description": "error",
          "tags-alphabetical": "off",
        },

      248 |       )
      249 |     );
    > 250 |     expect(recommendedStrictPreset).toMatchObject(expectedStrict);
          |                                     ^
      251 |   });
      252 | });
      253 |

      at packages/core/src/config/__tests__/config-resolvers.test.ts:250:37
      at fulfilled (packages/core/src/config/__tests__/config-resolvers.test.ts:5:58)

```

</details>


<p align="right">Report generated by <a href="https://github.com/ArtiomTr/jest-coverage-report-action">🧪jest coverage report action</a> from e40fe1ce48c54d7c528f0552d15a9fc1a9056ba9</p>

lornajane: I am so happy to see this feature landing! Does this new AsyncAPI 3.0 support include bundling, or is it lint-only? There's a limitation where descriptions use `$ref` - details are in #1543, but I can't tell from the source if we tackled that in this pull request.
AlexVarchuk: Hi @lornajane. It includes improvements for `bundle` command. I tested it using our internal description. It doesn't cover all cases. If you have description to reproduce https://github.com/Redocly/redocly-cli/issues/1543 please send me and I'll check it.

Patch:
diff --git a/.changeset/quiet-items-sparkle.md b/.changeset/quiet-items-sparkle.md
new file mode 100644
index 00000000..0fb2a7eb
--- /dev/null
+++ b/.changeset/quiet-items-sparkle.md
@@ -0,0 +1,6 @@
+---
+"@redocly/openapi-core": minor
+"@redocly/cli": minor
+---
+
+Added support for AsyncAPI 3.0 description linting.
diff --git a/docs/guides/lint-asyncapi.md b/docs/guides/lint-asyncapi.md
index db68d232..4f273fed 100644
--- a/docs/guides/lint-asyncapi.md
+++ b/docs/guides/lint-asyncapi.md
@@ -15,6 +15,7 @@ Redocly CLI supports the following linting approaches with AsyncAPI documents:
 
 - AsyncAPI document validation, including full binding validation for [supported protocols](#supported-protocols).
 - Supported versions:
+  - [AsyncAPI 3.0](https://www.asyncapi.com/docs/reference/specification/v3.0.0)
   - [AsyncAPI 2.6](https://v2.asyncapi.com/docs/reference/specification/v2.6.0)
   - earlier versions in the 2.x family may also validate successfully
 - Built-in rules for checking common standards requirements (see the [list of AsyncAPI rules](#asyncapi-rules)).
diff --git a/packages/cli/src/__mocks__/@redocly/openapi-core.ts b/packages/cli/src/__mocks__/@redocly/openapi-core.ts
index e89a8d75..a77f6493 100644
--- a/packages/cli/src/__mocks__/@redocly/openapi-core.ts
+++ b/packages/cli/src/__mocks__/@redocly/openapi-core.ts
@@ -73,6 +73,7 @@ export enum SpecVersion {
   OAS3_0 = 'oas3_0',
   OAS3_1 = 'oas3_1',
   Async2 = 'async2',
+  Async3 = 'async3',
 }
 
 export enum Oas3Operations {
diff --git a/packages/cli/src/utils/miscellaneous.ts b/packages/cli/src/utils/miscellaneous.ts
index ab0f23f5..2e6a42fb 100644
--- a/packages/cli/src/utils/miscellaneous.ts
+++ b/packages/cli/src/utils/miscellaneous.ts
@@ -522,6 +522,7 @@ export function checkIfRulesetExist(rules: typeof StyleguideConfig.prototype.rul
     ...rules.oas3_0,
     ...rules.oas3_1,
     ...rules.async2,
+    ...rules.async3,
     ...rules.arazzo,
   };
 
diff --git a/packages/core/src/bundle.ts b/packages/core/src/bundle.ts
index 28c0647e..9de168b0 100755
--- a/packages/core/src/bundle.ts
+++ b/packages/core/src/bundle.ts
@@ -292,6 +292,15 @@ export function mapTypeToComponent(typeName: string, version: SpecMajorVersion)
         default:
           return null;
       }
+    case SpecMajorVersion.Async3:
+      switch (typeName) {
+        case 'Schema':
+          return 'schemas';
+        case 'Parameter':
+          return 'parameters';
+        default:
+          return null;
+      }
     case SpecMajorVersion.Arazzo:
       switch (typeName) {
         case 'Root.x-parameters_items':
@@ -377,6 +386,8 @@ function makeBundleVisitor(
           components = root;
         } else if (version === SpecMajorVersion.Async2) {
           components = root.components = root.components || {};
+        } else if (version === SpecMajorVersion.Async3) {
+          components = root.components = root.components || {};
         } else if (version === SpecMajorVersion.Arazzo) {
           components = root.components = root.components || {};
         }
@@ -427,6 +438,8 @@ function makeBundleVisitor(
     components[componentType][name] = target.node;
     if (version === SpecMajorVersion.OAS3) {
       return `#/components/${componentType}/${name}`;
+    } else if (version === SpecMajorVersion.Async2 || version === SpecMajorVersion.Async3) {
+      return `#/components/${componentType}/${name}`;
     } else {
       return `#/${componentType}/${name}`;
     }
diff --git a/packages/core/src/config/all.ts b/packages/core/src/config/all.ts
index 6a5c0cc9..31d4e0e2 100644
--- a/packages/core/src/config/all.ts
+++ b/packages/core/src/config/all.ts
@@ -114,6 +114,15 @@ const all: PluginStyleguideConfig<'built-in'> = {
     'channels-kebab-case': 'error',
     'no-channel-trailing-slash': 'error',
   },
+  async3Rules: {
+    spec: 'error',
+    'info-contact': 'error',
+    'operation-operationId': 'error',
+    'tag-description': 'error',
+    'tags-alphabetical': 'error',
+    'channels-kebab-case': 'error',
+    'no-channel-trailing-slash': 'error',
+  },
   arazzoRules: { spec: 'error' },
 };
 
diff --git a/packages/core/src/config/builtIn.ts b/packages/core/src/config/builtIn.ts
index d7a4db65..c8c50e73 100644
--- a/packages/core/src/config/builtIn.ts
+++ b/packages/core/src/config/builtIn.ts
@@ -5,14 +5,17 @@ import minimal from './minimal';
 import { rules as oas3Rules } from '../rules/oas3';
 import { rules as oas2Rules } from '../rules/oas2';
 import { rules as async2Rules } from '../rules/async2';
+import { rules as async3Rules } from '../rules/async3';
 import { rules as arazzoRules } from '../rules/arazzo';
 import { preprocessors as oas3Preprocessors } from '../rules/oas3';
 import { preprocessors as oas2Preprocessors } from '../rules/oas2';
 import { preprocessors as async2Preprocessors } from '../rules/async2';
+import { preprocessors as async3Preprocessors } from '../rules/async3';
 import { preprocessors as arazzoPreprocessors } from '../rules/arazzo';
 import { decorators as oas3Decorators } from '../decorators/oas3';
 import { decorators as oas2Decorators } from '../decorators/oas2';
 import { decorators as async2Decorators } from '../decorators/async2';
+import { decorators as async3Decorators } from '../decorators/async3';
 import { decorators as arazzoDecorators } from '../decorators/arazzo';
 
 import type { CustomRulesConfig, StyleguideRawConfig, Plugin } from './types';
@@ -33,18 +36,21 @@ export const defaultPlugin: Plugin = {
     oas3: oas3Rules,
     oas2: oas2Rules,
     async2: async2Rules,
+    async3: async3Rules,
     arazzo: arazzoRules,
   } as CustomRulesConfig,
   preprocessors: {
     oas3: oas3Preprocessors,
     oas2: oas2Preprocessors,
     async2: async2Preprocessors,
+    async3: async3Preprocessors,
     arazzo: arazzoPreprocessors,
   },
   decorators: {
     oas3: oas3Decorators,
     oas2: oas2Decorators,
     async2: async2Decorators,
+    async3: async3Decorators,
     arazzo: arazzoDecorators,
   },
   configs: builtInConfigs,
diff --git a/packages/core/src/config/config-resolvers.ts b/packages/core/src/config/config-resolvers.ts
index ff4d3dbf..1edd85cd 100644
--- a/packages/core/src/config/config-resolvers.ts
+++ b/packages/core/src/config/config-resolvers.ts
@@ -181,7 +181,7 @@ export function resolvePlugins(
       if (pluginModule.rules) {
         if (!pluginModule.rules.oas3 && !pluginModule.rules.oas2 && !pluginModule.rules.async2) {
           throw new Error(
-            `Plugin rules must have \`oas3\`, \`oas2\`, \`async2\`, or \`arazzo\` rules "${p}.`
+            `Plugin rules must have \`oas3\`, \`oas2\`, \`async2\`, \`async3\` or \`arazzo\` rules "${p}.`
           );
         }
         plugin.rules = {};
@@ -194,6 +194,9 @@ export function resolvePlugins(
         if (pluginModule.rules.async2) {
           plugin.rules.async2 = prefixRules(pluginModule.rules.async2, id);
         }
+        if (pluginModule.rules.async3) {
+          plugin.rules.async3 = prefixRules(pluginModule.rules.async3, id);
+        }
         if (pluginModule.rules.arazzo) {
           plugin.rules.arazzo = prefixRules(pluginModule.rules.arazzo, id);
         }
@@ -203,6 +206,7 @@ export function resolvePlugins(
           !pluginModule.preprocessors.oas3 &&
           !pluginModule.preprocessors.oas2 &&
           !pluginModule.preprocessors.async2 &&
+          !pluginModule.preprocessors.async3 &&
           !pluginModule.preprocessors.arazzo
         ) {
           throw new Error(
@@ -219,6 +223,9 @@ export function resolvePlugins(
         if (pluginModule.preprocessors.async2) {
           plugin.preprocessors.async2 = prefixRules(pluginModule.preprocessors.async2, id);
         }
+        if (pluginModule.preprocessors.async3) {
+          plugin.preprocessors.async3 = prefixRules(pluginModule.preprocessors.async3, id);
+        }
         if (pluginModule.preprocessors.arazzo) {
           plugin.preprocessors.arazzo = prefixRules(pluginModule.preprocessors.arazzo, id);
         }
@@ -229,10 +236,11 @@ export function resolvePlugins(
           !pluginModule.decorators.oas3 &&
           !pluginModule.decorators.oas2 &&
           !pluginModule.decorators.async2 &&
+          !pluginModule.decorators.async3 &&
           !pluginModule.decorators.arazzo
         ) {
           throw new Error(
-            `Plugin \`decorators\` must have \`oas3\`, \`oas2\` or \`async2\` decorators "${p}.`
+            `Plugin \`decorators\` must have \`oas3\`, \`oas2\`, \`async2\` or \`async3\` decorators "${p}.`
           );
         }
         plugin.decorators = {};
@@ -245,6 +253,9 @@ export function resolvePlugins(
         if (pluginModule.decorators.async2) {
           plugin.decorators.async2 = prefixRules(pluginModule.decorators.async2, id);
         }
+        if (pluginModule.decorators.async3) {
+          plugin.decorators.async3 = prefixRules(pluginModule.decorators.async3, id);
+        }
         if (pluginModule.decorators.arazzo) {
           plugin.decorators.arazzo = prefixRules(pluginModule.decorators.arazzo, id);
         }
@@ -432,6 +443,8 @@ function getMergedRawStyleguideConfig(
     oas2Rules: { ...rootStyleguideConfig?.oas2Rules, ...apiStyleguideConfig?.oas2Rules },
     oas3_0Rules: { ...rootStyleguideConfig?.oas3_0Rules, ...apiStyleguideConfig?.oas3_0Rules },
     oas3_1Rules: { ...rootStyleguideConfig?.oas3_1Rules, ...apiStyleguideConfig?.oas3_1Rules },
+    async2Rules: { ...rootStyleguideConfig?.async2Rules, ...apiStyleguideConfig?.async2Rules },
+    async3Rules: { ...rootStyleguideConfig?.async3Rules, ...apiStyleguideConfig?.async3Rules },
     arazzoRules: { ...rootStyleguideConfig?.arazzoRules, ...apiStyleguideConfig?.arazzoRules },
     preprocessors: {
       ...rootStyleguideConfig?.preprocessors,
diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts
index 32db1754..c3f39551 100755
--- a/packages/core/src/config/config.ts
+++ b/packages/core/src/config/config.ts
@@ -9,6 +9,7 @@ import {
   Oas2RuleSet,
   Oas3RuleSet,
   Async2RuleSet,
+  Async3RuleSet,
   ArazzoRuleSet,
 } from '../oas-types';
 import { isBrowser } from '../env';
@@ -72,6 +73,7 @@ export class StyleguideConfig {
       [SpecVersion.OAS3_0]: { ...rawConfig.rules, ...rawConfig.oas3_0Rules },
       [SpecVersion.OAS3_1]: { ...rawConfig.rules, ...rawConfig.oas3_1Rules },
       [SpecVersion.Async2]: { ...rawConfig.rules, ...rawConfig.async2Rules },
+      [SpecVersion.Async3]: { ...rawConfig.rules, ...rawConfig.async3Rules },
       [SpecVersion.Arazzo]: { ...rawConfig.arazzoRules },
     };
 
@@ -80,6 +82,7 @@ export class StyleguideConfig {
       [SpecVersion.OAS3_0]: { ...rawConfig.preprocessors, ...rawConfig.oas3_0Preprocessors },
       [SpecVersion.OAS3_1]: { ...rawConfig.preprocessors, ...rawConfig.oas3_1Preprocessors },
       [SpecVersion.Async2]: { ...rawConfig.preprocessors, ...rawConfig.async2Preprocessors },
+      [SpecVersion.Async3]: { ...rawConfig.preprocessors, ...rawConfig.async3Preprocessors },
       [SpecVersion.Arazzo]: { ...rawConfig.arazzoPreprocessors },
     };
 
@@ -88,6 +91,7 @@ export class StyleguideConfig {
       [SpecVersion.OAS3_0]: { ...rawConfig.decorators, ...rawConfig.oas3_0Decorators },
       [SpecVersion.OAS3_1]: { ...rawConfig.decorators, ...rawConfig.oas3_1Decorators },
       [SpecVersion.Async2]: { ...rawConfig.decorators, ...rawConfig.async2Decorators },
+      [SpecVersion.Async3]: { ...rawConfig.decorators, ...rawConfig.async3Decorators },
       [SpecVersion.Arazzo]: { ...rawConfig.arazzoDecorators },
     };
 
@@ -182,6 +186,10 @@ export class StyleguideConfig {
             if (!plugin.typeExtension.async2) continue;
             extendedTypes = plugin.typeExtension.async2(extendedTypes, version);
             break;
+          case SpecVersion.Async3:
+            if (!plugin.typeExtension.async3) continue;
+            extendedTypes = plugin.typeExtension.async3(extendedTypes, version);
+            break;
           case SpecVersion.Arazzo:
             if (!plugin.typeExtension.arazzo) continue;
             extendedTypes = plugin.typeExtension.arazzo(extendedTypes, version);
@@ -276,15 +284,26 @@ export class StyleguideConfig {
         return oas2Rules;
       case SpecMajorVersion.Async2:
         // eslint-disable-next-line no-case-declarations
-        const asyncApiRules: Async2RuleSet[] = []; // default ruleset
+        const asyncApi2Rules: Async2RuleSet[] = []; // default ruleset
+        this.plugins.forEach(
+          (p) => p.preprocessors?.async2 && asyncApi2Rules.push(p.preprocessors.async2)
+        );
+        this.plugins.forEach((p) => p.rules?.async2 && asyncApi2Rules.push(p.rules.async2));
+        this.plugins.forEach(
+          (p) => p.decorators?.async2 && asyncApi2Rules.push(p.decorators.async2)
+        );
+        return asyncApi2Rules;
+      case SpecMajorVersion.Async3:
+        // eslint-disable-next-line no-case-declarations
+        const asyncApi3Rules: Async3RuleSet[] = []; // default ruleset
         this.plugins.forEach(
-          (p) => p.preprocessors?.async2 && asyncApiRules.push(p.preprocessors.async2)
+          (p) => p.preprocessors?.async3 && asyncApi3Rules.push(p.preprocessors.async3)
         );
-        this.plugins.forEach((p) => p.rules?.async2 && asyncApiRules.push(p.rules.async2));
+        this.plugins.forEach((p) => p.rules?.async3 && asyncApi3Rules.push(p.rules.async3));
         this.plugins.forEach(
-          (p) => p.decorators?.async2 && asyncApiRules.push(p.decorators.async2)
+          (p) => p.decorators?.async3 && asyncApi3Rules.push(p.decorators.async3)
         );
-        return asyncApiRules;
+        return asyncApi3Rules;
       case SpecMajorVersion.Arazzo:
         // eslint-disable-next-line no-case-declarations
         const arazzoRules: ArazzoRuleSet[] = []; // default ruleset
diff --git a/packages/core/src/config/minimal.ts b/packages/core/src/config/minimal.ts
index 6d780e3a..16c2e269 100644
--- a/packages/core/src/config/minimal.ts
+++ b/packages/core/src/config/minimal.ts
@@ -96,6 +96,15 @@ const minimal: PluginStyleguideConfig<'built-in'> = {
     'channels-kebab-case': 'off',
     'no-channel-trailing-slash': 'off',
   },
+  async3Rules: {
+    spec: 'error',
+    'info-contact': 'off',
+    'operation-operationId': 'warn',
+    'tag-description': 'warn',
+    'tags-alphabetical': 'off',
+    'channels-kebab-case': 'off',
+    'no-channel-trailing-slash': 'off',
+  },
   arazzoRules: {
     spec: 'error',
   },
diff --git a/packages/core/src/config/recommended-strict.ts b/packages/core/src/config/recommended-strict.ts
index ac03221a..b5482f54 100644
--- a/packages/core/src/config/recommended-strict.ts
+++ b/packages/core/src/config/recommended-strict.ts
@@ -96,6 +96,15 @@ const recommendedStrict: PluginStyleguideConfig<'built-in'> = {
     'channels-kebab-case': 'off',
     'no-channel-trailing-slash': 'off',
   },
+  async3Rules: {
+    spec: 'error',
+    'info-contact': 'off',
+    'operation-operationId': 'error',
+    'tag-description': 'error',
+    'tags-alphabetical': 'off',
+    'channels-kebab-case': 'off',
+    'no-channel-trailing-slash': 'off',
+  },
   arazzoRules: {
     spec: 'error',
   },
diff --git a/packages/core/src/config/recommended.ts b/packages/core/src/config/recommended.ts
index 6b615409..df79a866 100644
--- a/packages/core/src/config/recommended.ts
+++ b/packages/core/src/config/recommended.ts
@@ -96,6 +96,15 @@ const recommended: PluginStyleguideConfig<'built-in'> = {
     'channels-kebab-case': 'off',
     'no-channel-trailing-slash': 'off',
   },
+  async3Rules: {
+    spec: 'error',
+    'info-contact': 'off',
+    'operation-operationId': 'warn',
+    'tag-description': 'warn',
+    'tags-alphabetical': 'off',
+    'channels-kebab-case': 'off',
+    'no-channel-trailing-slash': 'off',
+  },
   arazzoRules: {
     spec: 'error',
   },
diff --git a/packages/core/src/config/types.ts b/packages/core/src/config/types.ts
index 778ec7d6..6a899d9e 100644
--- a/packages/core/src/config/types.ts
+++ b/packages/core/src/config/types.ts
@@ -11,6 +11,9 @@ import type {
   Async2PreprocessorsSet,
   Async2DecoratorsSet,
   Async2RuleSet,
+  Async3PreprocessorsSet,
+  Async3DecoratorsSet,
+  Async3RuleSet,
   ArazzoRuleSet,
   ArazzoPreprocessorsSet,
   ArazzoDecoratorsSet,
@@ -22,6 +25,7 @@ import { Location } from '../ref-utils';
 import type { SkipFunctionContext } from '../visitors';
 import {
   BuiltInAsync2RuleId,
+  BuiltInAsync3RuleId,
   BuiltInCommonOASRuleId,
   BuiltInOAS2RuleId,
   BuiltInOAS3RuleId,
@@ -59,6 +63,7 @@ export type StyleguideRawConfig<T = undefined> = {
   oas3_0Rules?: RuleMap<BuiltInOAS3RuleId, RuleConfig, T>;
   oas3_1Rules?: RuleMap<BuiltInOAS3RuleId, RuleConfig, T>;
   async2Rules?: RuleMap<BuiltInAsync2RuleId, RuleConfig, T>;
+  async3Rules?: RuleMap<BuiltInAsync3RuleId, RuleConfig, T>;
   arazzoRules?: RuleMap<BuiltInArazzoRuleId, RuleConfig, T>;
 
   preprocessors?: Record<string, PreprocessorConfig>;
@@ -66,6 +71,7 @@ export type StyleguideRawConfig<T = undefined> = {
   oas3_0Preprocessors?: Record<string, PreprocessorConfig>;
   oas3_1Preprocessors?: Record<string, PreprocessorConfig>;
   async2Preprocessors?: Record<string, PreprocessorConfig>;
+  async3Preprocessors?: Record<string, PreprocessorConfig>;
   arazzoPreprocessors?: Record<string, PreprocessorConfig>;
 
   decorators?: Record<string, DecoratorConfig>;
@@ -73,6 +79,7 @@ export type StyleguideRawConfig<T = undefined> = {
   oas3_0Decorators?: Record<string, DecoratorConfig>;
   oas3_1Decorators?: Record<string, DecoratorConfig>;
   async2Decorators?: Record<string, DecoratorConfig>;
+  async3Decorators?: Record<string, DecoratorConfig>;
   arazzoDecorators?: Record<string, DecoratorConfig>;
 };
 
@@ -90,6 +97,7 @@ export type PreprocessorsConfig = {
   oas3?: Oas3PreprocessorsSet;
   oas2?: Oas2PreprocessorsSet;
   async2?: Async2PreprocessorsSet;
+  async3?: Async3PreprocessorsSet;
   arazzo?: ArazzoPreprocessorsSet;
 };
 
@@ -97,6 +105,7 @@ export type DecoratorsConfig = {
   oas3?: Oas3DecoratorsSet;
   oas2?: Oas2DecoratorsSet;
   async2?: Async2DecoratorsSet;
+  async3?: Async3DecoratorsSet;
   arazzo?: ArazzoDecoratorsSet;
 };
 
@@ -111,6 +120,7 @@ export type CustomRulesConfig = {
   oas3?: Oas3RuleSet;
   oas2?: Oas2RuleSet;
   async2?: Async2RuleSet;
+  async3?: Async3RuleSet;
   arazzo?: ArazzoRuleSet;
 };
 
@@ -243,16 +253,19 @@ export type RulesFields =
   | 'oas3_0Rules'
   | 'oas3_1Rules'
   | 'async2Rules'
+  | 'async3Rules'
   | 'arazzoRules'
   | 'preprocessors'
   | 'oas2Preprocessors'
   | 'oas3_0Preprocessors'
   | 'oas3_1Preprocessors'
   | 'async2Preprocessors'
+  | 'async3Preprocessors'
   | 'arazzoPreprocessors'
   | 'decorators'
   | 'oas2Decorators'
   | 'oas3_0Decorators'
   | 'oas3_1Decorators'
-  | 'arazzoDecorators'
-  | 'async2Decorators';
+  | 'async2Decorators'
+  | 'async3Decorators'
+  | 'arazzoDecorators';
diff --git a/packages/core/src/config/utils.ts b/packages/core/src/config/utils.ts
index d19ef66b..27e3447a 100644
--- a/packages/core/src/config/utils.ts
+++ b/packages/core/src/config/utils.ts
@@ -56,6 +56,7 @@ function extractFlatConfig<
   oas3_0Rules,
   oas3_1Rules,
   async2Rules,
+  async3Rules,
   arazzoRules,
 
   preprocessors,
@@ -63,6 +64,7 @@ function extractFlatConfig<
   oas3_0Preprocessors,
   oas3_1Preprocessors,
   async2Preprocessors,
+  async3Preprocessors,
   arazzoPreprocessors,
 
   decorators,
@@ -70,6 +72,7 @@ function extractFlatConfig<
   oas3_0Decorators,
   oas3_1Decorators,
   async2Decorators,
+  async3Decorators,
   arazzoDecorators,
 
   ...rawConfigRest
@@ -86,6 +89,7 @@ function extractFlatConfig<
     oas3_0Rules,
     oas3_1Rules,
     async2Rules,
+    async3Rules,
     arazzoRules,
 
     preprocessors,
@@ -93,6 +97,7 @@ function extractFlatConfig<
     oas3_0Preprocessors,
     oas3_1Preprocessors,
     async2Preprocessors,
+    async3Preprocessors,
     arazzoPreprocessors,
 
     decorators,
@@ -100,6 +105,7 @@ function extractFlatConfig<
     oas3_0Decorators,
     oas3_1Decorators,
     async2Decorators,
+    async3Decorators,
     arazzoDecorators,
 
     doNotResolveExamples: rawConfigRest.resolve?.doNotResolveExamples,
@@ -158,6 +164,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
     oas3_0Rules: {},
     oas3_1Rules: {},
     async2Rules: {},
+    async3Rules: {},
     arazzoRules: {},
 
     preprocessors: {},
@@ -165,6 +172,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
     oas3_0Preprocessors: {},
     oas3_1Preprocessors: {},
     async2Preprocessors: {},
+    async3Preprocessors: {},
     arazzoPreprocessors: {},
 
     decorators: {},
@@ -172,6 +180,7 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
     oas3_0Decorators: {},
     oas3_1Decorators: {},
     async2Decorators: {},
+    async3Decorators: {},
     arazzoDecorators: {},
 
     plugins: [],
@@ -195,6 +204,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
     assignExisting(result.oas3_1Rules, rulesConf.rules || {});
     Object.assign(result.async2Rules, rulesConf.async2Rules);
     assignExisting(result.async2Rules, rulesConf.rules || {});
+    Object.assign(result.async3Rules, rulesConf.async3Rules);
+    assignExisting(result.async3Rules, rulesConf.rules || {});
     Object.assign(result.arazzoRules, rulesConf.arazzoRules);
     assignExisting(result.arazzoRules, rulesConf.rules || {});
 
@@ -207,6 +218,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
     assignExisting(result.oas3_1Preprocessors, rulesConf.preprocessors || {});
     Object.assign(result.async2Preprocessors, rulesConf.async2Preprocessors);
     assignExisting(result.async2Preprocessors, rulesConf.preprocessors || {});
+    Object.assign(result.async3Preprocessors, rulesConf.async3Preprocessors);
+    assignExisting(result.async3Preprocessors, rulesConf.preprocessors || {});
     Object.assign(result.arazzoPreprocessors, rulesConf.arazzoPreprocessors);
     assignExisting(result.arazzoPreprocessors, rulesConf.preprocessors || {});
 
@@ -219,6 +232,8 @@ export function mergeExtends(rulesConfList: ResolvedStyleguideConfig[]) {
     assignExisting(result.oas3_1Decorators, rulesConf.decorators || {});
     Object.assign(result.async2Decorators, rulesConf.async2Decorators);
     assignExisting(result.async2Decorators, rulesConf.decorators || {});
+    Object.assign(result.async3Decorators, rulesConf.async3Decorators);
+    assignExisting(result.async3Decorators, rulesConf.decorators || {});
     Object.assign(result.arazzoDecorators, rulesConf.arazzoDecorators);
     assignExisting(result.arazzoDecorators, rulesConf.decorators || {});
 
diff --git a/packages/core/src/decorators/async3/index.ts b/packages/core/src/decorators/async3/index.ts
new file mode 100644
index 00000000..6ebd5e11
--- /dev/null
+++ b/packages/core/src/decorators/async3/index.ts
@@ -0,0 +1,1 @@
+export const decorators = {};
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index a4a23ed8..16d645f1 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -11,7 +11,8 @@ export { Oas3_1Types } from './types/oas3_1';
 export { ArazzoTypes } from './types/arazzo';
 export { Oas3Types } from './types/oas3';
 export { Oas2Types } from './types/oas2';
-export { AsyncApi2Types } from './types/asyncapi';
+export { AsyncApi2Types } from './types/asyncapi2';
+export { AsyncApi3Types } from './types/asyncapi3';
 export { ConfigTypes } from './types/redocly-yaml';
 export type {
   Oas3Definition,
diff --git a/packages/core/src/oas-types.ts b/packages/core/src/oas-types.ts
index 07b1dda1..1d28d24a 100644
--- a/packages/core/src/oas-types.ts
+++ b/packages/core/src/oas-types.ts
@@ -5,16 +5,20 @@ import {
   Oas2Preprocessor,
   Async2Preprocessor,
   Async2Rule,
+  Async3Preprocessor,
+  Async3Rule,
   ArazzoPreprocessor,
   ArazzoRule,
 } from './visitors';
 import { Oas2Types } from './types/oas2';
 import { Oas3Types } from './types/oas3';
 import { Oas3_1Types } from './types/oas3_1';
-import { AsyncApi2Types } from './types/asyncapi';
+import { AsyncApi2Types } from './types/asyncapi2';
+import { AsyncApi3Types } from './types/asyncapi3';
 import { ArazzoTypes } from './types/arazzo';
 import {
   BuiltInAsync2RuleId,
+  BuiltInAsync3RuleId,
   BuiltInCommonOASRuleId,
   BuiltInArazzoRuleId,
   BuiltInOAS2RuleId,
@@ -27,7 +31,8 @@ export enum SpecVersion {
   OAS2 = 'oas2',
   OAS3_0 = 'oas3_0',
   OAS3_1 = 'oas3_1',
-  Async2 = 'async2', // todo split into 2.x maybe?
+  Async2 = 'async2',
+  Async3 = 'async3',
   Arazzo = 'arazzo',
 }
 
@@ -35,6 +40,7 @@ export enum SpecMajorVersion {
   OAS2 = 'oas2',
   OAS3 = 'oas3',
   Async2 = 'async2',
+  Async3 = 'async3',
   Arazzo = 'arazzo',
 }
 
@@ -43,6 +49,7 @@ const typesMap = {
   [SpecVersion.OAS3_0]: Oas3Types,
   [SpecVersion.OAS3_1]: Oas3_1Types,
   [SpecVersion.Async2]: AsyncApi2Types,
+  [SpecVersion.Async3]: AsyncApi3Types,
   [SpecVersion.Arazzo]: ArazzoTypes,
 };
 
@@ -65,6 +72,11 @@ export type Async2RuleSet<T = undefined> = RuleMap<
   Async2Rule,
   T
 >;
+export type Async3RuleSet<T = undefined> = RuleMap<
+  BuiltInAsync3RuleId | 'assertions',
+  Async3Rule,
+  T
+>;
 export type ArazzoRuleSet<T = undefined> = RuleMap<
   BuiltInArazzoRuleId | 'assertions',
   ArazzoRule,
@@ -74,11 +86,13 @@ export type ArazzoRuleSet<T = undefined> = RuleMap<
 export type Oas3PreprocessorsSet = Record<string, Oas3Preprocessor>;
 export type Oas2PreprocessorsSet = Record<string, Oas2Preprocessor>;
 export type Async2PreprocessorsSet = Record<string, Async2Preprocessor>;
+export type Async3PreprocessorsSet = Record<string, Async3Preprocessor>;
 export type ArazzoPreprocessorsSet = Record<string, ArazzoPreprocessor>;
 
 export type Oas3DecoratorsSet = Record<string, Oas3Preprocessor>;
 export type Oas2DecoratorsSet = Record<string, Oas2Preprocessor>;
 export type Async2DecoratorsSet = Record<string, Async2Preprocessor>;
+export type Async3DecoratorsSet = Record<string, Async3Preprocessor>;
 export type ArazzoDecoratorsSet = Record<string, ArazzoPreprocessor>;
 
 export function detectSpec(root: any): SpecVersion {
@@ -111,6 +125,10 @@ export function detectSpec(root: any): SpecVersion {
     return SpecVersion.Async2;
   }
 
+  if (root.asyncapi && root.asyncapi.startsWith('3.')) {
+    return SpecVersion.Async3;
+  }
+
   if (root.asyncapi) {
     throw new Error(`Unsupported AsyncAPI version: ${root.asyncapi}`);
   }
@@ -127,6 +145,8 @@ export function getMajorSpecVersion(version: SpecVersion): SpecMajorVersion {
     return SpecMajorVersion.OAS2;
   } else if (version === SpecVersion.Async2) {
     return SpecMajorVersion.Async2;
+  } else if (version === SpecVersion.Async3) {
+    return SpecMajorVersion.Async3;
   } else if (version === SpecVersion.Arazzo) {
     return SpecMajorVersion.Arazzo;
   } else {
diff --git a/packages/core/src/rules/async3/channels-kebab-case.ts b/packages/core/src/rules/async3/channels-kebab-case.ts
new file mode 100644
index 00000000..a14d7839
--- /dev/null
+++ b/packages/core/src/rules/async3/channels-kebab-case.ts
@@ -0,0 +1,19 @@
+import { Channel } from '../../typings/asyncapi3';
+import { Async3Rule } from '../../visitors';
+import { UserContext } from '../../walk';
+
+export const ChannelsKebabCase: Async3Rule = () => {
+  return {
+    Channel(channel: Channel, { report }: UserContext) {
+      const segments = (channel.address || '')
+        .split(/[/.:]/) // split on / or : as likely channel namespacers
+        .filter((s) => s !== ''); // filter out empty segments
+      if (!segments.every((segment) => /^{.+}$/.test(segment) || /^[a-z0-9-.]+$/.test(segment))) {
+        report({
+          message: `\`${channel.address}\` does not use kebab-case.`,
+          location: { reportOnKey: true },
+        });
+      }
+    },
+  };
+};
diff --git a/packages/core/src/rules/async3/index.ts b/packages/core/src/rules/async3/index.ts
new file mode 100644
index 00000000..3f4c2a07
--- /dev/null
+++ b/packages/core/src/rules/async3/index.ts
@@ -0,0 +1,23 @@
+import { Async3Rule } from '../../visitors';
+import { Assertions } from '../common/assertions';
+import { Spec } from '../common/spec';
+import { InfoContact } from '../common/info-contact';
+import { OperationOperationId } from '../common/operation-operationId';
+import { TagDescription } from '../common/tag-description';
+import { TagsAlphabetical } from '../common/tags-alphabetical';
+import { ChannelsKebabCase } from './channels-kebab-case';
+import { NoChannelTrailingSlash } from './no-channel-trailing-slash';
+import type { Async3RuleSet } from '../../oas-types';
+
+export const rules: Async3RuleSet<'built-in'> = {
+  spec: Spec as Async3Rule,
+  assertions: Assertions,
+  'info-contact': InfoContact,
+  'operation-operationId': OperationOperationId,
+  'channels-kebab-case': ChannelsKebabCase,
+  'no-channel-trailing-slash': NoChannelTrailingSlash,
+  'tag-description': TagDescription,
+  'tags-alphabetical': TagsAlphabetical,
+};
+
+export const preprocessors = {};
diff --git a/packages/core/src/rules/async3/no-channel-trailing-slash.ts b/packages/core/src/rules/async3/no-channel-trailing-slash.ts
new file mode 100644
index 00000000..7cc93e0c
--- /dev/null
+++ b/packages/core/src/rules/async3/no-channel-trailing-slash.ts
@@ -0,0 +1,16 @@
+import { Async3Rule } from '../../visitors';
+import { UserContext } from '../../walk';
+import { Channel } from '../../typings/asyncapi3';
+
+export const NoChannelTrailingSlash: Async3Rule = () => {
+  return {
+    Channel(channel: Channel, { report, location }: UserContext) {
+      if ((channel.address as string).endsWith('/') && channel.address !== '/') {
+        report({
+          message: `\`${channel.address}\` should not have a trailing slash.`,
+          location: location.key(),
+        });
+      }
+    },
+  };
+};
diff --git a/packages/core/src/rules/common/assertions/index.ts b/packages/core/src/rules/common/assertions/index.ts
index 95c48a56..83cfc215 100644
--- a/packages/core/src/rules/common/assertions/index.ts
+++ b/packages/core/src/rules/common/assertions/index.ts
@@ -1,6 +1,6 @@
 import { asserts, AssertionFn } from './asserts';
 import { buildSubjectVisitor, buildVisitorObject } from './utils';
-import { Oas2Visitor, Oas3Visitor } from '../../../visitors';
+import { Async2Visitor, Async3Visitor, Oas2Visitor, Oas3Visitor } from '../../../visitors';
 import { RuleSeverity } from '../../../config';
 import { isString } from '../../../utils';
 
@@ -28,7 +28,7 @@ export type RawAssertion = AssertionDefinition & {
 export type Assertion = RawAssertion & { assertionId: string };
 
 export const Assertions = (opts: Record<string, Assertion>) => {
-  const visitors: (Oas2Visitor | Oas3Visitor)[] = [];
+  const visitors: (Oas2Visitor | Oas3Visitor | Async2Visitor | Async3Visitor)[] = [];
 
   // As 'Assertions' has an array of asserts,
   // that array spreads into an 'opts' object on init rules phase here
diff --git a/packages/core/src/rules/common/spec.ts b/packages/core/src/rules/common/spec.ts
index eec0691f..1e9112c0 100644
--- a/packages/core/src/rules/common/spec.ts
+++ b/packages/core/src/rules/common/spec.ts
@@ -1,11 +1,11 @@
-import type { Oas3Rule, Oas2Rule, Async2Rule, ArazzoRule } from '../../visitors';
+import type { Oas3Rule, Oas2Rule, Async2Rule, Async3Rule, ArazzoRule } from '../../visitors';
 import { isNamedType, SpecExtension } from '../../types';
 import { oasTypeOf, matchesJsonSchemaType, getSuggest, validateSchemaEnumType } from '../utils';
 import { isRef } from '../../ref-utils';
 import { isPlainObject } from '../../utils';
 import { UserContext } from '../../walk';
 
-export const Spec: Oas3Rule | Oas2Rule | Async2Rule | ArazzoRule = () => {
+export const Spec: Oas3Rule | Oas2Rule | Async2Rule | Async3Rule | ArazzoRule = () => {
   return {
     any(
       node: any,
diff --git a/packages/core/src/types/asyncapi.ts b/packages/core/src/types/asyncapi2.ts
similarity index 97%
rename from packages/core/src/types/asyncapi.ts
rename to packages/core/src/types/asyncapi2.ts
index 5e24ccf9..913e4959 100644
--- a/packages/core/src/types/asyncapi.ts
+++ b/packages/core/src/types/asyncapi2.ts
@@ -61,7 +61,7 @@ const ChannelBindings: NodeType = {
   additionalProperties: { type: 'object' },
 };
 
-const Tag: NodeType = {
+export const Tag: NodeType = {
   properties: {
     name: { type: 'string' },
     description: { type: 'string' },
@@ -70,7 +70,7 @@ const Tag: NodeType = {
   required: ['name'],
 };
 
-const ExternalDocs: NodeType = {
+export const ExternalDocs: NodeType = {
   properties: {
     description: { type: 'string' },
     url: { type: 'string' },
@@ -126,13 +126,13 @@ const Server: NodeType = {
   required: ['url', 'protocol'],
 };
 
-const ServerMap: NodeType = {
+export const ServerMap: NodeType = {
   properties: {},
   additionalProperties: (_value: any, key: string) =>
     key.match(/^[A-Za-z0-9_\-]+$/) ? 'Server' : undefined,
 };
 
-const ServerVariable: NodeType = {
+export const ServerVariable: NodeType = {
   properties: {
     enum: {
       type: 'array',
@@ -160,7 +160,7 @@ const Info: NodeType = {
   required: ['title', 'version'],
 };
 
-const Contact: NodeType = {
+export const Contact: NodeType = {
   properties: {
     name: { type: 'string' },
     url: { type: 'string' },
@@ -168,7 +168,7 @@ const Contact: NodeType = {
   },
 };
 
-const License: NodeType = {
+export const License: NodeType = {
   properties: {
     name: { type: 'string' },
     url: { type: 'string' },
@@ -184,7 +184,7 @@ const Parameter: NodeType = {
   },
 };
 
-const CorrelationId: NodeType = {
+export const CorrelationId: NodeType = {
   properties: {
     description: { type: 'string' },
     location: { type: 'string' },
@@ -208,7 +208,7 @@ const Message: NodeType = {
     tags: 'TagList',
     externalDocs: 'ExternalDocs',
     bindings: 'MessageBindings',
-    // examples: 'MessageExampleList', // TODO: add support for message examples
+    examples: 'MessageExampleList',
     traits: 'MessageTraitList',
   },
   additionalProperties: {},
@@ -301,7 +301,7 @@ const MessageTrait: NodeType = {
     tags: 'TagList',
     externalDocs: 'ExternalDocs',
     bindings: 'MessageBindings',
-    // examples: 'MessageExampleList', // TODO: support examples for message traits
+    examples: 'MessageExampleList',
   },
   additionalProperties: {},
 };
@@ -322,7 +322,7 @@ const Operation: NodeType = {
   required: [],
 };
 
-const MessageExample: NodeType = {
+export const MessageExample: NodeType = {
   properties: {
     payload: { isExample: true },
     summary: { type: 'string' },
@@ -331,7 +331,7 @@ const MessageExample: NodeType = {
   },
 };
 
-const Schema: NodeType = {
+export const Schema: NodeType = {
   properties: {
     $id: { type: 'string' },
     $schema: { type: 'string' },
@@ -401,14 +401,14 @@ const Schema: NodeType = {
   },
 };
 
-const SchemaProperties: NodeType = {
+export const SchemaProperties: NodeType = {
   properties: {},
   additionalProperties: (value: any) => {
     return typeof value === 'boolean' ? { type: 'boolean' } : 'Schema';
   },
 };
 
-const DiscriminatorMapping: NodeType = {
+export const DiscriminatorMapping: NodeType = {
   properties: {},
   additionalProperties: (value: any) => {
     if (isMappingRef(value)) {
@@ -419,7 +419,7 @@ const DiscriminatorMapping: NodeType = {
   },
 };
 
-const Discriminator: NodeType = {
+export const Discriminator: NodeType = {
   properties: {
     propertyName: { type: 'string' },
     mapping: 'DiscriminatorMapping',
@@ -435,7 +435,6 @@ const Components: NodeType = {
     correlationIds: 'NamedCorrelationIds',
     messageTraits: 'NamedMessageTraits',
     operationTraits: 'NamedOperationTraits',
-    streamHeaders: 'NamedStreamHeaders',
     securitySchemes: 'NamedSecuritySchemes',
     servers: 'ServerMap',
     serverVariables: 'ServerVariablesMap',
@@ -484,7 +483,7 @@ const AuthorizationCode: NodeType = {
   required: ['authorizationUrl', 'tokenUrl', 'scopes'],
 };
 
-const SecuritySchemeFlows: NodeType = {
+export const SecuritySchemeFlows: NodeType = {
   properties: {
     implicit: 'ImplicitFlow',
     password: 'PasswordFlow',
@@ -555,7 +554,7 @@ const SecurityScheme: NodeType = {
   extensionsPrefix: 'x-',
 };
 
-const Dependencies: NodeType = {
+export const Dependencies: NodeType = {
   properties: {},
   additionalProperties: (value: any) => {
     return Array.isArray(value) ? { type: 'array', items: { type: 'string' } } : 'Schema';
@@ -987,22 +986,7 @@ OperationBindings.properties.mercure = MercureOperationBinding;
 // pulsar
 
 // --- End per-protocol node types
-
-export const AsyncApi2Types: Record<string, NodeType> = {
-  Root,
-  Tag,
-  TagList: listOf('Tag'),
-  ServerMap,
-  ExternalDocs,
-  Server,
-  ServerVariable,
-  ServerVariablesMap: mapOf('ServerVariable'),
-  SecurityRequirement,
-  SecurityRequirementList: listOf('SecurityRequirement'),
-  Info,
-  Contact,
-  License,
-
+export const AsyncApi2Bindings: Record<string, NodeType> = {
   HttpServerBinding,
   HttpChannelBinding,
   HttpMessageBinding,
@@ -1078,6 +1062,25 @@ export const AsyncApi2Types: Record<string, NodeType> = {
 
   ServerBindings,
   ChannelBindings,
+  MessageBindings,
+  OperationBindings,
+};
+
+export const AsyncApi2Types: Record<string, NodeType> = {
+  ...AsyncApi2Bindings,
+  Root,
+  Tag,
+  TagList: listOf('Tag'),
+  ServerMap,
+  ExternalDocs,
+  Server,
+  ServerVariable,
+  ServerVariablesMap: mapOf('ServerVariable'),
+  SecurityRequirement,
+  SecurityRequirementList: listOf('SecurityRequirement'),
+  Info,
+  Contact,
+  License,
   ChannelMap,
   Channel,
   Parameter,
@@ -1096,7 +1099,6 @@ export const AsyncApi2Types: Record<string, NodeType> = {
   NamedParameters: mapOf('Parameter'),
   NamedSecuritySchemes: mapOf('SecurityScheme'),
   NamedCorrelationIds: mapOf('CorrelationId'),
-  NamedStreamHeaders: mapOf('StreamHeader'),
   ImplicitFlow,
   PasswordFlow,
   ClientCredentials,
@@ -1110,6 +1112,7 @@ export const AsyncApi2Types: Record<string, NodeType> = {
   OperationTraitList: listOf('OperationTrait'),
   MessageTrait,
   MessageTraitList: listOf('MessageTrait'),
+  MessageExampleList: listOf('MessageExample'),
   CorrelationId,
   Dependencies,
 };
diff --git a/packages/core/src/types/asyncapi3.ts b/packages/core/src/types/asyncapi3.ts
new file mode 100644
index 00000000..776abc8d
--- /dev/null
+++ b/packages/core/src/types/asyncapi3.ts
@@ -0,0 +1,381 @@
+import { NodeType, listOf, mapOf } from '.';
+import {
+  AsyncApi2Bindings,
+  Schema,
+  Dependencies,
+  Discriminator,
+  DiscriminatorMapping,
+  SchemaProperties,
+  CorrelationId,
+  Tag,
+  ServerMap,
+  ExternalDocs,
+  SecuritySchemeFlows,
+  ServerVariable,
+  Contact,
+  License,
+  MessageExample,
+} from './asyncapi2';
+
+const Root: NodeType = {
+  properties: {
+    asyncapi: { type: 'string', enum: ['3.0.0'] },
+    info: 'Info',
+    id: { type: 'string' },
+    servers: 'ServerMap',
+    channels: 'NamedChannels',
+    components: 'Components',
+    operations: 'NamedOperations',
+    defaultContentType: { type: 'string' },
+  },
+  required: ['asyncapi', 'info'],
+};
+
+const Channel: NodeType = {
+  properties: {
+    address: { type: 'string' },
+    messages: 'NamedMessages',
+    title: { type: 'string' },
+    summary: { type: 'string' },
+    description: { type: 'string' },
+    servers: 'ServerList',
+    parameters: 'ParametersMap',
+    bindings: 'ChannelBindings',
+    tags: 'TagList',
+    externalDocs: 'ExternalDocs',
+  },
+};
+
+const Server: NodeType = {
+  properties: {
+    host: { type: 'string' },
+    pathname: { type: 'string' },
+    protocol: { type: 'string' },
+    protocolVersion: { type: 'string' },
+    description: { type: 'string' },
+    variables: 'ServerVariablesMap',
+    security: 'SecuritySchemeList',
+    bindings: 'ServerBindings',
+    externalDocs: 'ExternalDocs',
+    tags: 'TagList',
+  },
+  required: ['host', 'protocol'],
+};
+
+const Info: NodeType = {
+  properties: {
+    title: { type: 'string' },
+    version: { type: 'string' },
+    description: { type: 'string' },
+    termsOfService: { type: 'string' },
+    contact: 'Contact',
+    license: 'License',
+    tags: 'TagList',
+    externalDocs: 'ExternalDocs',
+  },
+  required: ['title', 'version'],
+};
+
+const Parameter: NodeType = {
+  properties: {
+    description: { type: 'string' },
+    enum: { type: 'array', items: { type: 'string' } },
+    default: { type: 'string' },
+    examples: { type: 'array', items: { type: 'string' } },
+    location: { type: 'string' },
+  },
+};
+
+const Message: NodeType = {
+  properties: {
+    headers: 'Schema',
+    payload: (value: Record<string, unknown>) => {
+      if (!!value && value?.['schemaFormat']) {
+        return {
+          properties: {
+            schema: 'Schema',
+            schemaFormat: { type: 'string' },
+          },
+          required: ['schema', 'schemaFormat'],
+        };
+      } else {
+        return 'Schema';
+      }
+    },
+    correlationId: 'CorrelationId',
+
+    contentType: { type: 'string' },
+    name: { type: 'string' },
+    title: { type: 'string' },
+    summary: { type: 'string' },
+    description: { type: 'string' },
+    tags: 'TagList',
+    externalDocs: 'ExternalDocs',
+    bindings: 'MessageBindings',
+    examples: 'MessageExampleList',
+    traits: 'MessageTraitList',
+  },
+  additionalProperties: {},
+};
+
+const OperationTrait: NodeType = {
+  properties: {
+    tags: 'TagList',
+    title: { type: 'string' },
+    summary: { type: 'string' },
+    description: { type: 'string' },
+    externalDocs: 'ExternalDocs',
+    security: 'SecuritySchemeList',
+
+    bindings: 'OperationBindings',
+  },
+  required: [],
+};
+
+const MessageTrait: NodeType = {
+  properties: {
+    headers: (value: unknown) => {
+      if (typeof value === 'function' || (typeof value === 'object' && !!value)) {
+        return {
+          properties: {
+            schema: 'Schema',
+            schemaFormat: { type: 'string' },
+          },
+        };
+      } else {
+        return 'Schema';
+      }
+    },
+    correlationId: 'CorrelationId',
+
+    contentType: { type: 'string' },
+    name: { type: 'string' },
+    title: { type: 'string' },
+    summary: { type: 'string' },
+    description: { type: 'string' },
+    tags: 'TagList',
+    externalDocs: 'ExternalDocs',
+    bindings: 'MessageBindings',
+    examples: 'MessageExampleList',
+  },
+  additionalProperties: {},
+};
+
+const Operation: NodeType = {
+  properties: {
+    action: { type: 'string', enum: ['send', 'receive'] },
+    channel: 'Channel',
+    title: { type: 'string' },
+    tags: 'TagList',
+    summary: { type: 'string' },
+    description: { type: 'string' },
+    externalDocs: 'ExternalDocs',
+    operationId: { type: 'string' },
+    security: 'SecuritySchemeList',
+
+    bindings: 'OperationBindings',
+    traits: 'OperationTraitList',
+    messages: 'MessageList',
+    reply: 'OperationReply',
+  },
+  required: ['action', 'channel'],
+};
+
+const OperationReply: NodeType = {
+  properties: {
+    channel: 'Channel',
+    messages: 'MessageList',
+    address: 'OperationReplyAddress',
+  },
+};
+
+const OperationReplyAddress: NodeType = {
+  properties: {
+    location: { type: 'string' },
+    description: { type: 'string' },
+  },
+  required: ['location'],
+};
+
+const Components: NodeType = {
+  properties: {
+    messages: 'NamedMessages',
+    parameters: 'NamedParameters',
+    schemas: 'NamedSchemas',
+    replies: 'NamedOperationReplies',
+    replyAddresses: 'NamedOperationRelyAddresses',
+    correlationIds: 'NamedCorrelationIds',
+    messageTraits: 'NamedMessageTraits',
+    operationTraits: 'NamedOperationTraits',
+    tags: 'NamedTags',
+    externalDocs: 'NamedExternalDocs',
+    securitySchemes: 'NamedSecuritySchemes',
+    servers: 'ServerMap',
+    serverVariables: 'ServerVariablesMap',
+    channels: 'NamedChannels',
+    operations: 'NamedOperations',
+    serverBindings: 'ServerBindings',
+    channelBindings: 'ChannelBindings',
+    operationBindings: 'OperationBindings',
+    messageBindings: 'MessageBindings',
+  },
+};
+
+const ImplicitFlow: NodeType = {
+  properties: {
+    refreshUrl: { type: 'string' },
+    availableScopes: { type: 'object', additionalProperties: { type: 'string' } },
+    authorizationUrl: { type: 'string' },
+  },
+  required: ['authorizationUrl', 'availableScopes'],
+};
+
+const PasswordFlow: NodeType = {
+  properties: {
+    refreshUrl: { type: 'string' },
+    availableScopes: { type: 'object', additionalProperties: { type: 'string' } },
+    tokenUrl: { type: 'string' },
+  },
+  required: ['tokenUrl', 'availableScopes'],
+};
+
+const ClientCredentials: NodeType = {
+  properties: {
+    refreshUrl: { type: 'string' },
+    availableScopes: { type: 'object', additionalProperties: { type: 'string' } },
+    tokenUrl: { type: 'string' },
+  },
+  required: ['tokenUrl', 'availableScopes'],
+};
+
+const AuthorizationCode: NodeType = {
+  properties: {
+    refreshUrl: { type: 'string' },
+    authorizationUrl: { type: 'string' },
+    availableScopes: { type: 'object', additionalProperties: { type: 'string' } },
+    tokenUrl: { type: 'string' },
+  },
+  required: ['authorizationUrl', 'tokenUrl', 'availableScopes'],
+};
+
+const SecurityScheme: NodeType = {
+  properties: {
+    type: {
+      enum: [
+        'userPassword',
+        'apiKey',
+        'X509',
+        'symmetricEncryption',
+        'asymmetricEncryption',
+        'httpApiKey',
+        'http',
+        'oauth2',
+        'openIdConnect',
+        'plain',
+        'scramSha256',
+        'scramSha512',
+        'gssapi',
+      ],
+    },
+    description: { type: 'string' },
+    name: { type: 'string' },
+    in: { type: 'string', enum: ['query', 'header', 'cookie', 'user', 'password'] },
+    scheme: { type: 'string' },
+    bearerFormat: { type: 'string' },
+    flows: 'SecuritySchemeFlows',
+    openIdConnectUrl: { type: 'string' },
+    scopes: { type: 'array', items: { type: 'string' } },
+  },
+  required(value) {
+    switch (value?.type) {
+      case 'apiKey':
+        return ['type', 'in'];
+      case 'httpApiKey':
+        return ['type', 'name', 'in'];
+      case 'http':
+        return ['type', 'scheme'];
+      case 'oauth2':
+        return ['type', 'flows'];
+      case 'openIdConnect':
+        return ['type', 'openIdConnectUrl'];
+      default:
+        return ['type'];
+    }
+  },
+  allowed(value) {
+    switch (value?.type) {
+      case 'apiKey':
+        return ['type', 'in', 'description'];
+      case 'httpApiKey':
+        return ['type', 'name', 'in', 'description'];
+      case 'http':
+        return ['type', 'scheme', 'bearerFormat', 'description'];
+      case 'oauth2':
+        return ['type', 'flows', 'description', 'scopes'];
+      case 'openIdConnect':
+        return ['type', 'openIdConnectUrl', 'description', 'scopes'];
+      default:
+        return ['type', 'description'];
+    }
+  },
+  extensionsPrefix: 'x-',
+};
+
+export const AsyncApi3Types: Record<string, NodeType> = {
+  // from asyncapi2
+  ...AsyncApi2Bindings,
+  CorrelationId,
+  SecuritySchemeFlows,
+  ServerVariable,
+  Contact,
+  License,
+  MessageExample,
+  Tag,
+  Dependencies,
+  Schema,
+  Discriminator,
+  DiscriminatorMapping,
+  SchemaProperties,
+  ServerMap,
+  ExternalDocs,
+  Root,
+
+  Channel,
+  Parameter,
+  Info,
+  Server,
+  MessageTrait,
+  Operation,
+  OperationReply,
+  OperationReplyAddress,
+  Components,
+  ImplicitFlow,
+  PasswordFlow,
+  ClientCredentials,
+  AuthorizationCode,
+  SecurityScheme,
+  Message,
+  OperationTrait,
+  ServerVariablesMap: mapOf('ServerVariable'),
+  NamedTags: mapOf('Tag'),
+  NamedExternalDocs: mapOf('ExternalDocs'),
+  NamedChannels: mapOf('Channel'),
+  ParametersMap: mapOf('Parameter'),
+  NamedOperations: mapOf('Operation'),
+  NamedOperationReplies: mapOf('OperationReply'),
+  NamedOperationRelyAddresses: mapOf('OperationReplyAddress'),
+  NamedSchemas: mapOf('Schema'),
+  NamedMessages: mapOf('Message'),
+  NamedMessageTraits: mapOf('MessageTrait'),
+  NamedOperationTraits: mapOf('OperationTrait'),
+  NamedParameters: mapOf('Parameter'),
+  NamedSecuritySchemes: mapOf('SecurityScheme'),
+  NamedCorrelationIds: mapOf('CorrelationId'),
+  ServerList: listOf('Server'),
+  SecuritySchemeList: listOf('SecurityScheme'),
+  MessageList: listOf('Message'),
+  OperationTraitList: listOf('OperationTrait'),
+  MessageTraitList: listOf('MessageTrait'),
+  MessageExampleList: listOf('MessageExample'),
+  TagList: listOf('Tag'),
+};
diff --git a/packages/core/src/types/redocly-yaml.ts b/packages/core/src/types/redocly-yaml.ts
index 30cf5ae2..20e69aa2 100644
--- a/packages/core/src/types/redocly-yaml.ts
+++ b/packages/core/src/types/redocly-yaml.ts
@@ -92,8 +92,20 @@ const builtInAsync2Rules = [
   'no-channel-trailing-slash',
 ] as const;
 
+const builtInAsync3Rules = [
+  'spec',
+  'info-contact',
+  'operation-operationId',
+  'tag-description',
+  'tags-alphabetical',
+  'channels-kebab-case',
+  'no-channel-trailing-slash',
+] as const;
+
 export type BuiltInAsync2RuleId = typeof builtInAsync2Rules[number];
 
+export type BuiltInAsync3RuleId = typeof builtInAsync3Rules[number];
+
 const builtInArazzoRules = ['spec'] as const;
 
 export type BuiltInArazzoRuleId = typeof builtInArazzoRules[number];
@@ -103,6 +115,7 @@ const builtInRules = [
   ...builtInOAS2Rules,
   ...builtInOAS3Rules,
   ...builtInAsync2Rules,
+  ...builtInAsync3Rules,
   ...builtInArazzoRules,
 ] as const;
 
diff --git a/packages/core/src/typings/asyncapi3.ts b/packages/core/src/typings/asyncapi3.ts
new file mode 100644
index 00000000..514a56e7
--- /dev/null
+++ b/packages/core/src/typings/asyncapi3.ts
@@ -0,0 +1,61 @@
+export interface Async3Definition {
+  asyncapi: string;
+  servers?: Record<string, any>;
+  info: Async3Info;
+  channels?: Record<string, Channel>;
+  components?: Record<string, any>;
+  operations?: Record<string, any>;
+  defaultContentType?: string;
+}
+
+export interface Async3Info {
+  title: string;
+  version: string;
+
+  description?: string;
+  termsOfService?: string;
+  contact?: Async3Contact;
+  license?: Async3License;
+  tags?: Tag[];
+  externalDocs?: ExternalDoc;
+}
+
+export interface Async3Contact {
+  name?: string;
+  url?: string;
+  email?: string;
+}
+
+export interface Async3License {
+  name: string;
+  url?: string;
+}
+
+export interface Tag {
+  name: string;
+  description?: string;
+  externalDocs?: ExternalDoc;
+}
+
+export interface ExternalDoc {
+  url: string;
+  description?: string;
+}
+
+export interface Channel {
+  address?: string | null;
+  messages?: Record<string, any>;
+  title?: string;
+  summary?: string;
+  description?: string;
+  servers?: Record<string, any>[];
+  parameters?: Record<string, any>;
+  tags?: Record<string, any>;
+  externalDocs?: ExternalDocumentation;
+  bindings?: Record<string, any>;
+}
+
+export interface ExternalDocumentation {
+  url: string;
+  description?: string;
+}
diff --git a/packages/core/src/visitors.ts b/packages/core/src/visitors.ts
index 71d2b7fb..e5c17f43 100644
--- a/packages/core/src/visitors.ts
+++ b/packages/core/src/visitors.ts
@@ -50,6 +50,7 @@ import type {
   Oas2SecurityScheme,
 } from './typings/swagger';
 import type { Async2Definition } from './typings/asyncapi';
+import type { Async3Definition } from './typings/asyncapi3';
 import type { ArazzoDefinition } from './typings/arazzo';
 
 export type SkipFunctionContext = Pick<
@@ -213,6 +214,10 @@ type Async2FlatVisitor = {
   Root?: VisitFunctionOrObject<Async2Definition>;
 };
 
+type Async3FlatVisitor = {
+  Root?: VisitFunctionOrObject<Async3Definition>;
+};
+
 type ArazzoFlatVisitor = {
   Root?: VisitFunctionOrObject<ArazzoDefinition>;
 };
@@ -249,6 +254,12 @@ type Async2NestedVisitor = {
     : Async2FlatVisitor[T] & NestedVisitor<Async2NestedVisitor>;
 };
 
+type Async3NestedVisitor = {
+  [T in keyof Async3FlatVisitor]: Async3FlatVisitor[T] extends Function
+    ? Async3FlatVisitor[T]
+    : Async3FlatVisitor[T] & NestedVisitor<Async3NestedVisitor>;
+};
+
 type ArazzoNestedVisitor = {
   [T in keyof ArazzoFlatVisitor]: ArazzoFlatVisitor[T] extends Function
     ? ArazzoFlatVisitor[T]
@@ -267,6 +278,10 @@ export type Async2Visitor = BaseVisitor &
   Async2NestedVisitor &
   Record<string, VisitFunction<any> | NestedVisitObject<any, Async2NestedVisitor>>;
 
+export type Async3Visitor = BaseVisitor &
+  Async3NestedVisitor &
+  Record<string, VisitFunction<any> | NestedVisitObject<any, Async3NestedVisitor>>;
+
 export type ArazzoVisitor = BaseVisitor &
   ArazzoNestedVisitor &
   Record<string, VisitFunction<any> | NestedVisitObject<any, ArazzoNestedVisitor>>;
@@ -293,14 +308,17 @@ export type NormalizedOasVisitors<T extends BaseVisitor> = {
 export type Oas3Rule = (options: Record<string, any>) => Oas3Visitor | Oas3Visitor[];
 export type Oas2Rule = (options: Record<string, any>) => Oas2Visitor | Oas2Visitor[];
 export type Async2Rule = (options: Record<string, any>) => Async2Visitor | Async2Visitor[];
+export type Async3Rule = (options: Record<string, any>) => Async3Visitor | Async3Visitor[];
 export type ArazzoRule = (options: Record<string, any>) => ArazzoVisitor | ArazzoVisitor[];
 export type Oas3Preprocessor = (options: Record<string, any>) => Oas3Visitor;
 export type Oas2Preprocessor = (options: Record<string, any>) => Oas2Visitor;
 export type Async2Preprocessor = (options: Record<string, any>) => Async2Visitor;
+export type Async3Preprocessor = (options: Record<string, any>) => Async3Visitor;
 export type ArazzoPreprocessor = (options: Record<string, any>) => ArazzoVisitor;
 export type Oas3Decorator = (options: Record<string, any>) => Oas3Visitor;
 export type Oas2Decorator = (options: Record<string, any>) => Oas2Visitor;
 export type Async2Decorator = (options: Record<string, any>) => Async2Visitor;
+export type Async3Decorator = (options: Record<string, any>) => Async3Visitor;
 export type ArazzoDecorator = (options: Record<string, any>) => ArazzoVisitor;
 
 // alias for the latest version supported
diff --git a/resources/async.yaml b/resources/async.yaml
index d329774d..41f5a203 100644
--- a/resources/async.yaml
+++ b/resources/async.yaml
@@ -173,7 +173,7 @@ channels:
                 description: The Id of the company.
             additionalProperties: false
 
-  smartylighting/streetlights/1/0/action/{streetlightId}/turn/on:
+  smartylighting/streetlights/1/0/action/{streetlightId}/turn/on/:
     parameters:
       streetlightId:
         $ref: '#/components/parameters/streetlightId'
diff --git a/resources/asyncapi3.yaml b/resources/asyncapi3.yaml
new file mode 100644
index 00000000..e5bde335
--- /dev/null
+++ b/resources/asyncapi3.yaml
@@ -0,0 +1,31 @@
+asyncapi: 3.0.0
+info:
+  title: Account Service
+  version: 1.0.0
+  description: This service is in charge of processing user signups
+channels:
+  userSignedup:
+    address: user/signedup/
+    messages:
+      UserSignedUp:
+        $ref: '#/components/messages/UserSignedUp'
+operations:
+  sendUserSignedup:
+    action: send
+    channel:
+      $ref: '#/channels/userSignedup'
+    messages:
+      - $ref: '#/channels/userSignedup/messages/UserSignedUp'
+components:
+  messages:
+    UserSignedUp:
+      payload:
+        type: object
+        properties:
+          displayName:
+            type: string
+            description: Name of the user
+          email:
+            type: string
+            format: email
+            description: Email of the user


Test patch:
diff --git a/__tests__/bundle/async3/UserSignedUp.yaml b/__tests__/bundle/async3/UserSignedUp.yaml
new file mode 100644
index 00000000..76da0031
--- /dev/null
+++ b/__tests__/bundle/async3/UserSignedUp.yaml
@@ -0,0 +1,11 @@
+UserSignedUp:
+  payload:
+    type: object
+    properties:
+      displayName:
+        type: string
+        description: Name of the user
+      email:
+        type: string
+        format: email
+        description: Email of the user
diff --git a/__tests__/bundle/async3/redocly.yaml b/__tests__/bundle/async3/redocly.yaml
new file mode 100644
index 00000000..01a377d4
--- /dev/null
+++ b/__tests__/bundle/async3/redocly.yaml
@@ -0,0 +1,3 @@
+apis:
+  main:
+    root: ./simple.yml
diff --git a/__tests__/bundle/async3/simple.yml b/__tests__/bundle/async3/simple.yml
new file mode 100644
index 00000000..1690851f
--- /dev/null
+++ b/__tests__/bundle/async3/simple.yml
@@ -0,0 +1,22 @@
+asyncapi: 3.0.0
+info:
+  title: Account Service
+  version: 1.0.0
+  description: This service is in charge of processing user signups
+channels:
+  userSignedup:
+    address: user/signedup
+    messages:
+      UserSignedUp:
+        $ref: '#/components/messages/UserSignedUp'
+operations:
+  sendUserSignedup:
+    action: send
+    channel:
+      $ref: '#/channels/userSignedup'
+    messages:
+      - $ref: '#/channels/userSignedup/messages/UserSignedUp'
+components:
+  messages:
+    UserSignedUp:
+      $ref: ./UserSignedUp.yaml
diff --git a/__tests__/bundle/async3/snapshot.js b/__tests__/bundle/async3/snapshot.js
new file mode 100644
index 00000000..edf3ca99
--- /dev/null
+++ b/__tests__/bundle/async3/snapshot.js
@@ -0,0 +1,60 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E bundle async3 1`] = `
+info:
+  title: Account Service
+  version: 1.0.0
+  description: This service is in charge of processing user signups
+components:
+  messages:
+    UserSignedUp:
+      UserSignedUp:
+        payload:
+          type: object
+          properties:
+            displayName:
+              type: string
+              description: Name of the user
+            email:
+              type: string
+              format: email
+              description: Email of the user
+asyncapi: 3.0.0
+channels:
+  userSignedup:
+    address: user/signedup
+    messages:
+      UserSignedUp:
+        UserSignedUp:
+          payload:
+            type: object
+            properties:
+              displayName:
+                type: string
+                description: Name of the user
+              email:
+                type: string
+                format: email
+                description: Email of the user
+operations:
+  sendUserSignedup:
+    action: send
+    channel:
+      $ref: '#/channels/userSignedup'
+    messages:
+      - UserSignedUp:
+          payload:
+            type: object
+            properties:
+              displayName:
+                type: string
+                description: Name of the user
+              email:
+                type: string
+                format: email
+                description: Email of the user
+
+bundling ./simple.yml...
+📦 Created a bundle for ./simple.yml at stdout <test>ms.
+
+`;
diff --git a/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js b/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js
index 7ec23b6a..2fd1aa2e 100644
--- a/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js
+++ b/__tests__/check-config/wrong-config-type-extensions-in-assertions/snapshot.js
@@ -4,7 +4,7 @@ exports[`E2E check-config wrong config type extension in assertions 1`] = `
 
 [1] redocly.yaml:10:13 at #/rules/rule~1metadata-lifecycle/subject/type
 
-\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "TagGroups", "TagGroup", "ExternalDocs", "Example", "ExamplesMap", "EnumDescriptions", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Logo", "Paths", "PathItem", "Parameter", "ParameterItems", "ParameterList", "Operation", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "XCodeSample", "XCodeSampleList", "XServerList", "XServer", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "DiscriminatorMapping", "Discriminator", "Components", "LinksMap", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "XMetaData", "NamedPathItems", "DependentRequired", "ServerMap", "HttpServerBinding", "HttpChannelBinding", "HttpMessageBinding", "HttpOperationBinding", "WsServerBinding", "WsChannelBinding", "WsMessageBinding", "WsOperationBinding", "KafkaServerBinding", "KafkaTopicConfiguration", "KafkaChannelBinding", "KafkaMessageBinding", "KafkaOperationBinding", "AnypointmqServerBinding", "AnypointmqChannelBinding", "AnypointmqMessageBinding", "AnypointmqOperationBinding", "AmqpServerBinding", "AmqpChannelBinding", "AmqpMessageBinding", "AmqpOperationBinding", "Amqp1ServerBinding", "Amqp1ChannelBinding", "Amqp1MessageBinding", "Amqp1OperationBinding", "MqttServerBindingLastWill", "MqttServerBinding", "MqttChannelBinding", "MqttMessageBinding", "MqttOperationBinding", "Mqtt5ServerBinding", "Mqtt5ChannelBinding", "Mqtt5MessageBinding", "Mqtt5OperationBinding", "NatsServerBinding", "NatsChannelBinding", "NatsMessageBinding", "NatsOperationBinding", "JmsServerBinding", "JmsChannelBinding", "JmsMessageBinding", "JmsOperationBinding", "SolaceServerBinding", "SolaceChannelBinding", "SolaceMessageBinding", "SolaceDestination", "SolaceOperationBinding", "StompServerBinding", "StompChannelBinding", "StompMessageBinding", "StompOperationBinding", "RedisServerBinding", "RedisChannelBinding", "RedisMessageBinding", "RedisOperationBinding", "MercureServerBinding", "MercureChannelBinding", "MercureMessageBinding", "MercureOperationBinding", "ServerBindings", "ChannelBindings", "ChannelMap", "Channel", "ParametersMap", "MessageExample", "NamedMessages", "NamedMessageTraits", "NamedOperationTraits", "NamedCorrelationIds", "NamedStreamHeaders", "SecuritySchemeFlows", "Message", "MessageBindings", "OperationBindings", "OperationTrait", "OperationTraitList", "MessageTrait", "MessageTraitList", "CorrelationId", "Dependencies", "Root.info", "Root.sourceDescriptions_items_0", "Root.sourceDescriptions_items_1", "Root.sourceDescriptions_items_2", "Root.sourceDescriptions", "Root.x-parameters_items_0", "Root.x-parameters_items_1", "Root.x-parameters", "Root.workflows_items.parameters_items_0", "Root.workflows_items.parameters_items_1", "Root.workflows_items.parameters", "Root.workflows_items.inputs", "Root.workflows_items.outputs", "Root.workflows_items.steps_items.parameters_items_0", "Root.workflows_items.steps_items.parameters_items_1", "Root.workflows_items.steps_items.parameters", "Root.workflows_items.steps_items.successCriteria_items.type_1", "Root.workflows_items.steps_items.successCriteria_items.type_2", "Root.workflows_items.steps_items.successCriteria_items", "Root.workflows_items.steps_items.successCriteria", "Root.workflows_items.steps_items.onSuccess_items.criteria_items.type_1", "Root.workflows_items.steps_items.onSuccess_items.criteria_items.type_2", "Root.workflows_items.steps_items.onSuccess_items.criteria_items", "Root.workflows_items.steps_items.onSuccess_items.criteria", "Root.workflows_items.steps_items.onSuccess_items", "Root.workflows_items.steps_items.onSuccess", "Root.workflows_items.steps_items.onFailure_items.criteria_items.type_1", "Root.workflows_items.steps_items.onFailure_items.criteria_items.type_2", "Root.workflows_items.steps_items.onFailure_items.criteria_items", "Root.workflows_items.steps_items.onFailure_items.criteria", "Root.workflows_items.steps_items.onFailure_items", "Root.workflows_items.steps_items.onFailure", "Root.workflows_items.steps_items.outputs", "Root.workflows_items.steps_items.x-expect", "Root.workflows_items.steps_items.x-operation", "Root.workflows_items.steps_items.requestBody.replacements_items", "Root.workflows_items.steps_items.requestBody.replacements", "Root.workflows_items.steps_items.requestBody", "Root.workflows_items.steps_items", "Root.workflows_items.steps", "Root.workflows_items.successActions_items.criteria_items.type_1", "Root.workflows_items.successActions_items.criteria_items.type_2", "Root.workflows_items.successActions_items.criteria_items", "Root.workflows_items.successActions_items.criteria", "Root.workflows_items.successActions_items", "Root.workflows_items.successActions", "Root.workflows_items.failureActions_items.criteria_items.type_1", "Root.workflows_items.failureActions_items.criteria_items.type_2", "Root.workflows_items.failureActions_items.criteria_items", "Root.workflows_items.failureActions_items.criteria", "Root.workflows_items.failureActions_items", "Root.workflows_items.failureActions", "Root.workflows_items", "Root.workflows", "Root.components.inputs_additionalProperties", "Root.components.inputs", "Root.components.parameters_additionalProperties_0", "Root.components.parameters_additionalProperties_1", "Root.components.parameters", "Root.components.successActions_additionalProperties.criteria_items.type_1", "Root.components.successActions_additionalProperties.criteria_items.type_2", "Root.components.successActions_additionalProperties.criteria_items", "Root.components.successActions_additionalProperties.criteria", "Root.components.successActions_additionalProperties", "Root.components.successActions", "Root.components.failureActions_additionalProperties.criteria_items.type_1", "Root.components.failureActions_additionalProperties.criteria_items.type_2", "Root.components.failureActions_additionalProperties.criteria_items", "Root.components.failureActions_additionalProperties.criteria", "Root.components.failureActions_additionalProperties", "Root.components.failureActions", "Root.components", "SpecExtension".
+\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "TagGroups", "TagGroup", "ExternalDocs", "Example", "ExamplesMap", "EnumDescriptions", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Logo", "Paths", "PathItem", "Parameter", "ParameterItems", "ParameterList", "Operation", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "XCodeSample", "XCodeSampleList", "XServerList", "XServer", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "DiscriminatorMapping", "Discriminator", "Components", "LinksMap", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "XMetaData", "NamedPathItems", "DependentRequired", "HttpServerBinding", "HttpChannelBinding", "HttpMessageBinding", "HttpOperationBinding", "WsServerBinding", "WsChannelBinding", "WsMessageBinding", "WsOperationBinding", "KafkaServerBinding", "KafkaTopicConfiguration", "KafkaChannelBinding", "KafkaMessageBinding", "KafkaOperationBinding", "AnypointmqServerBinding", "AnypointmqChannelBinding", "AnypointmqMessageBinding", "AnypointmqOperationBinding", "AmqpServerBinding", "AmqpChannelBinding", "AmqpMessageBinding", "AmqpOperationBinding", "Amqp1ServerBinding", "Amqp1ChannelBinding", "Amqp1MessageBinding", "Amqp1OperationBinding", "MqttServerBindingLastWill", "MqttServerBinding", "MqttChannelBinding", "MqttMessageBinding", "MqttOperationBinding", "Mqtt5ServerBinding", "Mqtt5ChannelBinding", "Mqtt5MessageBinding", "Mqtt5OperationBinding", "NatsServerBinding", "NatsChannelBinding", "NatsMessageBinding", "NatsOperationBinding", "JmsServerBinding", "JmsChannelBinding", "JmsMessageBinding", "JmsOperationBinding", "SolaceServerBinding", "SolaceChannelBinding", "SolaceMessageBinding", "SolaceDestination", "SolaceOperationBinding", "StompServerBinding", "StompChannelBinding", "StompMessageBinding", "StompOperationBinding", "RedisServerBinding", "RedisChannelBinding", "RedisMessageBinding", "RedisOperationBinding", "MercureServerBinding", "MercureChannelBinding", "MercureMessageBinding", "MercureOperationBinding", "ServerBindings", "ChannelBindings", "MessageBindings", "OperationBindings", "ServerMap", "ChannelMap", "Channel", "ParametersMap", "MessageExample", "NamedMessages", "NamedMessageTraits", "NamedOperationTraits", "NamedCorrelationIds", "SecuritySchemeFlows", "Message", "OperationTrait", "OperationTraitList", "MessageTrait", "MessageTraitList", "MessageExampleList", "CorrelationId", "Dependencies", "OperationReply", "OperationReplyAddress", "NamedTags", "NamedExternalDocs", "NamedChannels", "NamedOperations", "NamedOperationReplies", "NamedOperationRelyAddresses", "SecuritySchemeList", "MessageList", "Root.info", "Root.sourceDescriptions_items_0", "Root.sourceDescriptions_items_1", "Root.sourceDescriptions_items_2", "Root.sourceDescriptions", "Root.x-parameters_items_0", "Root.x-parameters_items_1", "Root.x-parameters", "Root.workflows_items.parameters_items_0", "Root.workflows_items.parameters_items_1", "Root.workflows_items.parameters", "Root.workflows_items.inputs", "Root.workflows_items.outputs", "Root.workflows_items.steps_items.parameters_items_0", "Root.workflows_items.steps_items.parameters_items_1", "Root.workflows_items.steps_items.parameters", "Root.workflows_items.steps_items.successCriteria_items.type_1", "Root.workflows_items.steps_items.successCriteria_items.type_2", "Root.workflows_items.steps_items.successCriteria_items", "Root.workflows_items.steps_items.successCriteria", "Root.workflows_items.steps_items.onSuccess_items.criteria_items.type_1", "Root.workflows_items.steps_items.onSuccess_items.criteria_items.type_2", "Root.workflows_items.steps_items.onSuccess_items.criteria_items", "Root.workflows_items.steps_items.onSuccess_items.criteria", "Root.workflows_items.steps_items.onSuccess_items", "Root.workflows_items.steps_items.onSuccess", "Root.workflows_items.steps_items.onFailure_items.criteria_items.type_1", "Root.workflows_items.steps_items.onFailure_items.criteria_items.type_2", "Root.workflows_items.steps_items.onFailure_items.criteria_items", "Root.workflows_items.steps_items.onFailure_items.criteria", "Root.workflows_items.steps_items.onFailure_items", "Root.workflows_items.steps_items.onFailure", "Root.workflows_items.steps_items.outputs", "Root.workflows_items.steps_items.x-expect", "Root.workflows_items.steps_items.x-operation", "Root.workflows_items.steps_items.requestBody.replacements_items", "Root.workflows_items.steps_items.requestBody.replacements", "Root.workflows_items.steps_items.requestBody", "Root.workflows_items.steps_items", "Root.workflows_items.steps", "Root.workflows_items.successActions_items.criteria_items.type_1", "Root.workflows_items.successActions_items.criteria_items.type_2", "Root.workflows_items.successActions_items.criteria_items", "Root.workflows_items.successActions_items.criteria", "Root.workflows_items.successActions_items", "Root.workflows_items.successActions", "Root.workflows_items.failureActions_items.criteria_items.type_1", "Root.workflows_items.failureActions_items.criteria_items.type_2", "Root.workflows_items.failureActions_items.criteria_items", "Root.workflows_items.failureActions_items.criteria", "Root.workflows_items.failureActions_items", "Root.workflows_items.failureActions", "Root.workflows_items", "Root.workflows", "Root.components.inputs_additionalProperties", "Root.components.inputs", "Root.components.parameters_additionalProperties_0", "Root.components.parameters_additionalProperties_1", "Root.components.parameters", "Root.components.successActions_additionalProperties.criteria_items.type_1", "Root.components.successActions_additionalProperties.criteria_items.type_2", "Root.components.successActions_additionalProperties.criteria_items", "Root.components.successActions_additionalProperties.criteria", "Root.components.successActions_additionalProperties", "Root.components.successActions", "Root.components.failureActions_additionalProperties.criteria_items.type_1", "Root.components.failureActions_additionalProperties.criteria_items.type_2", "Root.components.failureActions_additionalProperties.criteria_items", "Root.components.failureActions_additionalProperties.criteria", "Root.components.failureActions_additionalProperties", "Root.components.failureActions", "Root.components", "SpecExtension".
 
  8 | rule/metadata-lifecycle:
  9 |   subject:
diff --git a/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js b/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js
index fb82e2a9..582839e3 100644
--- a/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js
+++ b/__tests__/lint-config/invalid-config-assertation-config-type/snapshot.js
@@ -6,7 +6,7 @@ exports[`E2E lint-config test with option: { dirName: 'invalid-config-assertatio
 The 'assert/' syntax in assert/path-item-mutually-required is deprecated. Update your configuration to use 'rule/' instead. Examples and more information: https://redocly.com/docs/cli/rules/configurable-rules/
 [1] .redocly.yaml:9:17 at #/rules/assert~1path-item-mutually-required/where/0/subject/type
 
-\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "TagGroups", "TagGroup", "ExternalDocs", "Example", "ExamplesMap", "EnumDescriptions", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Logo", "Paths", "PathItem", "Parameter", "ParameterItems", "ParameterList", "Operation", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "XCodeSample", "XCodeSampleList", "XServerList", "XServer", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "DiscriminatorMapping", "Discriminator", "Components", "LinksMap", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "NamedPathItems", "DependentRequired", "ServerMap", "HttpServerBinding", "HttpChannelBinding", "HttpMessageBinding", "HttpOperationBinding", "WsServerBinding", "WsChannelBinding", "WsMessageBinding", "WsOperationBinding", "KafkaServerBinding", "KafkaTopicConfiguration", "KafkaChannelBinding", "KafkaMessageBinding", "KafkaOperationBinding", "AnypointmqServerBinding", "AnypointmqChannelBinding", "AnypointmqMessageBinding", "AnypointmqOperationBinding", "AmqpServerBinding", "AmqpChannelBinding", "AmqpMessageBinding", "AmqpOperationBinding", "Amqp1ServerBinding", "Amqp1ChannelBinding", "Amqp1MessageBinding", "Amqp1OperationBinding", "MqttServerBindingLastWill", "MqttServerBinding", "MqttChannelBinding", "MqttMessageBinding", "MqttOperationBinding", "Mqtt5ServerBinding", "Mqtt5ChannelBinding", "Mqtt5MessageBinding", "Mqtt5OperationBinding", "NatsServerBinding", "NatsChannelBinding", "NatsMessageBinding", "NatsOperationBinding", "JmsServerBinding", "JmsChannelBinding", "JmsMessageBinding", "JmsOperationBinding", "SolaceServerBinding", "SolaceChannelBinding", "SolaceMessageBinding", "SolaceDestination", "SolaceOperationBinding", "StompServerBinding", "StompChannelBinding", "StompMessageBinding", "StompOperationBinding", "RedisServerBinding", "RedisChannelBinding", "RedisMessageBinding", "RedisOperationBinding", "MercureServerBinding", "MercureChannelBinding", "MercureMessageBinding", "MercureOperationBinding", "ServerBindings", "ChannelBindings", "ChannelMap", "Channel", "ParametersMap", "MessageExample", "NamedMessages", "NamedMessageTraits", "NamedOperationTraits", "NamedCorrelationIds", "NamedStreamHeaders", "SecuritySchemeFlows", "Message", "MessageBindings", "OperationBindings", "OperationTrait", "OperationTraitList", "MessageTrait", "MessageTraitList", "CorrelationId", "Dependencies", "Root.info", "Root.sourceDescriptions_items_0", "Root.sourceDescriptions_items_1", "Root.sourceDescriptions_items_2", "Root.sourceDescriptions", "Root.x-parameters_items_0", "Root.x-parameters_items_1", "Root.x-parameters", "Root.workflows_items.parameters_items_0", "Root.workflows_items.parameters_items_1", "Root.workflows_items.parameters", "Root.workflows_items.inputs", "Root.workflows_items.outputs", "Root.workflows_items.steps_items.parameters_items_0", "Root.workflows_items.steps_items.parameters_items_1", "Root.workflows_items.steps_items.parameters", "Root.workflows_items.steps_items.successCriteria_items.type_1", "Root.workflows_items.steps_items.successCriteria_items.type_2", "Root.workflows_items.steps_items.successCriteria_items", "Root.workflows_items.steps_items.successCriteria", "Root.workflows_items.steps_items.onSuccess_items.criteria_items.type_1", "Root.workflows_items.steps_items.onSuccess_items.criteria_items.type_2", "Root.workflows_items.steps_items.onSuccess_items.criteria_items", "Root.workflows_items.steps_items.onSuccess_items.criteria", "Root.workflows_items.steps_items.onSuccess_items", "Root.workflows_items.steps_items.onSuccess", "Root.workflows_items.steps_items.onFailure_items.criteria_items.type_1", "Root.workflows_items.steps_items.onFailure_items.criteria_items.type_2", "Root.workflows_items.steps_items.onFailure_items.criteria_items", "Root.workflows_items.steps_items.onFailure_items.criteria", "Root.workflows_items.steps_items.onFailure_items", "Root.workflows_items.steps_items.onFailure", "Root.workflows_items.steps_items.outputs", "Root.workflows_items.steps_items.x-expect", "Root.workflows_items.steps_items.x-operation", "Root.workflows_items.steps_items.requestBody.replacements_items", "Root.workflows_items.steps_items.requestBody.replacements", "Root.workflows_items.steps_items.requestBody", "Root.workflows_items.steps_items", "Root.workflows_items.steps", "Root.workflows_items.successActions_items.criteria_items.type_1", "Root.workflows_items.successActions_items.criteria_items.type_2", "Root.workflows_items.successActions_items.criteria_items", "Root.workflows_items.successActions_items.criteria", "Root.workflows_items.successActions_items", "Root.workflows_items.successActions", "Root.workflows_items.failureActions_items.criteria_items.type_1", "Root.workflows_items.failureActions_items.criteria_items.type_2", "Root.workflows_items.failureActions_items.criteria_items", "Root.workflows_items.failureActions_items.criteria", "Root.workflows_items.failureActions_items", "Root.workflows_items.failureActions", "Root.workflows_items", "Root.workflows", "Root.components.inputs_additionalProperties", "Root.components.inputs", "Root.components.parameters_additionalProperties_0", "Root.components.parameters_additionalProperties_1", "Root.components.parameters", "Root.components.successActions_additionalProperties.criteria_items.type_1", "Root.components.successActions_additionalProperties.criteria_items.type_2", "Root.components.successActions_additionalProperties.criteria_items", "Root.components.successActions_additionalProperties.criteria", "Root.components.successActions_additionalProperties", "Root.components.successActions", "Root.components.failureActions_additionalProperties.criteria_items.type_1", "Root.components.failureActions_additionalProperties.criteria_items.type_2", "Root.components.failureActions_additionalProperties.criteria_items", "Root.components.failureActions_additionalProperties.criteria", "Root.components.failureActions_additionalProperties", "Root.components.failureActions", "Root.components", "SpecExtension".
+\`type\` can be one of the following only: "any", "Root", "Tag", "TagList", "TagGroups", "TagGroup", "ExternalDocs", "Example", "ExamplesMap", "EnumDescriptions", "SecurityRequirement", "SecurityRequirementList", "Info", "Contact", "License", "Logo", "Paths", "PathItem", "Parameter", "ParameterItems", "ParameterList", "Operation", "Examples", "Header", "Responses", "Response", "Schema", "Xml", "SchemaProperties", "NamedSchemas", "NamedResponses", "NamedParameters", "NamedSecuritySchemes", "SecurityScheme", "XCodeSample", "XCodeSampleList", "XServerList", "XServer", "Server", "ServerList", "ServerVariable", "ServerVariablesMap", "Callback", "CallbacksMap", "RequestBody", "MediaTypesMap", "MediaType", "Encoding", "EncodingMap", "HeadersMap", "Link", "DiscriminatorMapping", "Discriminator", "Components", "LinksMap", "NamedExamples", "NamedRequestBodies", "NamedHeaders", "NamedLinks", "NamedCallbacks", "ImplicitFlow", "PasswordFlow", "ClientCredentials", "AuthorizationCode", "OAuth2Flows", "XUsePkce", "WebhooksMap", "NamedPathItems", "DependentRequired", "HttpServerBinding", "HttpChannelBinding", "HttpMessageBinding", "HttpOperationBinding", "WsServerBinding", "WsChannelBinding", "WsMessageBinding", "WsOperationBinding", "KafkaServerBinding", "KafkaTopicConfiguration", "KafkaChannelBinding", "KafkaMessageBinding", "KafkaOperationBinding", "AnypointmqServerBinding", "AnypointmqChannelBinding", "AnypointmqMessageBinding", "AnypointmqOperationBinding", "AmqpServerBinding", "AmqpChannelBinding", "AmqpMessageBinding", "AmqpOperationBinding", "Amqp1ServerBinding", "Amqp1ChannelBinding", "Amqp1MessageBinding", "Amqp1OperationBinding", "MqttServerBindingLastWill", "MqttServerBinding", "MqttChannelBinding", "MqttMessageBinding", "MqttOperationBinding", "Mqtt5ServerBinding", "Mqtt5ChannelBinding", "Mqtt5MessageBinding", "Mqtt5OperationBinding", "NatsServerBinding", "NatsChannelBinding", "NatsMessageBinding", "NatsOperationBinding", "JmsServerBinding", "JmsChannelBinding", "JmsMessageBinding", "JmsOperationBinding", "SolaceServerBinding", "SolaceChannelBinding", "SolaceMessageBinding", "SolaceDestination", "SolaceOperationBinding", "StompServerBinding", "StompChannelBinding", "StompMessageBinding", "StompOperationBinding", "RedisServerBinding", "RedisChannelBinding", "RedisMessageBinding", "RedisOperationBinding", "MercureServerBinding", "MercureChannelBinding", "MercureMessageBinding", "MercureOperationBinding", "ServerBindings", "ChannelBindings", "MessageBindings", "OperationBindings", "ServerMap", "ChannelMap", "Channel", "ParametersMap", "MessageExample", "NamedMessages", "NamedMessageTraits", "NamedOperationTraits", "NamedCorrelationIds", "SecuritySchemeFlows", "Message", "OperationTrait", "OperationTraitList", "MessageTrait", "MessageTraitList", "MessageExampleList", "CorrelationId", "Dependencies", "OperationReply", "OperationReplyAddress", "NamedTags", "NamedExternalDocs", "NamedChannels", "NamedOperations", "NamedOperationReplies", "NamedOperationRelyAddresses", "SecuritySchemeList", "MessageList", "Root.info", "Root.sourceDescriptions_items_0", "Root.sourceDescriptions_items_1", "Root.sourceDescriptions_items_2", "Root.sourceDescriptions", "Root.x-parameters_items_0", "Root.x-parameters_items_1", "Root.x-parameters", "Root.workflows_items.parameters_items_0", "Root.workflows_items.parameters_items_1", "Root.workflows_items.parameters", "Root.workflows_items.inputs", "Root.workflows_items.outputs", "Root.workflows_items.steps_items.parameters_items_0", "Root.workflows_items.steps_items.parameters_items_1", "Root.workflows_items.steps_items.parameters", "Root.workflows_items.steps_items.successCriteria_items.type_1", "Root.workflows_items.steps_items.successCriteria_items.type_2", "Root.workflows_items.steps_items.successCriteria_items", "Root.workflows_items.steps_items.successCriteria", "Root.workflows_items.steps_items.onSuccess_items.criteria_items.type_1", "Root.workflows_items.steps_items.onSuccess_items.criteria_items.type_2", "Root.workflows_items.steps_items.onSuccess_items.criteria_items", "Root.workflows_items.steps_items.onSuccess_items.criteria", "Root.workflows_items.steps_items.onSuccess_items", "Root.workflows_items.steps_items.onSuccess", "Root.workflows_items.steps_items.onFailure_items.criteria_items.type_1", "Root.workflows_items.steps_items.onFailure_items.criteria_items.type_2", "Root.workflows_items.steps_items.onFailure_items.criteria_items", "Root.workflows_items.steps_items.onFailure_items.criteria", "Root.workflows_items.steps_items.onFailure_items", "Root.workflows_items.steps_items.onFailure", "Root.workflows_items.steps_items.outputs", "Root.workflows_items.steps_items.x-expect", "Root.workflows_items.steps_items.x-operation", "Root.workflows_items.steps_items.requestBody.replacements_items", "Root.workflows_items.steps_items.requestBody.replacements", "Root.workflows_items.steps_items.requestBody", "Root.workflows_items.steps_items", "Root.workflows_items.steps", "Root.workflows_items.successActions_items.criteria_items.type_1", "Root.workflows_items.successActions_items.criteria_items.type_2", "Root.workflows_items.successActions_items.criteria_items", "Root.workflows_items.successActions_items.criteria", "Root.workflows_items.successActions_items", "Root.workflows_items.successActions", "Root.workflows_items.failureActions_items.criteria_items.type_1", "Root.workflows_items.failureActions_items.criteria_items.type_2", "Root.workflows_items.failureActions_items.criteria_items", "Root.workflows_items.failureActions_items.criteria", "Root.workflows_items.failureActions_items", "Root.workflows_items.failureActions", "Root.workflows_items", "Root.workflows", "Root.components.inputs_additionalProperties", "Root.components.inputs", "Root.components.parameters_additionalProperties_0", "Root.components.parameters_additionalProperties_1", "Root.components.parameters", "Root.components.successActions_additionalProperties.criteria_items.type_1", "Root.components.successActions_additionalProperties.criteria_items.type_2", "Root.components.successActions_additionalProperties.criteria_items", "Root.components.successActions_additionalProperties.criteria", "Root.components.successActions_additionalProperties", "Root.components.successActions", "Root.components.failureActions_additionalProperties.criteria_items.type_1", "Root.components.failureActions_additionalProperties.criteria_items.type_2", "Root.components.failureActions_additionalProperties.criteria_items", "Root.components.failureActions_additionalProperties.criteria", "Root.components.failureActions_additionalProperties", "Root.components.failureActions", "Root.components", "SpecExtension".
 
  7 | where:
  8 |   - subject:
diff --git a/__tests__/lint/async3/anyof.yml b/__tests__/lint/async3/anyof.yml
new file mode 100644
index 00000000..a2df2d2a
--- /dev/null
+++ b/__tests__/lint/async3/anyof.yml
@@ -0,0 +1,36 @@
+asyncapi: 3.0.0
+info:
+  title: AnyOf example
+  version: 1.0.0
+channels:
+  test:
+    address: test
+    messages:
+      testMessages:
+        $ref: '#/components/messages/testMessages'
+operations:
+  test:
+    action: receive
+    channel:
+      $ref: '#/channels/test'
+    messages:
+      - $ref: '#/channels/test/messages/testMessages'
+components:
+  messages:
+    testMessages:
+      payload:
+        anyOf:
+          - $ref: '#/components/schemas/objectWithKey'
+          - $ref: '#/components/schemas/objectWithKey2'
+  schemas:
+    objectWithKey:
+      type: object
+      properties:
+        key:
+          type: string
+          additionalProperties: false
+    objectWithKey2:
+      type: object
+      properties:
+        key2:
+          type: string
diff --git a/__tests__/lint/async3/application-headers.yml b/__tests__/lint/async3/application-headers.yml
new file mode 100644
index 00000000..d7fcdc05
--- /dev/null
+++ b/__tests__/lint/async3/application-headers.yml
@@ -0,0 +1,86 @@
+asyncapi: 3.0.0
+info:
+  title: Application Headers example
+  version: 1.0.0
+  description: A cut of the Streetlights API to test application header changes supporting
+  license:
+    name: Apache 2.0
+    url: 'https://www.apache.org/licenses/LICENSE-2.0'
+defaultContentType: application/json
+servers:
+  production:
+    host: 'test.mosquitto.org:{port}'
+    protocol: mqtt
+    description: Test broker
+    variables:
+      port:
+        description: Secure connection (TLS) is available through port 8883.
+        default: '1883'
+        enum:
+          - '1883'
+          - '8883'
+channels:
+  lightingMeasured:
+    address: 'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured'
+    messages:
+      lightMeasured:
+        $ref: '#/components/messages/lightMeasured'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+operations:
+  receiveLightMeasurement:
+    action: receive
+    channel:
+      $ref: '#/channels/lightingMeasured'
+    summary: >-
+      Inform about environmental lighting conditions of a particular
+      streetlight.
+    messages:
+      - $ref: '#/channels/lightingMeasured/messages/lightMeasured'
+components:
+  messages:
+    lightMeasured:
+      name: lightMeasured
+      title: Light measured
+      summary: >-
+        Inform about environmental lighting conditions of a particular
+        streetlight.
+      correlationId:
+        location: $message.header#/MQMD/CorrelId
+      contentType: application/json
+      headers:
+        type: object
+        properties:
+          MQMD:
+            type: object
+            properties:
+              CorrelId:
+                type: string
+                minLength: 24
+                maxLength: 24
+                format: binary
+          applicationInstanceId:
+            $ref: '#/components/schemas/applicationInstanceId'
+      payload:
+        $ref: '#/components/schemas/lightMeasuredPayload'
+  schemas:
+    lightMeasuredPayload:
+      type: object
+      properties:
+        lumens:
+          type: integer
+          minimum: 0
+          description: Light intensity measured in lumens.
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    sentAt:
+      type: string
+      format: date-time
+      description: Date and time when the message was sent.
+    applicationInstanceId:
+      description: Unique identifier for a given instance of the publishing application
+      type: string
+  parameters:
+    streetlightId:
+      description: The ID of the streetlight.
diff --git a/__tests__/lint/async3/correlation-id.yml b/__tests__/lint/async3/correlation-id.yml
new file mode 100644
index 00000000..ec09bc37
--- /dev/null
+++ b/__tests__/lint/async3/correlation-id.yml
@@ -0,0 +1,180 @@
+asyncapi: 3.0.0
+info:
+  title: Correlation ID Example
+  version: 1.0.0
+  description: A cut of the Streetlights API to test Correlation ID
+  license:
+    name: Apache 2.0
+    url: 'https://www.apache.org/licenses/LICENSE-2.0'
+defaultContentType: application/json
+servers:
+  production:
+    host: 'test.mosquitto.org:{port}'
+    protocol: mqtt
+    description: Test broker
+    variables:
+      port:
+        description: Secure connection (TLS) is available through port 8883.
+        default: '1883'
+        enum:
+          - '1883'
+          - '8883'
+    security:
+      - $ref: '#/components/securitySchemes/apiKey'
+      - type: oauth2
+        description: Flows to support OAuth 2.0
+        flows:
+          implicit:
+            authorizationUrl: 'https://authserver.example/auth'
+            availableScopes:
+              'streetlights:on': Ability to switch lights on
+              'streetlights:off': Ability to switch lights off
+              'streetlights:dim': Ability to dim the lights
+          password:
+            tokenUrl: 'https://authserver.example/token'
+            availableScopes:
+              'streetlights:on': Ability to switch lights on
+              'streetlights:off': Ability to switch lights off
+              'streetlights:dim': Ability to dim the lights
+          clientCredentials:
+            tokenUrl: 'https://authserver.example/token'
+            availableScopes:
+              'streetlights:on': Ability to switch lights on
+              'streetlights:off': Ability to switch lights off
+              'streetlights:dim': Ability to dim the lights
+          authorizationCode:
+            authorizationUrl: 'https://authserver.example/auth'
+            tokenUrl: 'https://authserver.example/token'
+            refreshUrl: 'https://authserver.example/refresh'
+            availableScopes:
+              'streetlights:on': Ability to switch lights on
+              'streetlights:off': Ability to switch lights off
+              'streetlights:dim': Ability to dim the lights
+        scopes:
+          - 'streetlights:on'
+          - 'streetlights:off'
+          - 'streetlights:dim'
+      - $ref: '#/components/securitySchemes/openIdConnectWellKnown'
+channels:
+  lightingMeasured:
+    address: 'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured'
+    messages:
+      lightMeasured:
+        $ref: '#/components/messages/lightMeasured'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+  lightsDim:
+    address: 'smartylighting/streetlights/1/0/action/{streetlightId}/dim'
+    messages:
+      dimLight:
+        $ref: '#/components/messages/dimLight'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+operations:
+  receiveLightMeasurement:
+    action: receive
+    channel:
+      $ref: '#/channels/lightingMeasured'
+    summary: >-
+      Inform about environmental lighting conditions of a particular
+      streetlight.
+    messages:
+      - $ref: '#/channels/lightingMeasured/messages/lightMeasured'
+  dimLight:
+    action: send
+    channel:
+      $ref: '#/channels/lightsDim'
+    messages:
+      - $ref: '#/channels/lightsDim/messages/dimLight'
+components:
+  messages:
+    lightMeasured:
+      name: lightMeasured
+      title: Light measured
+      summary: >-
+        Inform about environmental lighting conditions of a particular
+        streetlight.
+      correlationId:
+        location: $message.header#/MQMD/CorrelId
+      contentType: application/json
+      payload:
+        $ref: '#/components/schemas/lightMeasuredPayload'
+    dimLight:
+      name: dimLight
+      title: Dim light
+      summary: Command a particular streetlight to dim the lights.
+      correlationId:
+        $ref: '#/components/correlationIds/sentAtCorrelator'
+      payload:
+        $ref: '#/components/schemas/dimLightPayload'
+  schemas:
+    lightMeasuredPayload:
+      type: object
+      properties:
+        lumens:
+          type: integer
+          minimum: 0
+          description: Light intensity measured in lumens.
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    dimLightPayload:
+      type: object
+      properties:
+        percentage:
+          type: integer
+          description: Percentage to which the light should be dimmed to.
+          minimum: 0
+          maximum: 100
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    sentAt:
+      type: string
+      format: date-time
+      description: Date and time when the message was sent.
+  parameters:
+    streetlightId:
+      description: The ID of the streetlight.
+  correlationIds:
+    sentAtCorrelator:
+      description: Data from message payload used as correlation ID
+      location: $message.payload#/sentAt
+  securitySchemes:
+    apiKey:
+      type: apiKey
+      in: user
+      description: Provide your API key as the user and leave the password empty.
+    supportedOauthFlows:
+      type: oauth2
+      description: Flows to support OAuth 2.0
+      flows:
+        implicit:
+          authorizationUrl: 'https://authserver.example/auth'
+          availableScopes:
+            'streetlights:on': Ability to switch lights on
+            'streetlights:off': Ability to switch lights off
+            'streetlights:dim': Ability to dim the lights
+        password:
+          tokenUrl: 'https://authserver.example/token'
+          availableScopes:
+            'streetlights:on': Ability to switch lights on
+            'streetlights:off': Ability to switch lights off
+            'streetlights:dim': Ability to dim the lights
+        clientCredentials:
+          tokenUrl: 'https://authserver.example/token'
+          availableScopes:
+            'streetlights:on': Ability to switch lights on
+            'streetlights:off': Ability to switch lights off
+            'streetlights:dim': Ability to dim the lights
+        authorizationCode:
+          authorizationUrl: 'https://authserver.example/auth'
+          tokenUrl: 'https://authserver.example/token'
+          refreshUrl: 'https://authserver.example/refresh'
+          availableScopes:
+            'streetlights:on': Ability to switch lights on
+            'streetlights:off': Ability to switch lights off
+            'streetlights:dim': Ability to dim the lights
+    openIdConnectWellKnown:
+      type: openIdConnect
+      openIdConnectUrl: 'https://authserver.example/.well-known'
diff --git a/__tests__/lint/async3/gitter-streaming.yml b/__tests__/lint/async3/gitter-streaming.yml
new file mode 100644
index 00000000..012b7df0
--- /dev/null
+++ b/__tests__/lint/async3/gitter-streaming.yml
@@ -0,0 +1,178 @@
+asyncapi: 3.0.0
+id: 'tag:stream.gitter.im,2022:api'
+info:
+  title: Gitter Streaming API
+  version: 1.0.0
+servers:
+  production:
+    host: stream.gitter.im
+    pathname: /v1
+    protocol: https
+    protocolVersion: '1.1'
+    security:
+      - $ref: '#/components/securitySchemes/httpBearerToken'
+channels:
+  rooms:
+    address: '/rooms/{roomId}/{resource}'
+    messages:
+      chatMessage:
+        $ref: '#/components/messages/chatMessage'
+      heartbeat:
+        $ref: '#/components/messages/heartbeat'
+    parameters:
+      roomId:
+        description: Id of the Gitter room.
+        examples:
+          - 53307860c3599d1de448e19d
+      resource:
+        enum:
+          - chatMessages
+          - events
+        description: The resource to consume.
+operations:
+  sendRoomInfo:
+    action: send
+    channel:
+      $ref: '#/channels/rooms'
+    bindings:
+      http:
+        method: POST
+    messages:
+      - $ref: '#/channels/rooms/messages/chatMessage'
+      - $ref: '#/channels/rooms/messages/heartbeat'
+components:
+  securitySchemes:
+    httpBearerToken:
+      type: http
+      scheme: bearer
+  messages:
+    chatMessage:
+      summary: >-
+        A message represents an individual chat message sent to a room. They are
+        a sub-resource of a room.
+      payload:
+        schemaFormat: application/schema+yaml;version=draft-07
+        schema:
+          type: object
+          properties:
+            id:
+              type: string
+              description: ID of the message.
+            text:
+              type: string
+              description: Original message in plain-text/markdown.
+            html:
+              type: string
+              description: HTML formatted message.
+            sent:
+              type: string
+              format: date-time
+              description: ISO formatted date of the message.
+            fromUser:
+              type: object
+              description: User that sent the message.
+              properties:
+                id:
+                  type: string
+                  description: Gitter User ID.
+                username:
+                  type: string
+                  description: Gitter/GitHub username.
+                displayName:
+                  type: string
+                  description: Gitter/GitHub user real name.
+                url:
+                  type: string
+                  description: Path to the user on Gitter.
+                avatarUrl:
+                  type: string
+                  format: uri
+                  description: User avatar URI.
+                avatarUrlSmall:
+                  type: string
+                  format: uri
+                  description: User avatar URI (small).
+                avatarUrlMedium:
+                  type: string
+                  format: uri
+                  description: User avatar URI (medium).
+                v:
+                  type: number
+                  description: Version.
+                gv:
+                  type: string
+                  description: Stands for "Gravatar version" and is used for cache busting.
+            unread:
+              type: boolean
+              description: Boolean that indicates if the current user has read the message.
+            readBy:
+              type: number
+              description: Number of users that have read the message.
+            urls:
+              type: array
+              description: List of URLs present in the message.
+              items:
+                type: string
+                format: uri
+            mentions:
+              type: array
+              description: List of @Mentions in the message.
+              items:
+                type: object
+                properties:
+                  screenName:
+                    type: string
+                  userId:
+                    type: string
+                  userIds:
+                    type: array
+                    items:
+                      type: string
+            issues:
+              type: array
+              description: 'List of #Issues referenced in the message.'
+              items:
+                type: object
+                properties:
+                  number:
+                    type: string
+            meta:
+              type: array
+              description: Metadata. This is currently not used for anything.
+              items: {}
+            v:
+              type: number
+              description: Version.
+            gv:
+              type: string
+              description: Stands for "Gravatar version" and is used for cache busting.
+      bindings:
+        http:
+          headers:
+            type: object
+            properties:
+              Transfer-Encoding:
+                type: string
+                const: chunked
+              Trailer:
+                type: string
+                const: \r\n
+    heartbeat:
+      summary: Its purpose is to keep the connection alive.
+      payload:
+        schemaFormat: application/schema+yaml;version=draft-07
+        schema:
+          type: string
+          enum:
+            - "\r\n"
+      bindings:
+        http:
+          headers:
+            type: object
+            properties:
+              Transfer-Encoding:
+                type: string
+                const: chunked
+              Trailer:
+                type: string
+                const: \r\n
diff --git a/__tests__/lint/async3/mercure.yml b/__tests__/lint/async3/mercure.yml
new file mode 100644
index 00000000..2fc1238c
--- /dev/null
+++ b/__tests__/lint/async3/mercure.yml
@@ -0,0 +1,58 @@
+asyncapi: 3.0.0
+info:
+  title: Mercure Hub Example
+  version: 1.0.0
+  description: This example demonstrates how to define a Mercure hub.
+defaultContentType: application/ld+json
+servers:
+  production:
+    host: demo.mercure.rocks
+    pathname: /.well-known/mercure
+    protocol: mercure
+channels:
+  books:
+    address: 'https://example.com/books/{id}'
+    messages:
+      book:
+        $ref: '#/components/messages/book'
+    description: >-
+      Every time a resource of type `http://schema.org/Book` is created or
+      modified, a JSON-LD representation of the new version of this resource
+      must be pushed in this Mercure topic.
+    parameters:
+      id:
+        description: ID of the book
+operations:
+  ReceiveBooksInfo:
+    action: receive
+    channel:
+      $ref: '#/channels/books'
+    messages:
+      - $ref: '#/channels/books/messages/book'
+  SendBooksInfo:
+    action: send
+    channel:
+      $ref: '#/channels/books'
+    messages:
+      - $ref: '#/channels/books/messages/book'
+components:
+  messages:
+    book:
+      summary: The content of a book resource.
+      externalDocs:
+        url: 'https://schema.org/Book'
+      payload:
+        type: object
+        properties:
+          '@id':
+            type: string
+            format: iri-reference
+          '@type':
+            type: string
+            format: iri-reference
+          name:
+            type: string
+          isbn:
+            type: string
+          abstract:
+            type: string
diff --git a/__tests__/lint/async3/not.yml b/__tests__/lint/async3/not.yml
new file mode 100644
index 00000000..ecd46504
--- /dev/null
+++ b/__tests__/lint/async3/not.yml
@@ -0,0 +1,29 @@
+asyncapi: 3.0.0
+info:
+  title: Not example
+  version: 1.0.0
+channels:
+  test:
+    address: test
+    messages:
+      testMessages:
+        $ref: '#/components/messages/testMessages'
+operations:
+  onTestMsg:
+    action: receive
+    channel:
+      $ref: '#/channels/test'
+    messages:
+      - $ref: '#/channels/test/messages/testMessages'
+components:
+  messages:
+    testMessages:
+      payload:
+        $ref: '#/components/schemas/testSchema'
+  schemas:
+    testSchema:
+      type: object
+      properties:
+        key:
+          not:
+            type: integer
diff --git a/__tests__/lint/async3/oneof.yml b/__tests__/lint/async3/oneof.yml
new file mode 100644
index 00000000..8e9879c6
--- /dev/null
+++ b/__tests__/lint/async3/oneof.yml
@@ -0,0 +1,57 @@
+asyncapi: 3.0.0
+info:
+  title: OneOf example
+  version: 1.0.0
+channels:
+  test:
+    address: test
+    messages:
+      testMessages:
+        $ref: '#/components/messages/testMessages'
+  test2:
+    address: test2
+    messages:
+      objectWithKey:
+        payload:
+          $ref: '#/components/schemas/objectWithKey'
+      objectWithKey2:
+        payload:
+          $ref: '#/components/schemas/objectWithKey2'
+operations:
+  onTestMsg:
+    action: receive
+    channel:
+      $ref: '#/channels/test'
+    messages:
+      - $ref: '#/channels/test/messages/testMessages'
+  sendTest:
+    action: send
+    channel:
+      $ref: '#/channels/test2'
+    messages:
+      - $ref: '#/channels/test2/messages/objectWithKey'
+      - $ref: '#/channels/test2/messages/objectWithKey2'
+components:
+  messages:
+    testMessages:
+      payload:
+        oneOf:
+          - $ref: '#/components/schemas/objectWithKey'
+          - $ref: '#/components/schemas/objectWithKey2'
+    testMessage1:
+      payload:
+        $ref: '#/components/schemas/objectWithKey'
+    testMessage2:
+      payload:
+        $ref: '#/components/schemas/objectWithKey2'
+  schemas:
+    objectWithKey:
+      type: object
+      properties:
+        key:
+          type: string
+    objectWithKey2:
+      type: object
+      properties:
+        key2:
+          type: string
diff --git a/__tests__/lint/async3/operation-security.yml b/__tests__/lint/async3/operation-security.yml
new file mode 100644
index 00000000..ad900e35
--- /dev/null
+++ b/__tests__/lint/async3/operation-security.yml
@@ -0,0 +1,117 @@
+asyncapi: 3.0.0
+info:
+  title: Notifications
+  version: 1.0.0
+  description: >-
+    This contract defines HTTP Push notification for  application authorization
+    revocation topic
+channels:
+  authRevoke:
+    address: AUTHORIZATION_REVOCATION
+    messages:
+      message:
+        $ref: '#/components/messages/message'
+operations:
+  sendAuthRevoke:
+    action: send
+    channel:
+      $ref: '#/channels/authRevoke'
+    security:
+      - type: oauth2
+        description: The oauth security descriptions
+        flows:
+          clientCredentials:
+            tokenUrl: 'https://example.com/api/oauth/dialog'
+            availableScopes:
+              'subscribe:auth_revocations': Scope required for authorization revocation topic
+        scopes:
+          - 'subscribe:auth_revocations'
+    bindings:
+      http:
+        method: POST
+    messages:
+      - $ref: '#/channels/authRevoke/messages/message'
+components:
+  messages:
+    message:
+      headers:
+        type: object
+        properties:
+          X-SIGNATURE:
+            description: ECC message signature
+            type: string
+          Content-Type:
+            type: string
+            enum:
+              - application/json
+      payload:
+        type: object
+        properties:
+          metadata:
+            $ref: '#/components/schemas/MetaData'
+          notification:
+            $ref: '#/components/schemas/Notification'
+  schemas:
+    MetaData:
+      type: object
+      properties:
+        topic:
+          type: string
+          description: Topic subscribed to.
+        schemaVersion:
+          type: string
+          description: The schema for this topic.
+        deprecated:
+          type: boolean
+          description: If this is a deprecated schema or topic.
+          default: 'false'
+    Notification:
+      type: object
+      properties:
+        notificationId:
+          type: string
+          description: The notification Id.
+        eventDate:
+          type: string
+          description: The event date associated with this notification in UTC.
+        publishDate:
+          type: string
+          description: The message publish date in UTC.
+        publishAttemptCount:
+          type: integer
+          description: The number of attempts made to publish this message.
+        data:
+          $ref: '#/components/schemas/AuthorizationRevocationData'
+    AuthorizationRevocationData:
+      type: object
+      description: The Authorization Revocation payload.
+      properties:
+        username:
+          type: string
+          description: The username for the user.
+        userId:
+          type: string
+          description: The immutable public userId for the user
+        eiasToken:
+          type: string
+          description: The legacy eiasToken specific to the user
+        revokeReason:
+          type: string
+          enum:
+            - REVOKED_BY_APP
+            - REVOKED_BY_USER
+            - REVOKED_BY_ADMIN
+            - PASSWORD_CHANGE
+          description: The reason for authorization revocation
+        revocationDate:
+          type: string
+          description: Date and time when the authorization was revoked
+  securitySchemes:
+    petstore_auth:
+      type: oauth2
+      description: The oauth security descriptions
+      flows:
+        clientCredentials:
+          tokenUrl: 'https://example.com/api/oauth/dialog'
+          availableScopes:
+            'subscribe:auth_revocations': Scope required for authorization revocation topic
diff --git a/__tests__/lint/async3/redocly.yaml b/__tests__/lint/async3/redocly.yaml
new file mode 100644
index 00000000..f84e1441
--- /dev/null
+++ b/__tests__/lint/async3/redocly.yaml
@@ -0,0 +1,36 @@
+apis:
+  anyof:
+    root: ./anyof.yml
+  application-headers:
+    root: ./application-headers.yml
+  correlation-id:
+    root: ./correlation-id.yml
+  gitter-streaming:
+    root: ./gitter-streaming.yml
+  mercure:
+    root: ./mercure.yml
+  not:
+    root: ./not.yml
+  oneof:
+    root: ./oneof.yml
+  operation-security:
+    root: ./operation-security.yml
+  rpc-client:
+    root: ./rpc-client.yml
+  rpc-server:
+    root: ./rpc-server.yml
+  simple:
+    root: ./simple.yml
+  slack-rtm:
+    root: ./slack-rtm.yml
+  streetlights-kafka:
+    root: ./streetlights-kafka.yml
+  streetlights-mqtt:
+    root: ./streetlights-mqtt.yml
+  streetlights-operation-security:
+    root: ./streetlights-operation-security.yml
+  websocket-gemini:
+    root: ./websocket-gemini.yml
+
+rules:
+  spec: error
diff --git a/__tests__/lint/async3/rpc-client.yml b/__tests__/lint/async3/rpc-client.yml
new file mode 100644
index 00000000..58602be3
--- /dev/null
+++ b/__tests__/lint/async3/rpc-client.yml
@@ -0,0 +1,72 @@
+asyncapi: 3.0.0
+id: 'urn:example:rpcclient'
+info:
+  title: RPC Client Example
+  version: 1.0.0
+  description: This example demonstrates how to define an RPC client.
+defaultContentType: application/json
+servers:
+  production:
+    host: rabbitmq.example.org
+    protocol: amqp
+channels:
+  queue:
+    address: '{queue}'
+    messages:
+      receiveSumResult:
+        correlationId:
+          location: $message.header#/correlation_id
+        payload:
+          type: object
+          properties:
+            result:
+              type: number
+              examples:
+                - 7
+    parameters:
+      queue: {}
+    bindings:
+      amqp:
+        is: queue
+        queue:
+          exclusive: true
+  rpc_queue:
+    address: rpc_queue
+    messages:
+      requestSum:
+        correlationId:
+          location: $message.header#/correlation_id
+        payload:
+          type: object
+          properties:
+            numbers:
+              type: array
+              items:
+                type: number
+              examples:
+                - - 4
+                  - 3
+    bindings:
+      amqp:
+        is: queue
+        queue:
+          durable: false
+operations:
+  receiveSumResult:
+    action: receive
+    channel:
+      $ref: '#/channels/queue'
+    bindings:
+      amqp:
+        ack: false
+    messages:
+      - $ref: '#/channels/queue/messages/receiveSumResult'
+  requestSum:
+    action: send
+    channel:
+      $ref: '#/channels/rpc_queue'
+    bindings:
+      amqp:
+        ack: true
+    messages:
+      - $ref: '#/channels/rpc_queue/messages/requestSum'
diff --git a/__tests__/lint/async3/rpc-server.yml b/__tests__/lint/async3/rpc-server.yml
new file mode 100644
index 00000000..3d93776c
--- /dev/null
+++ b/__tests__/lint/async3/rpc-server.yml
@@ -0,0 +1,69 @@
+asyncapi: 3.0.0
+id: 'urn:example:rpcserver'
+info:
+  title: RPC Server Example
+  version: 1.0.0
+  description: This example demonstrates how to define an RPC server.
+defaultContentType: application/json
+servers:
+  production:
+    host: rabbitmq.example.org
+    protocol: amqp
+channels:
+  queue:
+    address: '{queue}'
+    messages:
+      sendSumResult:
+        correlationId:
+          location: $message.header#/correlation_id
+        payload:
+          type: object
+          properties:
+            result:
+              type: number
+              examples:
+                - 7
+    parameters:
+      queue: {}
+    bindings:
+      amqp:
+        is: queue
+        queue:
+          exclusive: true
+  rpc_queue:
+    address: rpc_queue
+    messages:
+      sum:
+        correlationId:
+          location: $message.header#/correlation_id
+        payload:
+          type: object
+          properties:
+            numbers:
+              type: array
+              items:
+                type: number
+              examples:
+                - - 4
+                  - 3
+    bindings:
+      amqp:
+        is: queue
+        queue:
+          durable: false
+operations:
+  sendSumResult:
+    action: send
+    channel:
+      $ref: '#/channels/queue'
+    bindings:
+      amqp:
+        ack: true
+    messages:
+      - $ref: '#/channels/queue/messages/sendSumResult'
+  sum:
+    action: receive
+    channel:
+      $ref: '#/channels/rpc_queue'
+    messages:
+      - $ref: '#/channels/rpc_queue/messages/sum'
diff --git a/__tests__/lint/async3/simple.yml b/__tests__/lint/async3/simple.yml
new file mode 100644
index 00000000..59ab089e
--- /dev/null
+++ b/__tests__/lint/async3/simple.yml
@@ -0,0 +1,31 @@
+asyncapi: 3.0.0
+info:
+  title: Account Service
+  version: 1.0.0
+  description: This service is in charge of processing user signups
+channels:
+  userSignedup:
+    address: user/signedup
+    messages:
+      UserSignedUp:
+        $ref: '#/components/messages/UserSignedUp'
+operations:
+  sendUserSignedup:
+    action: send
+    channel:
+      $ref: '#/channels/userSignedup'
+    messages:
+      - $ref: '#/channels/userSignedup/messages/UserSignedUp'
+components:
+  messages:
+    UserSignedUp:
+      payload:
+        type: object
+        properties:
+          displayName:
+            type: string
+            description: Name of the user
+          email:
+            type: string
+            format: email
+            description: Email of the user
diff --git a/__tests__/lint/async3/slack-rtm.yml b/__tests__/lint/async3/slack-rtm.yml
new file mode 100644
index 00000000..9f50acd6
--- /dev/null
+++ b/__tests__/lint/async3/slack-rtm.yml
@@ -0,0 +1,982 @@
+asyncapi: 3.0.0
+id: 'wss://wss-primary.slack.com/websocket'
+info:
+  title: Slack Real Time Messaging API
+  version: 1.0.0
+servers:
+  production:
+    host: slack.com
+    pathname: /api/rtm.connect
+    protocol: https
+    protocolVersion: '1.1'
+    security:
+      - $ref: '#/components/securitySchemes/token'
+channels:
+  root:
+    address: /
+    messages:
+      outgoingMessage:
+        $ref: '#/components/messages/outgoingMessage'
+      hello:
+        $ref: '#/components/messages/hello'
+      connectionError:
+        $ref: '#/components/messages/connectionError'
+      accountsChanged:
+        $ref: '#/components/messages/accountsChanged'
+      botAdded:
+        $ref: '#/components/messages/botAdded'
+      botChanged:
+        $ref: '#/components/messages/botChanged'
+      channelArchive:
+        $ref: '#/components/messages/channelArchive'
+      channelCreated:
+        $ref: '#/components/messages/channelCreated'
+      channelDeleted:
+        $ref: '#/components/messages/channelDeleted'
+      channelHistoryChanged:
+        $ref: '#/components/messages/channelHistoryChanged'
+      channelJoined:
+        $ref: '#/components/messages/channelJoined'
+      channelLeft:
+        $ref: '#/components/messages/channelLeft'
+      channelMarked:
+        $ref: '#/components/messages/channelMarked'
+      channelRename:
+        $ref: '#/components/messages/channelRename'
+      channelUnarchive:
+        $ref: '#/components/messages/channelUnarchive'
+      commandsChanged:
+        $ref: '#/components/messages/commandsChanged'
+      dndUpdated:
+        $ref: '#/components/messages/dndUpdated'
+      dndUpdatedUser:
+        $ref: '#/components/messages/dndUpdatedUser'
+      emailDomainChanged:
+        $ref: '#/components/messages/emailDomainChanged'
+      emojiRemoved:
+        $ref: '#/components/messages/emojiRemoved'
+      emojiAdded:
+        $ref: '#/components/messages/emojiAdded'
+      fileChange:
+        $ref: '#/components/messages/fileChange'
+      fileCommentAdded:
+        $ref: '#/components/messages/fileCommentAdded'
+      fileCommentDeleted:
+        $ref: '#/components/messages/fileCommentDeleted'
+      fileCommentEdited:
+        $ref: '#/components/messages/fileCommentEdited'
+      fileCreated:
+        $ref: '#/components/messages/fileCreated'
+      fileDeleted:
+        $ref: '#/components/messages/fileDeleted'
+      filePublic:
+        $ref: '#/components/messages/filePublic'
+      fileShared:
+        $ref: '#/components/messages/fileShared'
+      fileUnshared:
+        $ref: '#/components/messages/fileUnshared'
+      goodbye:
+        $ref: '#/components/messages/goodbye'
+      groupArchive:
+        $ref: '#/components/messages/groupArchive'
+      groupClose:
+        $ref: '#/components/messages/groupClose'
+      groupHistoryChanged:
+        $ref: '#/components/messages/groupHistoryChanged'
+      groupJoined:
+        $ref: '#/components/messages/groupJoined'
+      groupLeft:
+        $ref: '#/components/messages/groupLeft'
+      groupMarked:
+        $ref: '#/components/messages/groupMarked'
+      groupOpen:
+        $ref: '#/components/messages/groupOpen'
+      groupRename:
+        $ref: '#/components/messages/groupRename'
+      groupUnarchive:
+        $ref: '#/components/messages/groupUnarchive'
+      imClose:
+        $ref: '#/components/messages/imClose'
+      imCreated:
+        $ref: '#/components/messages/imCreated'
+      imMarked:
+        $ref: '#/components/messages/imMarked'
+      imOpen:
+        $ref: '#/components/messages/imOpen'
+      manualPresenceChange:
+        $ref: '#/components/messages/manualPresenceChange'
+      memberJoinedChannel:
+        $ref: '#/components/messages/memberJoinedChannel'
+      message:
+        $ref: '#/components/messages/message'
+operations:
+  receiveOutgoingMessage:
+    action: receive
+    channel:
+      $ref: '#/channels/root'
+    messages:
+      - $ref: '#/channels/root/messages/outgoingMessage'
+  sendMessages:
+    action: send
+    channel:
+      $ref: '#/channels/root'
+    messages:
+      - $ref: '#/channels/root/messages/hello'
+      - $ref: '#/channels/root/messages/connectionError'
+      - $ref: '#/channels/root/messages/accountsChanged'
+      - $ref: '#/channels/root/messages/botAdded'
+      - $ref: '#/channels/root/messages/botChanged'
+      - $ref: '#/channels/root/messages/channelArchive'
+      - $ref: '#/channels/root/messages/channelCreated'
+      - $ref: '#/channels/root/messages/channelDeleted'
+      - $ref: '#/channels/root/messages/channelHistoryChanged'
+      - $ref: '#/channels/root/messages/channelJoined'
+      - $ref: '#/channels/root/messages/channelLeft'
+      - $ref: '#/channels/root/messages/channelMarked'
+      - $ref: '#/channels/root/messages/channelRename'
+      - $ref: '#/channels/root/messages/channelUnarchive'
+      - $ref: '#/channels/root/messages/commandsChanged'
+      - $ref: '#/channels/root/messages/dndUpdated'
+      - $ref: '#/channels/root/messages/dndUpdatedUser'
+      - $ref: '#/channels/root/messages/emailDomainChanged'
+      - $ref: '#/channels/root/messages/emojiRemoved'
+      - $ref: '#/channels/root/messages/emojiAdded'
+      - $ref: '#/channels/root/messages/fileChange'
+      - $ref: '#/channels/root/messages/fileCommentAdded'
+      - $ref: '#/channels/root/messages/fileCommentDeleted'
+      - $ref: '#/channels/root/messages/fileCommentEdited'
+      - $ref: '#/channels/root/messages/fileCreated'
+      - $ref: '#/channels/root/messages/fileDeleted'
+      - $ref: '#/channels/root/messages/filePublic'
+      - $ref: '#/channels/root/messages/fileShared'
+      - $ref: '#/channels/root/messages/fileUnshared'
+      - $ref: '#/channels/root/messages/goodbye'
+      - $ref: '#/channels/root/messages/groupArchive'
+      - $ref: '#/channels/root/messages/groupClose'
+      - $ref: '#/channels/root/messages/groupHistoryChanged'
+      - $ref: '#/channels/root/messages/groupJoined'
+      - $ref: '#/channels/root/messages/groupLeft'
+      - $ref: '#/channels/root/messages/groupMarked'
+      - $ref: '#/channels/root/messages/groupOpen'
+      - $ref: '#/channels/root/messages/groupRename'
+      - $ref: '#/channels/root/messages/groupUnarchive'
+      - $ref: '#/channels/root/messages/imClose'
+      - $ref: '#/channels/root/messages/imCreated'
+      - $ref: '#/channels/root/messages/imMarked'
+      - $ref: '#/channels/root/messages/imOpen'
+      - $ref: '#/channels/root/messages/manualPresenceChange'
+      - $ref: '#/channels/root/messages/memberJoinedChannel'
+      - $ref: '#/channels/root/messages/message'
+components:
+  securitySchemes:
+    token:
+      type: httpApiKey
+      name: token
+      in: query
+  schemas:
+    attachment:
+      type: object
+      properties:
+        fallback:
+          type: string
+        color:
+          type: string
+        pretext:
+          type: string
+        author_name:
+          type: string
+        author_link:
+          type: string
+          format: uri
+        author_icon:
+          type: string
+          format: uri
+        title:
+          type: string
+        title_link:
+          type: string
+          format: uri
+        text:
+          type: string
+        fields:
+          type: array
+          items:
+            type: object
+            properties:
+              title:
+                type: string
+              value:
+                type: string
+              short:
+                type: boolean
+        image_url:
+          type: string
+          format: uri
+        thumb_url:
+          type: string
+          format: uri
+        footer:
+          type: string
+        footer_icon:
+          type: string
+          format: uri
+        ts:
+          type: number
+  messages:
+    hello:
+      summary: First event received upon connection.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - hello
+    connectionError:
+      summary: Event received when a connection error happens.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - error
+          error:
+            type: object
+            properties:
+              code:
+                type: number
+              msg:
+                type: string
+    accountsChanged:
+      summary: The list of accounts a user is signed into has changed.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - accounts_changed
+    botAdded:
+      summary: A bot user was added.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - bot_added
+          bot:
+            type: object
+            properties:
+              id:
+                type: string
+              app_id:
+                type: string
+              name:
+                type: string
+              icons:
+                type: object
+                additionalProperties:
+                  type: string
+    botChanged:
+      summary: A bot user was changed.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - bot_added
+          bot:
+            type: object
+            properties:
+              id:
+                type: string
+              app_id:
+                type: string
+              name:
+                type: string
+              icons:
+                type: object
+                additionalProperties:
+                  type: string
+    channelArchive:
+      summary: A channel was archived.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - channel_archive
+          channel:
+            type: string
+          user:
+            type: string
+    channelCreated:
+      summary: A channel was created.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - channel_created
+          channel:
+            type: object
+            properties:
+              id:
+                type: string
+              name:
+                type: string
+              created:
+                type: number
+              creator:
+                type: string
+    channelDeleted:
+      summary: A channel was deleted.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - channel_deleted
+          channel:
+            type: string
+    channelHistoryChanged:
+      summary: Bulk updates were made to a channel's history.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - channel_history_changed
+          latest:
+            type: string
+          ts:
+            type: string
+          event_ts:
+            type: string
+    channelJoined:
+      summary: You joined a channel.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - channel_joined
+          channel:
+            type: object
+            properties:
+              id:
+                type: string
+              name:
+                type: string
+              created:
+                type: number
+              creator:
+                type: string
+    channelLeft:
+      summary: You left a channel.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - channel_left
+          channel:
+            type: string
+    channelMarked:
+      summary: Your channel read marker was updated.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - channel_marked
+          channel:
+            type: string
+          ts:
+            type: string
+    channelRename:
+      summary: A channel was renamed.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - channel_rename
+          channel:
+            type: object
+            properties:
+              id:
+                type: string
+              name:
+                type: string
+              created:
+                type: number
+    channelUnarchive:
+      summary: A channel was unarchived.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - channel_unarchive
+          channel:
+            type: string
+          user:
+            type: string
+    commandsChanged:
+      summary: A slash command has been added or changed.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - commands_changed
+          event_ts:
+            type: string
+    dndUpdated:
+      summary: Do not Disturb settings changed for the current user.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - dnd_updated
+          user:
+            type: string
+          dnd_status:
+            type: object
+            properties:
+              dnd_enabled:
+                type: boolean
+              next_dnd_start_ts:
+                type: number
+              next_dnd_end_ts:
+                type: number
+              snooze_enabled:
+                type: boolean
+              snooze_endtime:
+                type: number
+    dndUpdatedUser:
+      summary: Do not Disturb settings changed for a member.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - dnd_updated_user
+          user:
+            type: string
+          dnd_status:
+            type: object
+            properties:
+              dnd_enabled:
+                type: boolean
+              next_dnd_start_ts:
+                type: number
+              next_dnd_end_ts:
+                type: number
+    emailDomainChanged:
+      summary: The workspace email domain has changed.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - email_domain_changed
+          email_domain:
+            type: string
+          event_ts:
+            type: string
+    emojiRemoved:
+      summary: A custom emoji has been removed.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - emoji_changed
+          subtype:
+            type: string
+            enum:
+              - remove
+          names:
+            type: array
+            items:
+              type: string
+          event_ts:
+            type: string
+    emojiAdded:
+      summary: A custom emoji has been added.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - emoji_changed
+          subtype:
+            type: string
+            enum:
+              - add
+          name:
+            type: string
+          value:
+            type: string
+            format: uri
+          event_ts:
+            type: string
+    fileChange:
+      summary: A file was changed.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - file_change
+          file_id:
+            type: string
+          file:
+            type: object
+            properties:
+              id:
+                type: string
+    fileCommentAdded:
+      summary: A file comment was added.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - file_comment_added
+          comment: {}
+          file_id:
+            type: string
+          file:
+            type: object
+            properties:
+              id:
+                type: string
+    fileCommentDeleted:
+      summary: A file comment was deleted.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - file_comment_deleted
+          comment:
+            type: string
+          file_id:
+            type: string
+          file:
+            type: object
+            properties:
+              id:
+                type: string
+    fileCommentEdited:
+      summary: A file comment was edited.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - file_comment_edited
+          comment: {}
+          file_id:
+            type: string
+          file:
+            type: object
+            properties:
+              id:
+                type: string
+    fileCreated:
+      summary: A file was created.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - file_created
+          file_id:
+            type: string
+          file:
+            type: object
+            properties:
+              id:
+                type: string
+    fileDeleted:
+      summary: A file was deleted.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - file_deleted
+          file_id:
+            type: string
+          event_ts:
+            type: string
+    filePublic:
+      summary: A file was made public.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - file_public
+          file_id:
+            type: string
+          file:
+            type: object
+            properties:
+              id:
+                type: string
+    fileShared:
+      summary: A file was shared.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - file_shared
+          file_id:
+            type: string
+          file:
+            type: object
+            properties:
+              id:
+                type: string
+    fileUnshared:
+      summary: A file was unshared.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - file_unshared
+          file_id:
+            type: string
+          file:
+            type: object
+            properties:
+              id:
+                type: string
+    goodbye:
+      summary: The server intends to close the connection soon.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - goodbye
+    groupArchive:
+      summary: A private channel was archived.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - group_archive
+          channel:
+            type: string
+    groupClose:
+      summary: You closed a private channel.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - group_close
+          user:
+            type: string
+          channel:
+            type: string
+    groupHistoryChanged:
+      summary: Bulk updates were made to a private channel's history.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - group_history_changed
+          latest:
+            type: string
+          ts:
+            type: string
+          event_ts:
+            type: string
+    groupJoined:
+      summary: You joined a private channel.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - group_joined
+          channel:
+            type: object
+            properties:
+              id:
+                type: string
+              name:
+                type: string
+              created:
+                type: number
+              creator:
+                type: string
+    groupLeft:
+      summary: You left a private channel.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - group_left
+          channel:
+            type: string
+    groupMarked:
+      summary: A private channel read marker was updated.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - group_marked
+          channel:
+            type: string
+          ts:
+            type: string
+    groupOpen:
+      summary: You opened a private channel.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - group_open
+          user:
+            type: string
+          channel:
+            type: string
+    groupRename:
+      summary: A private channel was renamed.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - group_rename
+          channel:
+            type: object
+            properties:
+              id:
+                type: string
+              name:
+                type: string
+              created:
+                type: number
+    groupUnarchive:
+      summary: A private channel was unarchived.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - group_unarchive
+          channel:
+            type: string
+          user:
+            type: string
+    imClose:
+      summary: You closed a DM.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - im_close
+          channel:
+            type: string
+          user:
+            type: string
+    imCreated:
+      summary: A DM was created.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - im_created
+          channel:
+            type: object
+            properties:
+              id:
+                type: string
+              name:
+                type: string
+              created:
+                type: number
+              creator:
+                type: string
+          user:
+            type: string
+    imMarked:
+      summary: A direct message read marker was updated.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - im_marked
+          channel:
+            type: string
+          ts:
+            type: string
+    imOpen:
+      summary: You opened a DM.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - im_open
+          channel:
+            type: string
+          user:
+            type: string
+    manualPresenceChange:
+      summary: You manually updated your presence.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - manual_presence_change
+          presence:
+            type: string
+    memberJoinedChannel:
+      summary: A user joined a public or private channel.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - member_joined_channel
+          user:
+            type: string
+          channel:
+            type: string
+          channel_type:
+            type: string
+            enum:
+              - C
+              - G
+          team:
+            type: string
+          inviter:
+            type: string
+    memberLeftChannel:
+      summary: A user left a public or private channel.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - member_left_channel
+          user:
+            type: string
+          channel:
+            type: string
+          channel_type:
+            type: string
+            enum:
+              - C
+              - G
+          team:
+            type: string
+    message:
+      summary: A message was sent to a channel.
+      payload:
+        type: object
+        properties:
+          type:
+            type: string
+            enum:
+              - message
+          user:
+            type: string
+          channel:
+            type: string
+          text:
+            type: string
+          ts:
+            type: string
+          attachments:
+            type: array
+            items:
+              $ref: '#/components/schemas/attachment'
+          edited:
+            type: object
+            properties:
+              user:
+                type: string
+              ts:
+                type: string
+    outgoingMessage:
+      summary: A message was sent to a channel.
+      payload:
+        type: object
+        properties:
+          id:
+            type: number
+          type:
+            type: string
+            enum:
+              - message
+          channel:
+            type: string
+          text:
+            type: string
diff --git a/__tests__/lint/async3/snapshot.js b/__tests__/lint/async3/snapshot.js
new file mode 100644
index 00000000..a68b1f07
--- /dev/null
+++ b/__tests__/lint/async3/snapshot.js
@@ -0,0 +1,56 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`E2E lint async3 1`] = `
+
+validating /anyof.yml...
+/anyof.yml: validated in <test>ms
+
+validating /application-headers.yml...
+/application-headers.yml: validated in <test>ms
+
+validating /correlation-id.yml...
+/correlation-id.yml: validated in <test>ms
+
+validating /gitter-streaming.yml...
+/gitter-streaming.yml: validated in <test>ms
+
+validating /mercure.yml...
+/mercure.yml: validated in <test>ms
+
+validating /not.yml...
+/not.yml: validated in <test>ms
+
+validating /oneof.yml...
+/oneof.yml: validated in <test>ms
+
+validating /operation-security.yml...
+/operation-security.yml: validated in <test>ms
+
+validating /rpc-client.yml...
+/rpc-client.yml: validated in <test>ms
+
+validating /rpc-server.yml...
+/rpc-server.yml: validated in <test>ms
+
+validating /simple.yml...
+/simple.yml: validated in <test>ms
+
+validating /slack-rtm.yml...
+/slack-rtm.yml: validated in <test>ms
+
+validating /streetlights-kafka.yml...
+/streetlights-kafka.yml: validated in <test>ms
+
+validating /streetlights-mqtt.yml...
+/streetlights-mqtt.yml: validated in <test>ms
+
+validating /streetlights-operation-security.yml...
+/streetlights-operation-security.yml: validated in <test>ms
+
+validating /websocket-gemini.yml...
+/websocket-gemini.yml: validated in <test>ms
+
+Woohoo! Your API descriptions are valid. 🎉
+
+
+`;
diff --git a/__tests__/lint/async3/streetlights-kafka.yml b/__tests__/lint/async3/streetlights-kafka.yml
new file mode 100644
index 00000000..45eecd65
--- /dev/null
+++ b/__tests__/lint/async3/streetlights-kafka.yml
@@ -0,0 +1,199 @@
+asyncapi: 3.0.0
+info:
+  title: Streetlights Kafka API
+  version: 1.0.0
+  description: "The Smartylighting Streetlights API allows you to remotely manage the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time information about environmental lighting conditions \U0001F4C8\n"
+  license:
+    name: Apache 2.0
+    url: 'https://www.apache.org/licenses/LICENSE-2.0'
+defaultContentType: application/json
+servers:
+  scram-connections:
+    host: 'test.mykafkacluster.org:18092'
+    protocol: kafka-secure
+    description: Test broker secured with scramSha256
+    security:
+      - $ref: '#/components/securitySchemes/saslScram'
+    tags:
+      - name: 'env:test-scram'
+        description: >-
+          This environment is meant for running internal tests through
+          scramSha256
+      - name: 'kind:remote'
+        description: This server is a remote server. Not exposed by the application
+      - name: 'visibility:private'
+        description: This resource is private and only available to certain users
+  mtls-connections:
+    host: 'test.mykafkacluster.org:28092'
+    protocol: kafka-secure
+    description: Test broker secured with X509
+    security:
+      - $ref: '#/components/securitySchemes/certs'
+    tags:
+      - name: 'env:test-mtls'
+        description: This environment is meant for running internal tests through mtls
+      - name: 'kind:remote'
+        description: This server is a remote server. Not exposed by the application
+      - name: 'visibility:private'
+        description: This resource is private and only available to certain users
+channels:
+  lightingMeasured:
+    address: 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured'
+    messages:
+      lightMeasured:
+        $ref: '#/components/messages/lightMeasured'
+    description: The topic on which measured values may be produced and consumed.
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+  lightTurnOn:
+    address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.on'
+    messages:
+      turnOn:
+        $ref: '#/components/messages/turnOnOff'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+  lightTurnOff:
+    address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.off'
+    messages:
+      turnOff:
+        $ref: '#/components/messages/turnOnOff'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+  lightsDim:
+    address: 'smartylighting.streetlights.1.0.action.{streetlightId}.dim'
+    messages:
+      dimLight:
+        $ref: '#/components/messages/dimLight'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+operations:
+  receiveLightMeasurement:
+    action: receive
+    channel:
+      $ref: '#/channels/lightingMeasured'
+    summary: >-
+      Inform about environmental lighting conditions of a particular
+      streetlight.
+    traits:
+      - $ref: '#/components/operationTraits/kafka'
+    messages:
+      - $ref: '#/channels/lightingMeasured/messages/lightMeasured'
+  turnOn:
+    action: send
+    channel:
+      $ref: '#/channels/lightTurnOn'
+    traits:
+      - $ref: '#/components/operationTraits/kafka'
+    messages:
+      - $ref: '#/channels/lightTurnOn/messages/turnOn'
+  turnOff:
+    action: send
+    channel:
+      $ref: '#/channels/lightTurnOff'
+    traits:
+      - $ref: '#/components/operationTraits/kafka'
+    messages:
+      - $ref: '#/channels/lightTurnOff/messages/turnOff'
+  dimLight:
+    action: send
+    channel:
+      $ref: '#/channels/lightsDim'
+    traits:
+      - $ref: '#/components/operationTraits/kafka'
+    messages:
+      - $ref: '#/channels/lightsDim/messages/dimLight'
+components:
+  messages:
+    lightMeasured:
+      name: lightMeasured
+      title: Light measured
+      summary: >-
+        Inform about environmental lighting conditions of a particular
+        streetlight.
+      contentType: application/json
+      traits:
+        - $ref: '#/components/messageTraits/commonHeaders'
+      payload:
+        $ref: '#/components/schemas/lightMeasuredPayload'
+    turnOnOff:
+      name: turnOnOff
+      title: Turn on/off
+      summary: Command a particular streetlight to turn the lights on or off.
+      traits:
+        - $ref: '#/components/messageTraits/commonHeaders'
+      payload:
+        $ref: '#/components/schemas/turnOnOffPayload'
+    dimLight:
+      name: dimLight
+      title: Dim light
+      summary: Command a particular streetlight to dim the lights.
+      traits:
+        - $ref: '#/components/messageTraits/commonHeaders'
+      payload:
+        $ref: '#/components/schemas/dimLightPayload'
+  schemas:
+    lightMeasuredPayload:
+      type: object
+      properties:
+        lumens:
+          type: integer
+          minimum: 0
+          description: Light intensity measured in lumens.
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    turnOnOffPayload:
+      type: object
+      properties:
+        command:
+          type: string
+          enum:
+            - 'on'
+            - 'off'
+          description: Whether to turn on or off the light.
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    dimLightPayload:
+      type: object
+      properties:
+        percentage:
+          type: integer
+          description: Percentage to which the light should be dimmed to.
+          minimum: 0
+          maximum: 100
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    sentAt:
+      type: string
+      format: date-time
+      description: Date and time when the message was sent.
+  securitySchemes:
+    saslScram:
+      type: scramSha256
+      description: Provide your username and password for SASL/SCRAM authentication
+    certs:
+      type: X509
+      description: Download the certificate files from service provider
+  parameters:
+    streetlightId:
+      description: The ID of the streetlight.
+  messageTraits:
+    commonHeaders:
+      headers:
+        type: object
+        properties:
+          my-app-header:
+            type: integer
+            minimum: 0
+            maximum: 100
+  operationTraits:
+    kafka:
+      bindings:
+        kafka:
+          clientId:
+            type: string
+            enum:
+              - my-app-id
diff --git a/__tests__/lint/async3/streetlights-mqtt.yml b/__tests__/lint/async3/streetlights-mqtt.yml
new file mode 100644
index 00000000..ce655090
--- /dev/null
+++ b/__tests__/lint/async3/streetlights-mqtt.yml
@@ -0,0 +1,253 @@
+asyncapi: 3.0.0
+info:
+  title: Streetlights MQTT API
+  version: 1.0.0
+  description: "The Smartylighting Streetlights API allows you to remotely manage the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time information about environmental lighting conditions \U0001F4C8\n"
+  license:
+    name: Apache 2.0
+    url: 'https://www.apache.org/licenses/LICENSE-2.0'
+defaultContentType: application/json
+servers:
+  production:
+    host: 'test.mosquitto.org:{port}'
+    protocol: mqtt
+    description: Test broker
+    variables:
+      port:
+        description: Secure connection (TLS) is available through port 8883.
+        default: '1883'
+        enum:
+          - '1883'
+          - '8883'
+    security:
+      - $ref: '#/components/securitySchemes/apiKey'
+      - type: oauth2
+        description: Flows to support OAuth 2.0
+        flows:
+          implicit:
+            authorizationUrl: 'https://authserver.example/auth'
+            availableScopes:
+              'streetlights:on': Ability to switch lights on
+              'streetlights:off': Ability to switch lights off
+              'streetlights:dim': Ability to dim the lights
+          password:
+            tokenUrl: 'https://authserver.example/token'
+            availableScopes:
+              'streetlights:on': Ability to switch lights on
+              'streetlights:off': Ability to switch lights off
+              'streetlights:dim': Ability to dim the lights
+          clientCredentials:
+            tokenUrl: 'https://authserver.example/token'
+            availableScopes:
+              'streetlights:on': Ability to switch lights on
+              'streetlights:off': Ability to switch lights off
+              'streetlights:dim': Ability to dim the lights
+          authorizationCode:
+            authorizationUrl: 'https://authserver.example/auth'
+            tokenUrl: 'https://authserver.example/token'
+            refreshUrl: 'https://authserver.example/refresh'
+            availableScopes:
+              'streetlights:on': Ability to switch lights on
+              'streetlights:off': Ability to switch lights off
+              'streetlights:dim': Ability to dim the lights
+        scopes:
+          - 'streetlights:on'
+          - 'streetlights:off'
+          - 'streetlights:dim'
+      - $ref: '#/components/securitySchemes/openIdConnectWellKnown'
+    tags:
+      - name: 'env:production'
+        description: This environment is meant for production use case
+      - name: 'kind:remote'
+        description: This server is a remote server. Not exposed by the application
+      - name: 'visibility:public'
+        description: This resource is public and available to everyone
+channels:
+  lightingMeasured:
+    address: 'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured'
+    messages:
+      lightMeasured:
+        $ref: '#/components/messages/lightMeasured'
+    description: The topic on which measured values may be produced and consumed.
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+  lightTurnOn:
+    address: 'smartylighting/streetlights/1/0/action/{streetlightId}/turn/on'
+    messages:
+      turnOn:
+        $ref: '#/components/messages/turnOnOff'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+  lightTurnOff:
+    address: 'smartylighting/streetlights/1/0/action/{streetlightId}/turn/off'
+    messages:
+      turnOff:
+        $ref: '#/components/messages/turnOnOff'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+  lightsDim:
+    address: 'smartylighting/streetlights/1/0/action/{streetlightId}/dim'
+    messages:
+      dimLight:
+        $ref: '#/components/messages/dimLight'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+operations:
+  receiveLightMeasurement:
+    action: receive
+    channel:
+      $ref: '#/channels/lightingMeasured'
+    summary: >-
+      Inform about environmental lighting conditions of a particular
+      streetlight.
+    traits:
+      - $ref: '#/components/operationTraits/mqtt'
+    messages:
+      - $ref: '#/channels/lightingMeasured/messages/lightMeasured'
+  turnOn:
+    action: send
+    channel:
+      $ref: '#/channels/lightTurnOn'
+    traits:
+      - $ref: '#/components/operationTraits/mqtt'
+    messages:
+      - $ref: '#/channels/lightTurnOn/messages/turnOn'
+  turnOff:
+    action: send
+    channel:
+      $ref: '#/channels/lightTurnOff'
+    traits:
+      - $ref: '#/components/operationTraits/mqtt'
+    messages:
+      - $ref: '#/channels/lightTurnOff/messages/turnOff'
+  dimLight:
+    action: send
+    channel:
+      $ref: '#/channels/lightsDim'
+    traits:
+      - $ref: '#/components/operationTraits/mqtt'
+    messages:
+      - $ref: '#/channels/lightsDim/messages/dimLight'
+components:
+  messages:
+    lightMeasured:
+      name: lightMeasured
+      title: Light measured
+      summary: >-
+        Inform about environmental lighting conditions of a particular
+        streetlight.
+      contentType: application/json
+      traits:
+        - $ref: '#/components/messageTraits/commonHeaders'
+      payload:
+        $ref: '#/components/schemas/lightMeasuredPayload'
+    turnOnOff:
+      name: turnOnOff
+      title: Turn on/off
+      summary: Command a particular streetlight to turn the lights on or off.
+      traits:
+        - $ref: '#/components/messageTraits/commonHeaders'
+      payload:
+        $ref: '#/components/schemas/turnOnOffPayload'
+    dimLight:
+      name: dimLight
+      title: Dim light
+      summary: Command a particular streetlight to dim the lights.
+      traits:
+        - $ref: '#/components/messageTraits/commonHeaders'
+      payload:
+        $ref: '#/components/schemas/dimLightPayload'
+  schemas:
+    lightMeasuredPayload:
+      type: object
+      properties:
+        lumens:
+          type: integer
+          minimum: 0
+          description: Light intensity measured in lumens.
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    turnOnOffPayload:
+      type: object
+      properties:
+        command:
+          type: string
+          enum:
+            - 'on'
+            - 'off'
+          description: Whether to turn on or off the light.
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    dimLightPayload:
+      type: object
+      properties:
+        percentage:
+          type: integer
+          description: Percentage to which the light should be dimmed to.
+          minimum: 0
+          maximum: 100
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    sentAt:
+      type: string
+      format: date-time
+      description: Date and time when the message was sent.
+  securitySchemes:
+    apiKey:
+      type: apiKey
+      in: user
+      description: Provide your API key as the user and leave the password empty.
+    supportedOauthFlows:
+      type: oauth2
+      description: Flows to support OAuth 2.0
+      flows:
+        implicit:
+          authorizationUrl: 'https://authserver.example/auth'
+          availableScopes:
+            'streetlights:on': Ability to switch lights on
+            'streetlights:off': Ability to switch lights off
+            'streetlights:dim': Ability to dim the lights
+        password:
+          tokenUrl: 'https://authserver.example/token'
+          availableScopes:
+            'streetlights:on': Ability to switch lights on
+            'streetlights:off': Ability to switch lights off
+            'streetlights:dim': Ability to dim the lights
+        clientCredentials:
+          tokenUrl: 'https://authserver.example/token'
+          availableScopes:
+            'streetlights:on': Ability to switch lights on
+            'streetlights:off': Ability to switch lights off
+            'streetlights:dim': Ability to dim the lights
+        authorizationCode:
+          authorizationUrl: 'https://authserver.example/auth'
+          tokenUrl: 'https://authserver.example/token'
+          refreshUrl: 'https://authserver.example/refresh'
+          availableScopes:
+            'streetlights:on': Ability to switch lights on
+            'streetlights:off': Ability to switch lights off
+            'streetlights:dim': Ability to dim the lights
+    openIdConnectWellKnown:
+      type: openIdConnect
+      openIdConnectUrl: 'https://authserver.example/.well-known'
+  parameters:
+    streetlightId:
+      description: The ID of the streetlight.
+  messageTraits:
+    commonHeaders:
+      headers:
+        type: object
+        properties:
+          my-app-header:
+            type: integer
+            minimum: 0
+            maximum: 100
+  operationTraits:
+    mqtt:
+      bindings:
+        mqtt:
+          qos: 1
diff --git a/__tests__/lint/async3/streetlights-operation-security.yml b/__tests__/lint/async3/streetlights-operation-security.yml
new file mode 100644
index 00000000..31a115fe
--- /dev/null
+++ b/__tests__/lint/async3/streetlights-operation-security.yml
@@ -0,0 +1,240 @@
+asyncapi: 3.0.0
+info:
+  title: Streetlights Kafka API
+  version: 1.0.0
+  description: "The Smartylighting Streetlights API allows you to remotely manage the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time information about environmental lighting conditions \U0001F4C8\n"
+  license:
+    name: Apache 2.0
+    url: 'https://www.apache.org/licenses/LICENSE-2.0'
+defaultContentType: application/json
+servers:
+  test:
+    host: 'test.mykafkacluster.org:8092'
+    protocol: kafka-secure
+    description: Test broker
+    security:
+      - $ref: '#/components/securitySchemes/saslScram'
+  test_oauth:
+    host: 'test.mykafkacluster.org:8093'
+    protocol: kafka-secure
+    description: Test port for oauth
+    security:
+      - type: oauth2
+        description: The oauth security descriptions
+        flows:
+          clientCredentials:
+            tokenUrl: 'https://example.com/api/oauth/dialog'
+            availableScopes:
+              'streetlights:read': Scope required for subscribing to channel
+              'streetlights:write': Scope required for publishing to channel
+        scopes:
+          - 'streetlights:write'
+          - 'streetlights:read'
+channels:
+  lightingMeasured:
+    address: 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured'
+    messages:
+      lightMeasured:
+        $ref: '#/components/messages/lightMeasured'
+    description: The topic on which measured values may be produced and consumed.
+    servers:
+      - $ref: '#/servers/test'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+  lightTurnOn:
+    address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.on'
+    messages:
+      turnOn:
+        $ref: '#/components/messages/turnOnOff'
+    servers:
+      - $ref: '#/servers/test_oauth'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+  lightTurnOff:
+    address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.off'
+    messages:
+      turnOff:
+        $ref: '#/components/messages/turnOnOff'
+    servers:
+      - $ref: '#/servers/test_oauth'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+  lightsDim:
+    address: 'smartylighting.streetlights.1.0.action.{streetlightId}.dim'
+    messages:
+      dimLight:
+        $ref: '#/components/messages/dimLight'
+    servers:
+      - $ref: '#/servers/test_oauth'
+    parameters:
+      streetlightId:
+        $ref: '#/components/parameters/streetlightId'
+operations:
+  receiveLightMeasurement:
+    action: receive
+    channel:
+      $ref: '#/channels/lightingMeasured'
+    summary: >-
+      Inform about environmental lighting conditions of a particular
+      streetlight.
+    traits:
+      - $ref: '#/components/operationTraits/kafka'
+    messages:
+      - $ref: '#/channels/lightingMeasured/messages/lightMeasured'
+  turnOn:
+    action: send
+    channel:
+      $ref: '#/channels/lightTurnOn'
+    security:
+      - type: oauth2
+        description: The oauth security descriptions
+        flows:
+          clientCredentials:
+            tokenUrl: 'https://example.com/api/oauth/dialog'
+            availableScopes:
+              'streetlights:read': Scope required for subscribing to channel
+              'streetlights:write': Scope required for publishing to channel
+        scopes:
+          - 'streetlights:read'
+    traits:
+      - $ref: '#/components/operationTraits/kafka'
+    messages:
+      - $ref: '#/channels/lightTurnOn/messages/turnOn'
+  turnOff:
+    action: send
+    channel:
+      $ref: '#/channels/lightTurnOff'
+    security:
+      - type: oauth2
+        description: The oauth security descriptions
+        flows:
+          clientCredentials:
+            tokenUrl: 'https://example.com/api/oauth/dialog'
+            availableScopes:
+              'streetlights:read': Scope required for subscribing to channel
+              'streetlights:write': Scope required for publishing to channel
+        scopes:
+          - 'streetlights:read'
+    traits:
+      - $ref: '#/components/operationTraits/kafka'
+    messages:
+      - $ref: '#/channels/lightTurnOff/messages/turnOff'
+  dimLight:
+    action: send
+    channel:
+      $ref: '#/channels/lightsDim'
+    security:
+      - type: oauth2
+        description: The oauth security descriptions
+        flows:
+          clientCredentials:
+            tokenUrl: 'https://example.com/api/oauth/dialog'
+            availableScopes:
+              'streetlights:read': Scope required for subscribing to channel
+              'streetlights:write': Scope required for publishing to channel
+        scopes:
+          - 'streetlights:read'
+    traits:
+      - $ref: '#/components/operationTraits/kafka'
+    messages:
+      - $ref: '#/channels/lightsDim/messages/dimLight'
+components:
+  messages:
+    lightMeasured:
+      name: lightMeasured
+      title: Light measured
+      summary: >-
+        Inform about environmental lighting conditions of a particular
+        streetlight.
+      contentType: application/json
+      traits:
+        - $ref: '#/components/messageTraits/commonHeaders'
+      payload:
+        $ref: '#/components/schemas/lightMeasuredPayload'
+    turnOnOff:
+      name: turnOnOff
+      title: Turn on/off
+      summary: Command a particular streetlight to turn the lights on or off.
+      traits:
+        - $ref: '#/components/messageTraits/commonHeaders'
+      payload:
+        $ref: '#/components/schemas/turnOnOffPayload'
+    dimLight:
+      name: dimLight
+      title: Dim light
+      summary: Command a particular streetlight to dim the lights.
+      traits:
+        - $ref: '#/components/messageTraits/commonHeaders'
+      payload:
+        $ref: '#/components/schemas/dimLightPayload'
+  schemas:
+    lightMeasuredPayload:
+      type: object
+      properties:
+        lumens:
+          type: integer
+          minimum: 0
+          description: Light intensity measured in lumens.
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    turnOnOffPayload:
+      type: object
+      properties:
+        command:
+          type: string
+          enum:
+            - 'on'
+            - 'off'
+          description: Whether to turn on or off the light.
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    dimLightPayload:
+      type: object
+      properties:
+        percentage:
+          type: integer
+          description: Percentage to which the light should be dimmed to.
+          minimum: 0
+          maximum: 100
+        sentAt:
+          $ref: '#/components/schemas/sentAt'
+    sentAt:
+      type: string
+      format: date-time
+      description: Date and time when the message was sent.
+  securitySchemes:
+    saslScram:
+      type: scramSha256
+      description: Provide your username and password for SASL/SCRAM authentication
+    streetlights_auth:
+      type: oauth2
+      description: The oauth security descriptions
+      flows:
+        clientCredentials:
+          tokenUrl: 'https://example.com/api/oauth/dialog'
+          availableScopes:
+            'streetlights:read': Scope required for subscribing to channel
+            'streetlights:write': Scope required for publishing to channel
+  parameters:
+    streetlightId:
+      description: The ID of the streetlight.
+  messageTraits:
+    commonHeaders:
+      headers:
+        type: object
+        properties:
+          my-app-header:
+            type: integer
+            minimum: 0
+            maximum: 100
+  operationTraits:
+    kafka:
+      bindings:
+        kafka:
+          clientId:
+            type: string
+            enum:
+              - my-app-id
diff --git a/__tests__/lint/async3/websocket-gemini.yml b/__tests__/lint/async3/websocket-gemini.yml
new file mode 100644
index 00000000..fe127799
--- /dev/null
+++ b/__tests__/lint/async3/websocket-gemini.yml
@@ -0,0 +1,301 @@
+asyncapi: 3.0.0
+info:
+  title: Gemini Market Data Websocket API
+  version: 1.0.0
+  description: >
+    Market data is a public API that streams all the market data on a given
+    symbol.
+
+
+    You can quickly play with the API using
+    [websocat](https://github.com/vi/websocat#installation) like this:
+
+    ```bash
+
+    websocat wss://api.gemini.com/v1/marketdata/btcusd?heartbeat=true -S
+
+    ```
+  contact:
+    name: Gemini
+    url: 'https://www.gemini.com/'
+  externalDocs:
+    url: 'https://docs.sandbox.gemini.com/websocket-api/#market-data'
+servers:
+  public:
+    host: api.gemini.com
+    protocol: wss
+channels:
+  marketDataV1:
+    address: '/v1/marketdata/{symbol}'
+    messages:
+      marketData:
+        $ref: '#/components/messages/marketData'
+    parameters:
+      symbol:
+        enum:
+          - btcusd
+          - ethbtc
+          - ethusd
+          - zecusd
+          - zecbtc
+          - zeceth
+          - zecbch
+          - zecltc
+          - bchusd
+          - bchbtc
+          - bcheth
+          - ltcusd
+          - ltcbtc
+          - ltceth
+          - ltcbch
+          - batusd
+          - daiusd
+          - linkusd
+          - oxtusd
+          - batbtc
+          - linkbtc
+          - oxtbtc
+          - bateth
+          - linketh
+          - oxteth
+          - ampusd
+          - compusd
+          - paxgusd
+          - mkrusd
+          - zrxusd
+          - kncusd
+          - manausd
+          - storjusd
+          - snxusd
+          - crvusd
+          - balusd
+          - uniusd
+          - renusd
+          - umausd
+          - yfiusd
+          - btcdai
+          - ethdai
+          - aaveusd
+          - filusd
+          - btceur
+          - btcgbp
+          - etheur
+          - ethgbp
+          - btcsgd
+          - ethsgd
+          - sklusd
+          - grtusd
+          - bntusd
+          - 1inchusd
+          - enjusd
+          - lrcusd
+          - sandusd
+          - cubeusd
+          - lptusd
+          - bondusd
+          - maticusd
+          - injusd
+          - sushiusd
+        description: >
+          Symbols are formatted as CCY1CCY2 where prices are in CCY2 and
+          quantities are in CCY1. To read more click
+          [here](https://docs.sandbox.gemini.com/websocket-api/#symbols-and-minimums).
+    bindings:
+      ws:
+        bindingVersion: 0.1.0
+        query:
+          type: object
+          description: >
+            The semantics of entry type filtering is:
+
+
+            If any entry type is specified as true or false, all of them must be
+            explicitly flagged true to show up in the response
+
+            If no entry types filtering parameters are included in the url, then
+            all entry types will appear in the response
+
+
+            NOTE: top_of_book has no meaning and initial book events are empty
+            when only trades is specified
+          properties:
+            heartbeat:
+              type: boolean
+              default: false
+              description: >-
+                Optionally add this parameter and set to true to receive a
+                heartbeat every 5 seconds
+            top_of_book:
+              type: boolean
+              default: false
+              description: >-
+                If absent or false, receive full order book depth; if present
+                and true, receive top of book only. Only applies to bids and
+                offers.
+            bids:
+              type: boolean
+              default: true
+              description: Include bids in change events
+            offers:
+              type: boolean
+              default: true
+              description: Include asks in change events
+            trades:
+              type: boolean
+              default: true
+              description: Include trade events
+            auctions:
+              type: boolean
+              default: true
+              description: Include auction events
+operations:
+  sendMarketData:
+    action: send
+    channel:
+      $ref: '#/channels/marketDataV1'
+    summary: Receive market updates on a given symbol
+    messages:
+      - $ref: '#/channels/marketDataV1/messages/marketData'
+components:
+  messages:
+    marketData:
+      summary: Message with marked data information.
+      description: >
+        The initial response message will show the existing state of the order
+        book. Subsequent messages will show all executed trades, as well as all
+        other changes to the order book from orders placed or canceled.
+      payload:
+        $ref: '#/components/schemas/market'
+      examples:
+        - name: updateMessage
+          summary: >-
+            Example of an update message that contains a change in price
+            information.
+          payload:
+            type: update
+            eventId: 36902233362
+            timestamp: 1619769673
+            timestampms: 1619769673527
+            socket_sequence: 661
+            events:
+              - type: change
+                side: bid
+                price: 54350.40
+                remaining: 0.002
+                delta: 0.002
+                reason: place
+        - name: heartbeatMessage
+          summary: Example of additional heartbeat message when you enable them.
+          payload:
+            type: heartbeat
+            socket_sequence: 1656
+  schemas:
+    market:
+      type: object
+      oneOf:
+        - $ref: '#/components/schemas/heartbeat'
+        - $ref: '#/components/schemas/update'
+    heartbeat:
+      allOf:
+        - properties:
+            type:
+              type: string
+              const: heartbeat
+          required:
+            - type
+        - $ref: '#/components/schemas/default'
+    update:
+      allOf:
+        - properties:
+            type:
+              type: string
+              const: update
+            eventId:
+              type: integer
+              description: >-
+                A monotonically increasing sequence number indicating when this
+                change occurred. These numbers are persistent and consistent
+                between market data connections.
+            events:
+              $ref: '#/components/schemas/events'
+            timestamp:
+              type: number
+              description: >-
+                The timestamp in seconds for this group of events (included for
+                compatibility reasons). We recommend using the timestampms field
+                instead.
+            timestampms:
+              type: number
+              description: The timestamp in milliseconds for this group of events.
+          required:
+            - type
+            - eventId
+            - events
+            - timestamp
+            - timestampms
+        - $ref: '#/components/schemas/default'
+    default:
+      type: object
+      description: >-
+        This object is always part of the payload. In case of type=heartbeat,
+        these are the only fields.
+      required:
+        - type
+        - socket_sequence
+      properties:
+        socket_sequence:
+          type: integer
+          description: >-
+            zero-indexed monotonic increasing sequence number attached to each
+            message sent - if there is a gap in this sequence, you have missed a
+            message. If you choose to enable heartbeats, then heartbeat and
+            update messages will share a single increasing sequence. See
+            [Sequence
+            Numbers](https://docs.sandbox.gemini.com/websocket-api/#sequence-numbers)
+            for more information.
+    events:
+      type: array
+      description: >-
+        Either a change to the order book, or the indication that a trade has
+        occurred.
+      items:
+        type: object
+        additionalProperties: false
+        properties:
+          type:
+            type: string
+            enum:
+              - trade
+              - change
+              - 'auction, block_trade'
+          price:
+            type: number
+            multipleOf: 0.01
+            description: The price of this order book entry.
+          side:
+            type: string
+            enum:
+              - bid
+              - side
+          reason:
+            type: string
+            enum:
+              - place
+              - trade
+              - cancel
+              - initial
+            description: >-
+              Indicates why the change has occurred. initial is for the initial
+              response message, which will show the entire existing state of the
+              order book.
+          remaining:
+            type: number
+            description: >-
+              The quantity remaining at that price level after this change
+              occurred. May be zero if all orders at this price level have been
+              filled or canceled.
+          delta:
+            type: number
+            description: >-
+              The quantity changed. May be negative, if an order is filled or
+              canceled. For initial messages, delta will equal remaining.
diff --git a/packages/cli/src/__tests__/utils.test.ts b/packages/cli/src/__tests__/utils.test.ts
index 7a99bbd8..e29f2496 100644
--- a/packages/cli/src/__tests__/utils.test.ts
+++ b/packages/cli/src/__tests__/utils.test.ts
@@ -474,6 +474,7 @@ describe('checkIfRulesetExist', () => {
       oas3_0: {},
       oas3_1: {},
       async2: {},
+      async3: {},
       arazzo: {},
     };
     expect(() => checkIfRulesetExist(rules)).toThrowError(
diff --git a/packages/core/src/__tests__/bundle.test.ts b/packages/core/src/__tests__/bundle.test.ts
index 99a73fd0..aed9007f 100644
--- a/packages/core/src/__tests__/bundle.test.ts
+++ b/packages/core/src/__tests__/bundle.test.ts
@@ -274,3 +274,145 @@ describe('bundleFromString', () => {
     expect(rest.source.body).toEqual(stringDocument);
   });
 });
+
+describe('bundle async', () => {
+  it('should bundle async of version 2.x', async () => {
+    const testDocument = parseYamlToDocument(
+      outdent`
+        asyncapi: '2.6.0'
+        info:
+          title: Account Service
+          version: 1.0.0
+          description: This service is in charge of processing user signups
+        channels:
+          user/signedup:
+            subscribe:
+              message:
+                $ref: '#/components/messages/UserSignedUp'
+        components:
+          schemas:
+            UserSignedUp:
+              type: object
+              properties:
+                displayName:
+                  type: string
+                  description: Name of the user
+          messages:
+            UserSignedUp:
+              payload:
+                $ref: '#/components/schemas/UserSignedUp'
+      `,
+      ''
+    );
+
+    const config = await makeConfig({});
+
+    const {
+      bundle: { parsed },
+      problems,
+    } = await bundleDocument({
+      document: testDocument,
+      config: config,
+      externalRefResolver: new BaseResolver(),
+      dereference: true,
+    });
+
+    expect(problems).toHaveLength(0);
+    expect(parsed).toMatchInlineSnapshot(`
+      asyncapi: 2.6.0
+      info:
+        title: Account Service
+        version: 1.0.0
+        description: This service is in charge of processing user signups
+      channels:
+        user/signedup:
+          subscribe:
+            message:
+              payload: &ref_1
+                type: object
+                properties: &ref_0
+                  displayName:
+                    type: string
+                    description: Name of the user
+      components:
+        schemas:
+          UserSignedUp:
+            type: object
+            properties: *ref_0
+        messages:
+          UserSignedUp:
+            payload: *ref_1
+
+    `);
+  });
+
+  it('should bundle async of version 3.0', async () => {
+    const testDocument = parseYamlToDocument(
+      outdent`
+        asyncapi: 3.0.0
+        info:
+          title: Account Service
+          version: 1.0.0
+          description: This service is in charge of processing user signups
+        operations:
+          sendUserSignedup:
+            action: send
+            messages:
+              - $ref: '#/components/messages/UserSignedUp'
+        components:
+          schemas:
+            UserSignedUp:
+              type: object
+              properties:
+                displayName:
+                  type: string
+                  description: Name of the user
+          messages:
+            UserSignedUp:
+              payload:
+                $ref: '#/components/schemas/UserSignedUp'
+      `,
+      ''
+    );
+
+    const config = await makeConfig({});
+
+    const {
+      bundle: { parsed },
+      problems,
+    } = await bundleDocument({
+      document: testDocument,
+      config: config,
+      externalRefResolver: new BaseResolver(),
+      dereference: true,
+    });
+
+    expect(problems).toHaveLength(0);
+    expect(parsed).toMatchInlineSnapshot(`
+      asyncapi: 3.0.0
+      info:
+        title: Account Service
+        version: 1.0.0
+        description: This service is in charge of processing user signups
+      operations:
+        sendUserSignedup:
+          action: send
+          messages:
+            - payload: &ref_1
+                type: object
+                properties: &ref_0
+                  displayName:
+                    type: string
+                    description: Name of the user
+      components:
+        schemas:
+          UserSignedUp:
+            type: object
+            properties: *ref_0
+        messages:
+          UserSignedUp:
+            payload: *ref_1
+
+    `);
+  });
+});
diff --git a/packages/core/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap b/packages/core/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap
index e6bf5edc..535a9795 100644
--- a/packages/core/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap
+++ b/packages/core/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap
@@ -18,6 +18,17 @@ exports[`resolveConfig should ignore minimal from the root and read local file 1
     "tag-description": "warn",
     "tags-alphabetical": "off",
   },
+  "async3Decorators": {},
+  "async3Preprocessors": {},
+  "async3Rules": {
+    "channels-kebab-case": "off",
+    "info-contact": "off",
+    "no-channel-trailing-slash": "off",
+    "operation-operationId": "warn",
+    "spec": "error",
+    "tag-description": "warn",
+    "tags-alphabetical": "off",
+  },
   "decorators": {},
   "doNotResolveExamples": undefined,
   "oas2Decorators": {},
@@ -135,6 +146,17 @@ exports[`resolveStyleguideConfig should resolve extends with local file config w
     "tag-description": "warn",
     "tags-alphabetical": "off",
   },
+  "async3Decorators": {},
+  "async3Preprocessors": {},
+  "async3Rules": {
+    "channels-kebab-case": "off",
+    "info-contact": "off",
+    "no-channel-trailing-slash": "off",
+    "operation-operationId": "warn",
+    "spec": "error",
+    "tag-description": "warn",
+    "tags-alphabetical": "off",
+  },
   "decorators": {},
   "doNotResolveExamples": undefined,
   "oas2Decorators": {},
diff --git a/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap b/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap
index 233ae25d..c4826542 100644
--- a/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap
+++ b/packages/core/src/config/__tests__/__snapshots__/config.test.ts.snap
@@ -12,6 +12,11 @@ StyleguideConfig {
       "oas3_0": {},
       "oas3_1": {},
     },
+    "async3": {
+      "oas2": {},
+      "oas3_0": {},
+      "oas3_1": {},
+    },
     "oas2": {
       "oas2": {},
       "oas3_0": {},
@@ -51,6 +56,11 @@ StyleguideConfig {
       "oas3_0": {},
       "oas3_1": {},
     },
+    "async3": {
+      "oas2": {},
+      "oas3_0": {},
+      "oas3_1": {},
+    },
     "oas2": {
       "oas2": {},
       "oas3_0": {},
@@ -124,6 +134,20 @@ StyleguideConfig {
         "operation-summary": "error",
       },
     },
+    "async3": {
+      "oas2": {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+      "oas3_0": {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+      "oas3_1": {
+        "no-empty-servers": "error",
+        "operation-summary": "error",
+      },
+    },
     "oas2": {
       "oas2": {
         "no-empty-servers": "error",
diff --git a/packages/core/src/config/__tests__/config.test.ts b/packages/core/src/config/__tests__/config.test.ts
index a83f6b04..650787fd 100644
--- a/packages/core/src/config/__tests__/config.test.ts
+++ b/packages/core/src/config/__tests__/config.test.ts
@@ -119,6 +119,7 @@ describe('getMergedConfig', () => {
           "decorators": {
             "arazzo": {},
             "async2": {},
+            "async3": {},
             "oas2": {},
             "oas3_0": {},
             "oas3_1": {},
@@ -131,6 +132,7 @@ describe('getMergedConfig', () => {
           "preprocessors": {
             "arazzo": {},
             "async2": {},
+            "async3": {},
             "oas2": {},
             "oas3_0": {},
             "oas3_1": {},
@@ -148,6 +150,9 @@ describe('getMergedConfig', () => {
             "async2": {
               "operation-summary": "warn",
             },
+            "async3": {
+              "operation-summary": "warn",
+            },
             "oas2": {
               "operation-summary": "warn",
             },
@@ -227,6 +232,7 @@ describe('getMergedConfig', () => {
           "decorators": {
             "arazzo": {},
             "async2": {},
+            "async3": {},
             "oas2": {},
             "oas3_0": {},
             "oas3_1": {},
@@ -239,6 +245,7 @@ describe('getMergedConfig', () => {
           "preprocessors": {
             "arazzo": {},
             "async2": {},
+            "async3": {},
             "oas2": {},
             "oas3_0": {},
             "oas3_1": {},
@@ -259,6 +266,10 @@ describe('getMergedConfig', () => {
               "no-empty-servers": "error",
               "operation-summary": "error",
             },
+            "async3": {
+              "no-empty-servers": "error",
+              "operation-summary": "error",
+            },
             "oas2": {
               "no-empty-servers": "error",
               "operation-summary": "error",
diff --git a/packages/core/src/rules/async3/__tests__/channels-kebab-case.test.ts b/packages/core/src/rules/async3/__tests__/channels-kebab-case.test.ts
new file mode 100644
index 00000000..3123b817
--- /dev/null
+++ b/packages/core/src/rules/async3/__tests__/channels-kebab-case.test.ts
@@ -0,0 +1,141 @@
+import { outdent } from 'outdent';
+import { lintDocument } from '../../../lint';
+import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
+import { BaseResolver } from '../../../resolve';
+
+describe('Async2 channels-kebab-case', () => {
+  it('should report on no kebab-case channel path', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+        asyncapi: '3.0.0'
+        info:
+          title: Cool API
+          version: 1.0.0
+        channels:
+          channel1:
+            address: /NOT_A_KEBAB/
+            payload:
+              type: object
+        `,
+      'asyncapi.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({ 'channels-kebab-case': 'error' }),
+    });
+
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
+      [
+        {
+          "location": [
+            {
+              "pointer": "#/channels/channel1",
+              "reportOnKey": true,
+              "source": "asyncapi.yaml",
+            },
+          ],
+          "message": "\`/NOT_A_KEBAB/\` does not use kebab-case.",
+          "ruleId": "channels-kebab-case",
+          "severity": "error",
+          "suggest": [],
+        },
+      ]
+    `);
+  });
+
+  it('should report on snake_case in channel path', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+        asyncapi: '3.0.0'
+        info:
+          title: Cool API
+          version: 1.0.0
+        channels:
+          channel1:
+            address: snake_kebab
+            payload:
+              type: object
+        `,
+      'asyncapi.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({ 'channels-kebab-case': 'error' }),
+    });
+
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
+      [
+        {
+          "location": [
+            {
+              "pointer": "#/channels/channel1",
+              "reportOnKey": true,
+              "source": "asyncapi.yaml",
+            },
+          ],
+          "message": "\`snake_kebab\` does not use kebab-case.",
+          "ruleId": "channels-kebab-case",
+          "severity": "error",
+          "suggest": [],
+        },
+      ]
+    `);
+  });
+
+  it('should allow trailing slash in channel path with "channels-kebab-case" rule', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+        asyncapi: '3.0.0'
+        info:
+          title: Cool API
+          version: 1.0.0
+        channels:
+          channel1:
+            address: kebab/
+            payload:
+              type: object
+        `,
+      'asyncapi.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({
+        'paths-kebab-case': 'error',
+        'no-path-trailing-slash': 'off',
+      }),
+    });
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
+  });
+
+  it('words with hyphens are allowed with "channels-kebab-case" rule', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+        asyncapi: '3.0.0'
+        info:
+          title: Cool API
+          version: 1.0.0
+        channels:
+          channel1:
+            address: kebab-with-longer-channel-path:/
+            payload:
+              type: object
+        `,
+      'asyncapi.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({
+        'paths-kebab-case': 'error',
+      }),
+    });
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
+  });
+});
diff --git a/packages/core/src/rules/async3/__tests__/no-channel-trailing-slash.test.ts b/packages/core/src/rules/async3/__tests__/no-channel-trailing-slash.test.ts
new file mode 100644
index 00000000..cb84e9b7
--- /dev/null
+++ b/packages/core/src/rules/async3/__tests__/no-channel-trailing-slash.test.ts
@@ -0,0 +1,96 @@
+import { outdent } from 'outdent';
+import { lintDocument } from '../../../lint';
+import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
+import { BaseResolver } from '../../../resolve';
+
+describe('no-channel-trailing-slash', () => {
+  it.only('should report on trailing slash in a channel path', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+          asyncapi: '3.0.0'
+          info:
+            title: Excellent API
+            version: 1.0.0
+          channels:
+            channel1:
+              address: /trailing/
+              payload:
+                type: object
+        `,
+      'asyncapi.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({ 'no-channel-trailing-slash': 'error' }),
+    });
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
+      [
+        {
+          "location": [
+            {
+              "pointer": "#/channels/channel1",
+              "reportOnKey": true,
+              "source": "asyncapi.yaml",
+            },
+          ],
+          "message": "\`/trailing/\` should not have a trailing slash.",
+          "ruleId": "no-channel-trailing-slash",
+          "severity": "error",
+          "suggest": [],
+        },
+      ]
+    `);
+  });
+
+  it('should not report on if no trailing slash in path', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+          asyncapi: '3.0.0'
+          info:
+            title: Excellent API
+            version: 1.0.0
+          channels:
+            channel1:
+            address: /expected
+            payload:
+              type: object
+        `,
+      'asyncapi.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({ 'no-channel-trailing-slash': 'error' }),
+    });
+
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
+  });
+
+  it('should not report on trailing slash in path if the path is root', async () => {
+    const document = parseYamlToDocument(
+      outdent`
+          asyncapi: '3.0.0'
+          info:
+            title: Excellent API
+            version: 1.0.0
+          channels:
+            channel1:
+            address: /
+            payload:
+              type: object
+        `,
+      'foobar.yaml'
+    );
+
+    const results = await lintDocument({
+      externalRefResolver: new BaseResolver(),
+      document,
+      config: await makeConfig({ 'no-channel-trailing-slash': 'error' }),
+    });
+
+    expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
+  });
+});