diff --git a/.ci/validation/package-lock.json b/.ci/validation/package-lock.json
index ad60031d..3976d2eb 100644
--- a/.ci/validation/package-lock.json
+++ b/.ci/validation/package-lock.json
@@ -1563,12 +1563,13 @@
       }
     },
     "node_modules/braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "fill-range": "^7.0.1"
+        "fill-range": "^7.1.1"
       },
       "engines": {
         "node": ">=8"
@@ -2046,10 +2047,11 @@
       }
     },
     "node_modules/fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "to-regex-range": "^5.0.1"
       },
@@ -2315,6 +2317,7 @@
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
       "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": ">=0.12.0"
       }
@@ -3257,12 +3260,13 @@
       "peer": true
     },
     "node_modules/micromatch": {
-      "version": "4.0.5",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
-      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+      "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "braces": "^3.0.2",
+        "braces": "^3.0.3",
         "picomatch": "^2.3.1"
       },
       "engines": {
@@ -3895,6 +3899,7 @@
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
       "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "is-number": "^7.0.0"
       },
diff --git a/.ci/validation/src/ctk.test.ts b/.ci/validation/src/ctk.test.ts
new file mode 100644
index 00000000..364567de
--- /dev/null
+++ b/.ci/validation/src/ctk.test.ts
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023-Present The Serverless Workflow Specification Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { SWSchemaValidator } from "./index";
+import fs from "node:fs";
+import path from "node:path";
+
+SWSchemaValidator.prepareSchemas();
+
+const ctkDir = path.join(__dirname, "..", "..", "..", "ctk", "features");
+
+function extractYamlBlocks(content: string): string[] {
+    const yamlBlockRegex = /"""yaml\s([\s\S]*?)\s"""/gm;  // Match YAML blocks
+    let match;
+    const yamlBlocks: string[] = [];
+
+    while ((match = yamlBlockRegex.exec(content)) !== null) {
+        yamlBlocks.push(match[1]);
+    }
+
+    return yamlBlocks;
+}
+
+const workflows = fs.readdirSync(ctkDir)
+    .filter((file) => file.endsWith(".feature"))
+    .flatMap((file) => {
+        const filePath = path.join(ctkDir, file);
+        const fileContent = fs.readFileSync(filePath, SWSchemaValidator.defaultEncoding);
+
+        const yamlBlocks = extractYamlBlocks(fileContent);
+
+        return yamlBlocks
+            .map((yamlText) => SWSchemaValidator.yamlToJSON(yamlText))
+            .filter((workflow) => typeof workflow === "object")
+            .filter((workflow) => "document" in workflow)
+            .filter((workflow) => "dsl" in workflow.document)
+            .map((workflow) => ({ workflow, file }));
+    });
+
+describe(`Validate workflows from .feature files`, () => {
+    test.each(workflows)('$workflow.document.name (from $file)', ({ workflow, file }) => {
+        const results = SWSchemaValidator.validateSchema(workflow);
+
+        if (results?.errors) {
+            console.warn(
+                `Schema validation failed for workflow "${workflow.document.name}" in file "${file}" with:`,
+                JSON.stringify(results.errors, null, 2)
+            );
+        }
+
+        expect(results?.valid).toBeTruthy();
+    });
+});
diff --git a/ctk/features/branch.feature b/ctk/features/branch.feature
index cbd6c0e7..3fa03df4 100644
--- a/ctk/features/branch.feature
+++ b/ctk/features/branch.feature
@@ -11,6 +11,7 @@ Feature: Composite Task
       dsl: '1.0.0'
       namespace: default
       name: fork
+      version: '1.0.0'
     do:
       - branchWithCompete:
           fork:
diff --git a/ctk/features/call.feature b/ctk/features/call.feature
index f69ba876..f5425918 100644
--- a/ctk/features/call.feature
+++ b/ctk/features/call.feature
@@ -14,6 +14,7 @@ Feature: Call Task
       dsl: '1.0.0'
       namespace: default
       name: http-call-with-content-output
+      version: '1.0.0'
     do:
       - findPet:
           call: http
@@ -42,6 +43,7 @@ Feature: Call Task
       dsl: '1.0.0'
       namespace: default
       name: http-call-with-response-output
+      version: '1.0.0'
     do:
       - getPet:
           call: http
@@ -69,6 +71,7 @@ Feature: Call Task
       dsl: '1.0.0'
       namespace: default
       name: http-call-with-basic-auth
+      version: '1.0.0'
     do:
       - login:
           call: http
@@ -98,6 +101,7 @@ Feature: Call Task
       dsl: '1.0.0'
       namespace: default
       name: openapi-call-with-content-output
+      version: '1.0.0'
     do:
       - findPet:
           call: openapi
@@ -126,6 +130,7 @@ Feature: Call Task
       dsl: '1.0.0'
       namespace: default
       name: openapi-call-with-response-output
+      version: '1.0.0'
     do:
       - getPet:
           call: openapi
