Gate shared-service multi-attach to provable shared infrastructure.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 20:44:58 +02:00
parent 4a7b3ff124
commit 4f3d6c40e7
4 changed files with 298 additions and 23 deletions

View File

@@ -62,11 +62,13 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
}
TriggerPoint trigger = chain.getTriggerPoint();
if (trigger != null && matchesConfiguredTransitionEvent(trigger, machineTransitions)) {
if (trigger != null
&& SharedServiceRoutingPolicy.isProvablySharedInfrastructure(chain)
&& matchesConfiguredTransitionEvent(trigger, machineTransitions)) {
return true;
}
return false;
return isRoutedToCorrectMachine(chain, currentMachineName, context);
}
@Override

View File

@@ -9,9 +9,44 @@ import java.util.Set;
*/
final class PackageNameRoutingHeuristics {
private static final Set<String> GENERIC_CLASS_PREFIXES = Set.of(
"state", "statemachine", "config", "configuration", "adapter",
"enterprise", "app", "global", "core", "project", "main",
"base", "common", "shared");
private static final Set<String> GENERIC_PACKAGE_SEGMENTS = Set.of(
"config", "configuration", "service", "services", "impl", "api", "web",
"controller", "controllers", "messaging", "listener", "listeners",
"repository", "repositories", "db", "model", "models", "domain",
"constants", "utils", "helper", "helpers", "event", "events",
"state", "states", "transition", "transitions", "shared", "common",
"global", "core", "base", "main", "app", "enterprise");
private PackageNameRoutingHeuristics() {
}
/**
* @return true when any class in the call chain carries a vertical domain prefix or package segment
*/
static boolean hasVerticalDomainOwnership(CallChain chain) {
if (chain == null) {
return false;
}
if (chain.getTriggerPoint() != null && chain.getTriggerPoint().getClassName() != null) {
if (classHasVerticalDomainOwnership(chain.getTriggerPoint().getClassName())) {
return true;
}
}
if (chain.getMethodChain() != null) {
for (String method : chain.getMethodChain()) {
if (classHasVerticalDomainOwnership(getClassNameOnly(method))) {
return true;
}
}
}
return false;
}
/**
* @return true/false if package heuristics decide, null if inconclusive
*/
@@ -60,7 +95,7 @@ final class PackageNameRoutingHeuristics {
String chainSimple = getSimpleClassName(chainClass);
String chainPrefix = getFirstCamelCaseWord(chainSimple);
if (smPrefix != null && chainPrefix != null && smPrefix.equals(chainPrefix)) {
if (hasMatchingDomainPrefix(smPrefix, chainPrefix)) {
hasPositiveMatch = true;
}
@@ -75,7 +110,7 @@ final class PackageNameRoutingHeuristics {
String chainSimple = getSimpleClassName(chainClass);
String chainPrefix = getFirstCamelCaseWord(chainSimple);
if (smPrefix != null && chainPrefix != null && smPrefix.equals(chainPrefix)) {
if (hasMatchingDomainPrefix(smPrefix, chainPrefix)) {
hasPositiveMatch = true;
}
@@ -151,16 +186,7 @@ final class PackageNameRoutingHeuristics {
String smLower = smPrefix.toLowerCase();
String chainLower = chainPrefix.toLowerCase();
if (smLower.equals("state") || smLower.equals("statemachine") || smLower.equals("config")
|| smLower.equals("configuration") || smLower.equals("adapter")
|| smLower.equals("enterprise") || smLower.equals("app") || smLower.equals("global")
|| smLower.equals("core") || smLower.equals("project") || smLower.equals("main")
|| smLower.equals("base") || smLower.equals("common") || smLower.equals("shared")
|| chainLower.equals("state") || chainLower.equals("statemachine") || chainLower.equals("config")
|| chainLower.equals("configuration") || chainLower.equals("adapter")
|| chainLower.equals("enterprise") || chainLower.equals("app") || chainLower.equals("global")
|| chainLower.equals("core") || chainLower.equals("project") || chainLower.equals("main")
|| chainLower.equals("base") || chainLower.equals("common") || chainLower.equals("shared")) {
if (isGenericClassPrefix(smLower) || isGenericClassPrefix(chainLower)) {
return false;
}
@@ -181,22 +207,15 @@ final class PackageNameRoutingHeuristics {
}
if (matchIdx >= 1) {
boolean onlyGenericDivergence = true;
Set<String> genericLayers = Set.of(
"config", "configuration", "service", "services", "impl", "api", "web",
"controller", "controllers", "messaging", "listener", "listeners",
"repository", "repositories", "db", "model", "models", "domain",
"constants", "utils", "helper", "helpers", "event", "events",
"state", "states", "transition", "transitions"
);
for (int i = matchIdx + 1; i < p1.length; i++) {
if (!genericLayers.contains(p1[i].toLowerCase())) {
if (!isGenericPackageSegment(p1[i])) {
onlyGenericDivergence = false;
break;
}
}
if (onlyGenericDivergence) {
for (int i = matchIdx + 1; i < p2.length; i++) {
if (!genericLayers.contains(p2[i].toLowerCase())) {
if (!isGenericPackageSegment(p2[i])) {
onlyGenericDivergence = false;
break;
}
@@ -253,4 +272,40 @@ final class PackageNameRoutingHeuristics {
}
return null;
}
private static boolean classHasVerticalDomainOwnership(String classFqn) {
if (classFqn == null || classFqn.isEmpty()) {
return false;
}
String prefix = getFirstCamelCaseWord(getSimpleClassName(classFqn));
if (prefix != null && !isGenericClassPrefix(prefix)) {
return true;
}
return hasVerticalPackageSegment(getPackageName(classFqn));
}
private static boolean hasVerticalPackageSegment(String packageName) {
if (packageName == null || packageName.isEmpty()) {
return false;
}
String[] segments = packageName.split("\\.");
for (int i = 2; i < segments.length; i++) {
if (!isGenericPackageSegment(segments[i])) {
return true;
}
}
return false;
}
private static boolean hasMatchingDomainPrefix(String smPrefix, String chainPrefix) {
return smPrefix != null && chainPrefix != null && smPrefix.equals(chainPrefix);
}
private static boolean isGenericClassPrefix(String prefix) {
return prefix != null && GENERIC_CLASS_PREFIXES.contains(prefix.toLowerCase());
}
private static boolean isGenericPackageSegment(String segment) {
return segment != null && GENERIC_PACKAGE_SEGMENTS.contains(segment.toLowerCase());
}
}

View File

@@ -0,0 +1,31 @@
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
/**
* Decides whether a call chain is provably shared infrastructure that may intentionally
* attach to multiple state machines when they share the same transition event.
* <p>
* Shared infrastructure requires source-derived evidence only: no distinct machine enum types
* at the sendEvent site and no vertical domain ownership in the call chain classes/packages.
* Event-name matching alone is never sufficient outside this policy.
*/
final class SharedServiceRoutingPolicy {
private SharedServiceRoutingPolicy() {
}
/**
* @return true when the chain may multi-attach to every machine that shares its transition event
*/
static boolean isProvablySharedInfrastructure(CallChain chain) {
if (chain == null) {
return false;
}
MachineRoutingEvidence evidence = MachineRoutingEvidence.from(chain);
if (evidence.eventTypeFqn() != null || evidence.stateTypeFqn() != null) {
return false;
}
return !PackageNameRoutingHeuristics.hasVerticalDomainOwnership(chain);
}
}