diff --git a/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/application/contracts/AcceptUrlTemplate.kt b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/application/contracts/AcceptUrlTemplate.kt index ee912f907..d8f0fbd4a 100644 --- a/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/application/contracts/AcceptUrlTemplate.kt +++ b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/application/contracts/AcceptUrlTemplate.kt @@ -1,13 +1,5 @@ package com.profiletailors.smp.platformadmin.application.contracts -import org.springframework.modulith.NamedInterface - -/** - * Builds the fully-formed accept URL with the raw token embedded. Centralised so the URL - * shape stays consistent across invite/resend flows and so the raw token never has to be - * mixed into controller code. - */ -@NamedInterface("contracts") fun interface AcceptUrlTemplate { fun build(rawToken: String): String } diff --git a/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/application/contracts/package-info.java b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/application/contracts/package-info.java new file mode 100644 index 000000000..a981aadcf --- /dev/null +++ b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/application/contracts/package-info.java @@ -0,0 +1 @@ +package com.profiletailors.smp.platformadmin.application.contracts; diff --git a/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/DirectInvitationResent.kt b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/DirectInvitationResent.kt index 354407155..2c68df93b 100644 --- a/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/DirectInvitationResent.kt +++ b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/DirectInvitationResent.kt @@ -1,10 +1,8 @@ package com.profiletailors.smp.platformadmin.domain import com.profiletailors.common.domain.bus.event.BaseDomainEvent -import org.springframework.modulith.NamedInterface import java.util.UUID -@NamedInterface("domain") data class DirectInvitationResent( val invitationId: UUID, val operatorPrincipalId: UUID, diff --git a/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/DomainLayerExports.kt b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/DomainLayerExports.kt deleted file mode 100644 index 5b996082b..000000000 --- a/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/DomainLayerExports.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.profiletailors.smp.platformadmin.domain - -import org.springframework.modulith.NamedInterface - -/** - * Marker class exposing the domain layer to the notifications module. - * - * Excluded from HexagonalArchTest rules - * [domainLayerShouldNotDependOnSpring] / [domainLayerShouldNotDependOnInfrastructureFrameworks] - * via explicit class-name predicate: haveSimpleName != "DomainLayerExports". - * - * Spring Modulith requires a [@NamedInterface("domain")] on a class residing in the - * `platformadmin.domain` package for the `platformadmin :: domain` allowed-dependency - * syntax to resolve correctly. - */ -@NamedInterface("domain") -internal class DomainLayerExports diff --git a/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/InvitationIssued.kt b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/InvitationIssued.kt index b22f90b4e..866265815 100644 --- a/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/InvitationIssued.kt +++ b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/InvitationIssued.kt @@ -1,25 +1,8 @@ package com.profiletailors.smp.platformadmin.domain import com.profiletailors.common.domain.bus.event.BaseDomainEvent -import org.springframework.modulith.NamedInterface import java.util.UUID -/** - * Domain event published when a platform operator issues a new invitation to a waitlist lead. - * - * This event contains the non-sensitive invitation data required by notification delivery. - * - * This replaces the previous `InvitationCreated` event as part of establishing clean architectural - * boundaries between the Invitation lifecycle (ACTIVE/ACCEPTED/EXPIRED/REVOKED) and Notification - * delivery lifecycle (PENDING/SENT/FAILED). - * - * @property invitationId canonical invitation identifier (UUID) - * @property recipientEmail normalized email address of the invitee - * @property workspaceName human-readable workspace name used in email copy - * @property locale optional BCP-47 locale code (e.g. "en", "es") for template selection - * @property rawToken ephemeral bearer token used by the notification consumer to build acceptance URLs - */ -@NamedInterface("domain") data class InvitationIssued( val invitationId: UUID, val recipientEmail: String, diff --git a/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/package-info.java b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/package-info.java new file mode 100644 index 000000000..e9e1b0143 --- /dev/null +++ b/server/smp/src/main/kotlin/com/profiletailors/smp/platformadmin/domain/package-info.java @@ -0,0 +1 @@ +package com.profiletailors.smp.platformadmin.domain; diff --git a/server/smp/src/test/kotlin/com/profiletailors/smp/HexagonalArchTest.kt b/server/smp/src/test/kotlin/com/profiletailors/smp/HexagonalArchTest.kt index ff60cf627..e5a53cfb6 100644 --- a/server/smp/src/test/kotlin/com/profiletailors/smp/HexagonalArchTest.kt +++ b/server/smp/src/test/kotlin/com/profiletailors/smp/HexagonalArchTest.kt @@ -9,18 +9,6 @@ import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -/** - * Architecture tests that enforce hexagonal layer boundaries in the smp module. - * - * Rules: - * - domain: pure Kotlin, no Spring, no upward dependencies - * - application: depends on domain only (within smp packages) - * - infrastructure: may depend on domain and application - * - every bounded context exposes domain, application, and infrastructure packages - * - * Bounded contexts are auto-discovered from the package tree so that - * adding a new context automatically includes it in the validation. - */ internal class HexagonalArchTest { private lateinit var importedClasses: JavaClasses @@ -54,12 +42,6 @@ internal class HexagonalArchTest { .haveSimpleNameNotEndingWith("ModuleMetadata") .and() .haveSimpleNameNotEndingWith("package-info") - .and() - .haveSimpleNameNotContaining("InvitationIssued") - .and() - .haveSimpleNameNotContaining("DirectInvitationResent") - .and() - .haveSimpleNameNotContaining("DomainLayerExports") .should() .dependOnClassesThat() .resideInAnyPackage("org.springframework..") @@ -119,10 +101,6 @@ internal class HexagonalArchTest { .check(importedClasses) } - /** - * Guards against the real violation: application layer using Spring R2DBC, HTTP, or Security - * imports directly — bypassing the domain port/abstraction layer. - */ @Test fun applicationLayerShouldNotDependOnSpringR2dbcHttpOrSecurity() { ArchRuleDefinition.noClasses() @@ -143,9 +121,6 @@ internal class HexagonalArchTest { .check(importedClasses) } - /** - * Guards against reactive/infrastructure imports leaking into application via coroutine adapters. - */ @Test fun applicationLayerShouldNotDependOnReactorOrCoroutinesReactor() { ArchRuleDefinition.noClasses() @@ -164,9 +139,6 @@ internal class HexagonalArchTest { .check(importedClasses) } - /** - * Guards against Spring Security base classes leaking into application exceptions. - */ @Test fun applicationLayerShouldNotExtendSpringSecurityClasses() { ArchRuleDefinition.noClasses() @@ -182,9 +154,6 @@ internal class HexagonalArchTest { .check(importedClasses) } - /** - * Guards domain from any infrastructure framework — even narrower than the existing Spring check. - */ @Test fun domainLayerShouldNotDependOnInfrastructureFrameworks() { ArchRuleDefinition.noClasses() @@ -194,12 +163,6 @@ internal class HexagonalArchTest { .haveSimpleNameNotEndingWith("ModuleMetadata") .and() .haveSimpleNameNotEndingWith("package-info") - .and() - .haveSimpleNameNotContaining("InvitationIssued") - .and() - .haveSimpleNameNotContaining("DirectInvitationResent") - .and() - .haveSimpleNameNotContaining("DomainLayerExports") .should() .dependOnClassesThat() .resideInAnyPackage(