diff --git a/ctk/features/data-flow.feature b/ctk/features/data-flow.feature
index 289160e5..01faa9eb 100644
--- a/ctk/features/data-flow.feature
+++ b/ctk/features/data-flow.feature
@@ -11,6 +11,7 @@ Feature: Data Flow
       dsl: '1.0.0'
       namespace: default
       name: output-filtering
+      version: '1.0.0'
     do:
       - setPlayerId:
           input:
@@ -38,6 +39,7 @@ Feature: Data Flow
       dsl: '1.0.0'
       namespace: default
       name: output-filtering
+      version: '1.0.0'
     do:
       - getPet:
           call: http
@@ -66,6 +68,7 @@ Feature: Data Flow
       dsl: '1.0.0'
       namespace: default
       name: non-object-output
+      version: '1.0.0'
     do:
       - getPetById1:
           call: http
diff --git a/ctk/features/do.feature b/ctk/features/do.feature
index 8668b23e..3831e3a0 100644
--- a/ctk/features/do.feature
+++ b/ctk/features/do.feature
@@ -11,6 +11,7 @@ Feature: Composite Task
       dsl: '1.0.0'
       namespace: default
       name: do
+      version: '1.0.0'
     do:
       - compositeExample:
           do:
diff --git a/ctk/features/emit.feature b/ctk/features/emit.feature
index 0ec2046f..ef3a7815 100644
--- a/ctk/features/emit.feature
+++ b/ctk/features/emit.feature
@@ -11,6 +11,7 @@ Feature: Emit Task
       dsl: '1.0.0'
       namespace: default
       name: emit
+      version: '1.0.0'
     do:
       - emitEvent:
           emit:
diff --git a/ctk/features/flow.feature b/ctk/features/flow.feature
index d7fb90d4..b27d15a4 100644
--- a/ctk/features/flow.feature
+++ b/ctk/features/flow.feature
@@ -10,6 +10,7 @@ Feature: Flow Directive
       dsl: '1.0.0'
       namespace: default
       name: implicit-sequence
+      version: '1.0.0'
     do:
       - setRed:
           set:
@@ -37,6 +38,7 @@ Feature: Flow Directive
       dsl: '1.0.0'
       namespace: default
       name: explicit-sequence
+      version: '1.0.0'
     do:
       - setRed:
           set:
diff --git a/ctk/features/for.feature b/ctk/features/for.feature
index 6ebb0d07..92cc94df 100644
--- a/ctk/features/for.feature
+++ b/ctk/features/for.feature
@@ -13,6 +13,7 @@ Feature: For Task
       dsl: '1.0.0'
       namespace: default
       name: for
+      version: '1.0.0'
     do:
       - loopColors:
           for:
diff --git a/ctk/features/raise.feature b/ctk/features/raise.feature
index d7bbc82e..b1ecf179 100644
--- a/ctk/features/raise.feature
+++ b/ctk/features/raise.feature
@@ -10,6 +10,7 @@ Feature: Raise Task
       dsl: '1.0.0'
       namespace: default
       name: raise-custom-error
+      version: '1.0.0'
     do:
       - raiseError:
           raise:
diff --git a/ctk/features/set.feature b/ctk/features/set.feature
index 1b5c9d96..56d71fd7 100644
--- a/ctk/features/set.feature
+++ b/ctk/features/set.feature
@@ -11,6 +11,7 @@ Feature: Set Task
       dsl: '1.0.0'
       namespace: default
       name: set
+      version: '1.0.0'
     do:
       - setShape:
           set:
diff --git a/ctk/features/switch.feature b/ctk/features/switch.feature
index 86b797a1..567398e1 100644
--- a/ctk/features/switch.feature
+++ b/ctk/features/switch.feature
@@ -10,6 +10,7 @@ Feature: Switch Task
       dsl: '1.0.0'
       namespace: default
       name: switch-match
+      version: '1.0.0'
     do:
       - switchColor:
           switch:
@@ -54,6 +55,7 @@ Feature: Switch Task
       dsl: '1.0.0'
       namespace: default
       name: switch-default-implicit
+      version: '1.0.0'
     do:
       - switchColor:
           switch:
@@ -96,6 +98,7 @@ Feature: Switch Task
       dsl: '1.0.0'
       namespace: default
       name: switch-default-implicit
+      version: '1.0.0'
     do:
       - switchColor:
           switch:
diff --git a/ctk/features/try.feature b/ctk/features/try.feature
index c28ac5c5..6ac7800c 100644
--- a/ctk/features/try.feature
+++ b/ctk/features/try.feature
@@ -14,6 +14,7 @@ Feature: Try Task
       dsl: '1.0.0'
       namespace: default
       name: try-catch-404
+      version: '1.0.0'
     do:
       - tryGetPet:
           try:
@@ -57,6 +58,7 @@ Feature: Try Task
       dsl: '1.0.0'
       namespace: default
       name: try-catch-503
+      version: '1.0.0'
     do:
       - tryGetPet:
           try: