diff --git a/src/main/java/com/solubris/enforcer/BomImportScopeRule.java b/src/main/java/com/solubris/enforcer/BomImportScopeRule.java
new file mode 100644
index 0000000..e222d18
--- /dev/null
+++ b/src/main/java/com/solubris/enforcer/BomImportScopeRule.java
@@ -0,0 +1,106 @@
+package com.solubris.enforcer;
+
+import org.apache.maven.enforcer.rule.api.AbstractEnforcerRule;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.DependencyManagement;
+import org.apache.maven.model.Model;
+import org.apache.maven.project.MavenProject;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.util.Collection;
+import java.util.Optional;
+import java.util.concurrent.atomic.LongAdder;
+import java.util.stream.Stream;
+
+import static java.text.MessageFormat.format;
+import static java.util.stream.Collectors.joining;
+import static org.apache.maven.artifact.ArtifactUtils.key;
+
+/**
+ * A custom Maven Enforcer rule that ensures all dependencies declared with
+ * {@code type=pom} (i.e. BOMs) also specify {@code scope=import}.
+ *
+ *
This rule inspects both the top-level {@code } section and
+ * the {@code } section of the project. Any dependency
+ * with {@code type=pom} that does not have {@code scope=import} will cause
+ * a build failure.
+ *
+ * BOMs (Bill of Materials) are intended to be imported via
+ * {@code import} inside {@code }.
+ * Using them without import scope is almost always a mistake and can lead
+ * to unexpected transitive dependency resolution.
+ */
+@Named("bomImportScopeRule")
+public class BomImportScopeRule extends AbstractEnforcerRule {
+ private static final String POM_TYPE = "pom";
+ private static final String IMPORT_SCOPE = "import";
+
+ private final Model model;
+
+ @SuppressWarnings("unused")
+ @Inject
+ public BomImportScopeRule(MavenSession session) {
+ this(modelFrom(session));
+ }
+
+ protected BomImportScopeRule(Model model) {
+ this.model = model;
+ }
+
+ private static Model modelFrom(MavenSession session) {
+ MavenProject project = session.getCurrentProject();
+ Model originalModel = project.getOriginalModel();
+ return originalModel != null ? originalModel : project.getModel();
+ }
+
+ @Override
+ public void execute() throws EnforcerRuleException {
+ LongAdder count = new LongAdder();
+ String body = scanAll()
+ .peek(v -> count.increment())
+ .map(v -> " - " + v)
+ .collect(joining("\n", "", "\n"));
+ if (count.longValue() > 0) {
+ String title = "BOM dependencies (type=pom) must use scope=import. Found " + count.sum() + " violation(s):";
+ throw new EnforcerRuleException(title + "\n" + body);
+ }
+
+ getLog().info("All BOM dependencies (type=pom) correctly use scope=import.");
+ }
+
+ protected Stream scanAll() {
+ Stream topLevelViolations = directDependencies()
+ .filter(dep -> POM_TYPE.equals(dep.getType()))
+ .map(dep -> formatViolation(dep, "dependencies"));
+
+ Stream depMgmtViolations = managedDependencies()
+ .filter(dep -> POM_TYPE.equals(dep.getType()))
+ .filter(dep -> !IMPORT_SCOPE.equals(dep.getScope()))
+ .map(dep -> formatViolation(dep, "dependencyManagement"));
+
+ return Stream.concat(topLevelViolations, depMgmtViolations);
+ }
+
+ private Stream directDependencies() {
+ return Optional.ofNullable(model.getDependencies())
+ .stream()
+ .flatMap(Collection::stream);
+ }
+
+ private Stream managedDependencies() {
+ return Optional.ofNullable(model.getDependencyManagement())
+ .map(DependencyManagement::getDependencies)
+ .stream()
+ .flatMap(Collection::stream);
+ }
+
+ private static String formatViolation(Dependency dep, String section) {
+ String scope = dep.getScope();
+ String scopeInfo = (scope == null || scope.isEmpty()) ? "no scope" : "scope=" + scope;
+ String key = key(dep.getGroupId(), dep.getArtifactId(), dep.getVersion());
+ return format("{0} in <{1}> has type=pom but {2} (expected scope=import)", key, section, scopeInfo);
+ }
+}
diff --git a/src/test/java/com/solubris/enforcer/BomImportScopeRuleTest.java b/src/test/java/com/solubris/enforcer/BomImportScopeRuleTest.java
new file mode 100644
index 0000000..10d5f7c
--- /dev/null
+++ b/src/test/java/com/solubris/enforcer/BomImportScopeRuleTest.java
@@ -0,0 +1,151 @@
+package com.solubris.enforcer;
+
+import org.apache.maven.enforcer.rule.api.EnforcerLogger;
+import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.DependencyManagement;
+import org.apache.maven.model.Model;
+import org.junit.jupiter.api.Test;
+
+import java.util.stream.Stream;
+
+import static com.solubris.enforcer.ModelStubber.dependencyOf;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+class BomImportScopeRuleTest {
+ private final Model model = new Model();
+ private final BomImportScopeRule rule = new BomImportScopeRule(model);
+
+ BomImportScopeRuleTest() {
+ rule.setLog(mock(EnforcerLogger.class));
+ }
+
+ @Test
+ void noDependenciesPasses() {
+ assertThatNoException().isThrownBy(rule::execute);
+ }
+
+ @Test
+ void jarDependencyPasses() {
+ model.addDependency(dependencyOf("com.example", "some-lib", "1.0"));
+
+ assertThatNoException().isThrownBy(rule::execute);
+ }
+
+ @Test
+ void bomWithImportScopeInDepMgmtPasses() {
+ Dependency bom = bomDependency("com.example", "some-bom", "1.0", "import");
+ DependencyManagement depMgmt = new DependencyManagement();
+ depMgmt.addDependency(bom);
+ model.setDependencyManagement(depMgmt);
+
+ assertThatNoException().isThrownBy(rule::execute);
+ }
+
+ @Test
+ void bomWithoutImportScopeInDepMgmtFails() {
+ Dependency bom = bomDependency("com.example", "some-bom", "1.0", "compile");
+ DependencyManagement depMgmt = new DependencyManagement();
+ depMgmt.addDependency(bom);
+ model.setDependencyManagement(depMgmt);
+
+ Stream violations = rule.scanAll();
+
+ assertThat(violations)
+ .hasSize(1)
+ .first()
+ .asString()
+ .contains("com.example:some-bom:1.0")
+ .contains("scope=compile")
+ .contains("dependencyManagement");
+ }
+
+ @Test
+ void bomWithNoScopeInDepMgmtFails() {
+ Dependency bom = bomDependency("com.example", "some-bom", "1.0", null);
+ DependencyManagement depMgmt = new DependencyManagement();
+ depMgmt.addDependency(bom);
+ model.setDependencyManagement(depMgmt);
+
+ Stream violations = rule.scanAll();
+
+ assertThat(violations)
+ .hasSize(1)
+ .first()
+ .asString()
+ .contains("com.example:some-bom:1.0")
+ .contains("no scope");
+ }
+
+ @Test
+ void bomInTopLevelDependenciesFails() {
+ Dependency bom = bomDependency("com.example", "some-bom", "1.0", "import");
+ model.addDependency(bom);
+
+ Stream violations = rule.scanAll();
+
+ assertThat(violations)
+ .hasSize(1)
+ .first()
+ .asString()
+ .contains("com.example:some-bom:1.0")
+ .contains("");
+ }
+
+ @Test
+ void multipleViolationsReported() {
+ // BOM in top-level dependencies
+ Dependency topBom = bomDependency("com.example", "top-bom", "1.0", "compile");
+ model.addDependency(topBom);
+
+ // BOM in depMgmt without import scope
+ Dependency mgmtBom = bomDependency("com.example", "mgmt-bom", "2.0", "runtime");
+ DependencyManagement depMgmt = new DependencyManagement();
+ depMgmt.addDependency(mgmtBom);
+ model.setDependencyManagement(depMgmt);
+
+ Stream violations = rule.scanAll();
+
+ assertThat(violations)
+ .hasSize(2)
+ .anyMatch(v -> v.contains("com.example:top-bom:1.0"))
+ .anyMatch(v -> v.contains("com.example:mgmt-bom:2.0"));
+ }
+
+ @Test
+ void mixedDependenciesWithValidBomPasses() {
+ // Non-pom dependency in top-level deps
+ model.addDependency(dependencyOf("com.example", "some-lib", "1.0"));
+
+ // Valid BOM in depMgmt
+ Dependency bom = bomDependency("com.example", "some-bom", "1.0", "import");
+ DependencyManagement depMgmt = new DependencyManagement();
+ depMgmt.addDependency(bom);
+ model.setDependencyManagement(depMgmt);
+
+ assertThatNoException().isThrownBy(rule::execute);
+ }
+
+ @Test
+ void executeThrowsWithViolationMessage() {
+ Dependency bom = bomDependency("com.example", "bad-bom", "3.0", "compile");
+ DependencyManagement depMgmt = new DependencyManagement();
+ depMgmt.addDependency(bom);
+ model.setDependencyManagement(depMgmt);
+
+ assertThatThrownBy(rule::execute)
+ .isInstanceOf(EnforcerRuleException.class)
+ .hasMessageContaining("com.example:bad-bom:3.0")
+ .hasMessageContaining("1 violation(s)");
+ }
+
+ private static Dependency bomDependency(String groupId, String artifactId, String version, String scope) {
+ Dependency dep = dependencyOf(groupId, artifactId, version);
+ dep.setType("pom");
+ dep.setScope(scope);
+ return dep;
+ }
+}