diff --git a/README.md b/README.md
index 86871819..b5abc7e3 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,8 @@ Pipeline
↓
FilterChain
↓
+Router
+ ↓
Plugin
↓
HttpResponseWriter
diff --git a/pom.xml b/pom.xml
index 34bfbeba..a86081ee 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,12 @@
${mockito.version}
test
+
+ com.tngtech.archunit
+ archunit
+ 1.4.1
+ test
+
org.yaml
snakeyaml
diff --git a/src/test/java/org/juv25d/ArchitectureTest.java b/src/test/java/org/juv25d/ArchitectureTest.java
new file mode 100644
index 00000000..6923bece
--- /dev/null
+++ b/src/test/java/org/juv25d/ArchitectureTest.java
@@ -0,0 +1,172 @@
+package org.juv25d;
+
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.core.importer.ClassFileImporter;
+import com.tngtech.archunit.core.importer.ImportOption;
+
+import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+
+import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
+import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleName;
+
+/**
+ * This system follows a specific lifecycle where an HTTP request is processed through specialized layers.
+ * To maintain a clean architecture, the core principle is that lower layers must never depend on higher layers.
+ * While the flow is not strictly linear—for instance, filters must call the chain to proceed—these rules ensure that
+ * dependencies only move in authorized directions and that components do not "skip" steps unnecessarily
+ *
+ * The request lifecycle is designed to follow this strict flow: (Runtime Flow)
+ *
+ * Client
+ * ↓
+ * ServerSocket
+ * ↓
+ * ConnectionHandler (Virtual Thread)
+ * ↓
+ * Pipeline
+ * ↓
+ * FilterChain
+ * ↓
+ * Router
+ * ↓
+ * Plugin
+ * ↓
+ * HttpResponseWriter
+ * ↓
+ * Client
+ *
+ * Note: This describes the runtime execution flow, not direct code dependencies.
+ * Dependencies are allowed for bootstrapping and controlled object creation,
+ * but must never violate the downward lifecycle direction.
+ */
+
+public class ArchitectureTest {
+
+ private static JavaClasses importedClasses;
+
+
+ @BeforeAll
+ static void setup() {
+ importedClasses = new ClassFileImporter()
+ .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
+ .importPackages("org.juv25d");
+ }
+ /**
+ * This rule ensures that only the Server and its associated factories can initiate a ConnectionHandler.
+ * This prevents other parts of the application from accidentally manipulating direct client connections.
+ */
+ @Test
+ void connectionHandlerAccessRule () {
+ ArchRuleDefinition.classes()
+ .that().haveSimpleName("ConnectionHandler")
+ .should().onlyBeAccessed().byClassesThat(
+ simpleName("Server")
+ .or(simpleName("ConnectionHandler"))
+ .or(simpleName("DefaultConnectionHandlerFactory"))
+ .or(simpleName("ConnectionHandlerFactory")))
+ .as("ConnectionHandler access rule")
+ .because("ConnectionHandler should only be accessed by server, connectionhandler or its factories")
+ .check(importedClasses);
+ }
+
+ /**
+ * Only the network layer (ConnectionHandler) or the application's startup class (App) may interact with the Pipeline.
+ * This guarantees that the execution chain remains intact and is not modified during an active request.
+ */
+ @Test
+ void pipelineAccessRule () {
+ ArchRuleDefinition.classes()
+ .that().haveSimpleName("Pipeline")
+ .should().onlyBeAccessed().byClassesThat(
+ simpleName("ConnectionHandler")
+ .or(simpleName("ConnectionHandlerFactory"))
+ .or(simpleName("DefaultConnectionHandlerFactory"))
+ .or(simpleName("Pipeline"))
+ .or(simpleName("App")) // App handles bootstrapping and wiring of the Pipeline during startup. This should stay.
+ .or(simpleName("Bootstrap")))
+ .as("Pipeline access rule")
+ .because("Pipeline should only be accessed by ConnectionHandler, App, Bootstrap during setup")
+ .check(importedClasses);
+ }
+
+
+ /**
+ * The FilterChain is created by the Pipeline and triggered by the ConnectionHandler.
+ * This rule also allows individual filters to access the chain.
+ */
+ @Test
+ void filterChainRule () {
+ ArchRuleDefinition.classes()
+ .that().haveSimpleName("FilterChain")
+ .should().onlyBeAccessed().byClassesThat(
+ simpleName("Pipeline")
+ .or(simpleName("FilterChain"))
+ .or(simpleName("FilterChainImpl"))
+ .or(resideInAPackage("..filter.."))
+ .or(simpleName("ConnectionHandler"))) //This needs to be accessed because ConnectionHandler creates doFilter()
+ .as("FilterChain access rule")
+ .because("FilterChain should only be accessed by Pipeline, ConnectionHandler")
+ .check(importedClasses);
+ }
+
+
+ /**
+ * The Router should only be accessed by the FilterChain to determine which plugin to execute,
+ * or by App and Pipeline during the system's bootstrapping phase.
+ */
+ @Test
+ void routerRule () {
+ ArchRuleDefinition.classes()
+ .that().haveSimpleName("Router")
+ .should().onlyBeAccessed().byClassesThat(
+ simpleName("FilterChain")
+ .or(simpleName("FilterChainImpl"))
+ .or(simpleName("Router"))
+ .or(simpleName("Pipeline")) //Pipeline injects router
+ .or(simpleName("App"))) //App Creates router
+ .as("Router access rule")
+ .because("Router should only be accessed by FilterChain, Pipeline, App")
+ .check(importedClasses);
+ }
+
+
+ /**
+ * Plugins must only be instantiated by App at startup and subsequently called by the router or
+ * the execution chain (FilterChainImpl).
+ */
+ @Test
+ void pluginRule () {
+ ArchRuleDefinition.classes()
+ .that().resideInAPackage("..plugin..")
+ .should().onlyBeAccessed().byClassesThat(
+ resideInAPackage("..router..")
+ .or(resideInAPackage("..plugin.."))
+ .or(simpleName("App")) //App creates plugin
+ .or(simpleName("FilterChainImpl"))) //FilterChainImpl calls the plugin after the router has decided which one to run.
+ .as("Plugin access rule")
+ .because("Plugins should only be managed by the Router, App, FilterChainImpl")
+ .check(importedClasses);
+ }
+
+ /**
+ * The HttpResponseWriter is the final step in the request lifecycle, responsible for delivering the response to the client.
+ * This rule ensures that only the ConnectionHandler accesses it, guaranteeing controlled delivery and architectural integrity.
+ */
+ @Test
+ void httpResponseWriterAccessRule() {
+ ArchRuleDefinition.classes()
+ .that().haveSimpleName("HttpResponseWriter")
+ .should().onlyBeAccessed().byClassesThat(
+ simpleName("ConnectionHandler")
+ .or(simpleName("HttpResponseWriter")))
+ .as("HttpResponseWriter access rule")
+ .because("HttpResponseWriter is the final step in the lifecycle and should" +
+ "only be used by ConnectionHandler to ensure controlled delivery")
+ .check(importedClasses);
+ }
+}
+
+