Expose ConfigFeatureFlagTransitionFactory to Starlark.

Note that two allowlists are attached:
//tools/allowlist/config_feature_flag:config_feature_flag - pre-existing and checks that any rule instance triggering the transitions (by setting the marked attribute) is in the allowlist

//tools/allowlist/config_feature_flag:config_feature_flag_setter - new and checks that any rule class/rule definition attaching the transition at all is in the allowlist. It is expected this allowlist is very short. Also, due to implementation constraints, this check is done by the rule instances (not when the rule() is executed in the bzl file).

PiperOrigin-RevId: 392817406
diff --git a/src/main/java/com/google/devtools/build/lib/packages/NonconfiguredAttributeMapper.java b/src/main/java/com/google/devtools/build/lib/packages/NonconfiguredAttributeMapper.java
new file mode 100644
index 0000000..98749f8
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/packages/NonconfiguredAttributeMapper.java
@@ -0,0 +1,56 @@
+// Copyright 2021 The Bazel Authors. All rights reserved.
+//
+// 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.
+package com.google.devtools.build.lib.packages;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * {@link AttributeMap} implementation that triggers an {@link IllegalStateException} if called on
+ * any attribute that is configured (has a select statement).
+ *
+ * <p>This is particularly useful for logic that doesn't have access to configurations - it protects
+ * against undefined behavior in response to unexpected configuration-dependent inputs.
+ *
+ * <p>This is different from {@link NonconfigurableAttributeMapper} as it does not require the
+ * attribute be declared as nonconfigurable. Instead, the attributes must not currently be
+ * configured (that is, have select statements). This distinction is crucial when considering
+ * Starlark-defined rules as they cannot declare attributes as nonconfigurable.
+ */
+public class NonconfiguredAttributeMapper extends AbstractAttributeMapper {
+  private NonconfiguredAttributeMapper(Rule rule) {
+    super(rule);
+  }
+
+  /**
+   * Example usage:
+   *
+   * <pre>
+   *   Label fooLabel = UnconfiguredAttributeMapper.of(rule).get("foo", Type.LABEL);
+   * </pre>
+   */
+  public static NonconfiguredAttributeMapper of(Rule rule) {
+    return new NonconfiguredAttributeMapper(rule);
+  }
+
+  @Override
+  public <T> T get(String attributeName, com.google.devtools.build.lib.packages.Type<T> type) {
+    if (getAttributeDefinition(attributeName).isConfigurable()) {
+      Preconditions.checkState(
+          getSelectorList(attributeName, type) == null,
+          "Attribute '%s' is configured - not allowed here",
+          attributeName);
+    }
+    return super.get(attributeName, type);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/config/BUILD b/src/main/java/com/google/devtools/build/lib/rules/config/BUILD
index 1a37be8..5be3f54 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/config/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/rules/config/BUILD
@@ -38,6 +38,7 @@
         "//src/main/java/com/google/devtools/build/lib/analysis:config/starlark_defined_config_transition",
         "//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/no_transition",
         "//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/patch_transition",
+        "//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/starlark_exposed_rule_transition_factory",
         "//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/transition_factory",
         "//src/main/java/com/google/devtools/build/lib/analysis:config/transitive_option_details",
         "//src/main/java/com/google/devtools/build/lib/analysis:configured_target",
diff --git a/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlag.java b/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlag.java
index 246db32..ccc2a8a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlag.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlag.java
@@ -46,10 +46,13 @@
  * The implementation of the config_feature_flag rule for defining custom flags for Android rules.
  */
 public class ConfigFeatureFlag implements RuleConfiguredTargetFactory {
-  /** The name of the policy that is used to restrict access to the config_feature_flag rule. */
+  /**
+   * The name of the policy that is used to restrict access to the config_feature_flag rule and
+   * attribute-triggered access to the feature flags setter transition.
+   */
   public static final String ALLOWLIST_NAME = "config_feature_flag";
 
-  /** The label of the policy that is used to restrict access to the config_feature_flag rule. */
+  /** The label of the policy for ALLOWLIST_NAME. */
   private static final String ALLOWLIST_LABEL =
       "//tools/allowlists/config_feature_flag:config_feature_flag";
 
@@ -62,21 +65,49 @@
   /**
    * Constructs a definition for the attribute used to restrict access to config_feature_flag. The
    * allowlist will only be reached if the given {@code attributeToInspect} has a value explicitly
-   * specified. It must be non-configurable.
+   * specified.
    */
   public static Attribute.Builder<Label> getAllowlistAttribute(
       RuleDefinitionEnvironment env, String attributeToInspect) {
     final Label label = env.getToolsLabel(ALLOWLIST_LABEL);
     return Allowlist.getAttributeFromAllowlistName(ALLOWLIST_NAME)
         .value(
+            /**
+             * Critically, get is never actually called on attributeToInspect and thus it is not
+             * necessary to declare whether it is configurable, for this context.
+             */
             new ComputedDefault() {
               @Override
               public Label getDefault(AttributeMap rule) {
                 return rule.isAttributeValueExplicitlySpecified(attributeToInspect) ? label : null;
               }
+
+              @Override
+              public boolean resolvableWithRawAttributes() {
+                return true;
+              }
             });
   }
 
+  /**
+   * The name of the policy that is used to restrict access to rule definitions attaching the
+   * feature flag setting transition.
+   *
+   * <p>Defined here for consistency with ALLOWLIST_NAME policy.
+   */
+  public static final String SETTER_ALLOWLIST_NAME = "config_feature_flag_setter";
+
+  /** The label of the policy for SETTER_ALLOWLIST_NAME. */
+  private static final String SETTER_ALLOWLIST_LABEL =
+      "//tools/allowlists/config_feature_flag:config_feature_flag_setter";
+
+  /** Constructs a definition for the attribute used to restrict access to config_feature_flag. */
+  public static Attribute.Builder<Label> getSetterAllowlistAttribute(
+      RuleDefinitionEnvironment env) {
+    return Allowlist.getAttributeFromAllowlistName(SETTER_ALLOWLIST_NAME)
+        .value(env.getToolsLabel(SETTER_ALLOWLIST_LABEL));
+  }
+
   @Override
   public ConfiguredTarget create(RuleContext ruleContext)
       throws InterruptedException, RuleErrorException, ActionConflictException {
@@ -85,7 +116,7 @@
     Predicate<String> isValidValue = Predicates.in(values);
     if (values.size() != specifiedValues.size()) {
       ImmutableMultiset<String> groupedValues = ImmutableMultiset.copyOf(specifiedValues);
-      ImmutableList.Builder<String> duplicates = new ImmutableList.Builder<String>();
+      ImmutableList.Builder<String> duplicates = new ImmutableList.Builder<>();
       for (Multiset.Entry<String> value : groupedValues.entrySet()) {
         if (value.getCount() > 1) {
           duplicates.add(value.getElement());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTaggedTrimmingTransitionFactory.java b/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTaggedTrimmingTransitionFactory.java
index 72c9a62..b30c8fc 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTaggedTrimmingTransitionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTaggedTrimmingTransitionFactory.java
@@ -28,7 +28,7 @@
 import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.EventHandler;
-import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
+import com.google.devtools.build.lib.packages.NonconfiguredAttributeMapper;
 import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleTransitionData;
 
@@ -93,7 +93,7 @@
 
   @Override
   public PatchTransition create(RuleTransitionData ruleData) {
-    NonconfigurableAttributeMapper attrs = NonconfigurableAttributeMapper.of(ruleData.rule());
+    NonconfiguredAttributeMapper attrs = NonconfiguredAttributeMapper.of(ruleData.rule());
     RuleClass ruleClass = ruleData.rule().getRuleClassObject();
     if (ruleClass.getName().equals(ConfigRuleClasses.ConfigFeatureFlagRule.RULE_NAME)) {
       return new ConfigFeatureFlagTaggedTrimmingTransition(
diff --git a/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTransitionFactory.java b/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTransitionFactory.java
index 5c82a2a..36f29af 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTransitionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/config/ConfigFeatureFlagTransitionFactory.java
@@ -18,15 +18,18 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedMap;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.analysis.config.BuildOptionsView;
 import com.google.devtools.build.lib.analysis.config.FragmentOptions;
 import com.google.devtools.build.lib.analysis.config.transitions.NoTransition;
 import com.google.devtools.build.lib.analysis.config.transitions.PatchTransition;
-import com.google.devtools.build.lib.analysis.config.transitions.TransitionFactory;
+import com.google.devtools.build.lib.analysis.config.transitions.StarlarkExposedRuleTransitionFactory;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.events.EventHandler;
-import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper;
+import com.google.devtools.build.lib.packages.AllowlistChecker;
+import com.google.devtools.build.lib.packages.NonconfiguredAttributeMapper;
+import com.google.devtools.build.lib.packages.RuleClass;
 import com.google.devtools.build.lib.packages.RuleTransitionData;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
@@ -38,7 +41,27 @@
  *
  * <p>Currently, this is only intended for use by android_binary and other Android top-level rules.
  */
-public class ConfigFeatureFlagTransitionFactory implements TransitionFactory<RuleTransitionData> {
+public class ConfigFeatureFlagTransitionFactory implements StarlarkExposedRuleTransitionFactory {
+  @Override
+  public void addToStarlarkRule(RuleDefinitionEnvironment ctx, RuleClass.Builder builder) {
+    builder.add(ConfigFeatureFlag.getAllowlistAttribute(ctx, attributeName));
+    builder.addAllowlistChecker(
+        AllowlistChecker.builder()
+            .setAllowlistAttr(ConfigFeatureFlag.ALLOWLIST_NAME)
+            .setErrorMessage("the attribute " + attributeName + " is not available in this package")
+            .setLocationCheck(AllowlistChecker.LocationCheck.INSTANCE)
+            .setAttributeSetTrigger(attributeName)
+            .build());
+    builder.add(ConfigFeatureFlag.getSetterAllowlistAttribute(ctx));
+    builder.addAllowlistChecker(
+        AllowlistChecker.builder()
+            .setAllowlistAttr(ConfigFeatureFlag.SETTER_ALLOWLIST_NAME)
+            .setErrorMessage(
+                "the rule class is not allowed access to feature flags setter transition")
+            .setLocationCheck(AllowlistChecker.LocationCheck.DEFINITION)
+            .setAttributeSetTrigger(attributeName)
+            .build());
+  }
 
   /** Transition which resets the set of flag-value pairs to the map it was constructed with. */
   @AutoCodec
@@ -95,7 +118,7 @@
    * exactly the flag values in the attribute with the given {@code attributeName} of that rule,
    * unsetting any flag values not listed there.
    *
-   * <p>This attribute must be a nonconfigurable {@code LABEL_KEYED_STRING_DICT}.
+   * <p>This attribute must not be a configured {@code LABEL_KEYED_STRING_DICT}. (No selects)
    */
   public ConfigFeatureFlagTransitionFactory(String attributeName) {
     this.attributeName = attributeName;
@@ -103,7 +126,7 @@
 
   @Override
   public PatchTransition create(RuleTransitionData ruleData) {
-    NonconfigurableAttributeMapper attrs = NonconfigurableAttributeMapper.of(ruleData.rule());
+    NonconfiguredAttributeMapper attrs = NonconfiguredAttributeMapper.of(ruleData.rule());
     if (attrs.isAttributeValueExplicitlySpecified(attributeName)) {
       return new ConfigFeatureFlagValuesTransition(
           attrs.get(attributeName, LABEL_KEYED_STRING_DICT));
diff --git a/src/main/java/com/google/devtools/build/lib/rules/config/ConfigStarlarkCommon.java b/src/main/java/com/google/devtools/build/lib/rules/config/ConfigStarlarkCommon.java
index de98aab..66461c5 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/config/ConfigStarlarkCommon.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/config/ConfigStarlarkCommon.java
@@ -14,6 +14,7 @@
 
 package com.google.devtools.build.lib.rules.config;
 
+import com.google.devtools.build.lib.analysis.config.transitions.StarlarkExposedRuleTransitionFactory;
 import com.google.devtools.build.lib.packages.Provider;
 import com.google.devtools.build.lib.starlarkbuildapi.config.ConfigStarlarkCommonApi;
 
@@ -24,4 +25,10 @@
   public Provider getConfigFeatureFlagProviderConstructor() {
     return ConfigFeatureFlagProvider.STARLARK_CONSTRUCTOR;
   }
+
+  @Override
+  public StarlarkExposedRuleTransitionFactory createConfigFeatureFlagTransitionFactory(
+      String attribute) {
+    return new ConfigFeatureFlagTransitionFactory(attribute);
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config/BUILD b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config/BUILD
index fa41d5d..7115d62 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config/BUILD
@@ -21,6 +21,7 @@
     srcs = glob(["*.java"]),
     deps = [
         "//src/main/java/com/google/devtools/build/docgen/annot",
+        "//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/starlark_exposed_rule_transition_factory",
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi",
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/core",
         "//src/main/java/net/starlark/java/annot",
diff --git a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config/ConfigStarlarkCommonApi.java b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config/ConfigStarlarkCommonApi.java
index b0ad1e5..ed337b6 100644
--- a/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config/ConfigStarlarkCommonApi.java
+++ b/src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config/ConfigStarlarkCommonApi.java
@@ -14,7 +14,9 @@
 
 package com.google.devtools.build.lib.starlarkbuildapi.config;
 
+import com.google.devtools.build.lib.analysis.config.transitions.StarlarkExposedRuleTransitionFactory;
 import com.google.devtools.build.lib.starlarkbuildapi.core.ProviderApi;
+import net.starlark.java.annot.Param;
 import net.starlark.java.annot.StarlarkBuiltin;
 import net.starlark.java.annot.StarlarkMethod;
 import net.starlark.java.eval.StarlarkValue;
@@ -30,4 +32,16 @@
       doc = "The key used to retrieve the provider containing config_feature_flag's value.",
       structField = true)
   ProviderApi getConfigFeatureFlagProviderConstructor();
+
+  @StarlarkMethod(
+      name = "config_feature_flag_transition",
+      documented = false,
+      parameters = {
+        @Param(
+            name = "attribute",
+            positional = true,
+            named = false,
+            doc = "string corresponding to rule attribute to read")
+      })
+  StarlarkExposedRuleTransitionFactory createConfigFeatureFlagTransitionFactory(String attribute);
 }
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/BUILD b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/BUILD
index 841a2a6..c31b66c 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/BUILD
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/BUILD
@@ -15,6 +15,7 @@
     srcs = glob(["**/*.java"]),
     deps = [
         "//src/main/java/com/google/devtools/build/lib/actions:artifacts",
+        "//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/starlark_exposed_rule_transition_factory",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi",
         "//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/config",
diff --git a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/config/FakeConfigStarlarkCommon.java b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/config/FakeConfigStarlarkCommon.java
index 5efe193..e81a6dc 100644
--- a/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/config/FakeConfigStarlarkCommon.java
+++ b/src/main/java/com/google/devtools/build/skydoc/fakebuildapi/config/FakeConfigStarlarkCommon.java
@@ -14,6 +14,7 @@
 
 package com.google.devtools.build.skydoc.fakebuildapi.config;
 
+import com.google.devtools.build.lib.analysis.config.transitions.StarlarkExposedRuleTransitionFactory;
 import com.google.devtools.build.lib.starlarkbuildapi.config.ConfigStarlarkCommonApi;
 import com.google.devtools.build.lib.starlarkbuildapi.core.ProviderApi;
 import com.google.devtools.build.skydoc.fakebuildapi.FakeProviderApi;
@@ -27,4 +28,10 @@
   public ProviderApi getConfigFeatureFlagProviderConstructor() {
     return new FakeProviderApi("FeatureFlagInfo");
   }
+
+  @Override
+  public StarlarkExposedRuleTransitionFactory createConfigFeatureFlagTransitionFactory(
+      String attribute) {
+    return null;
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
index 0191c7c..2ce22fa 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
@@ -279,15 +279,16 @@
         "package_group(",
         "    name='config_feature_flag',",
         "    includes=['@//tools/allowlists/config_feature_flag'],",
+        ")",
+        "package_group(",
+        "    name='config_feature_flag_setter',",
+        "    includes=['@//tools/allowlists/config_feature_flag:config_feature_flag_setter'],",
         ")");
 
     config.create(
         "tools/allowlists/config_feature_flag/BUILD",
-        "package_group(name='config_feature_flag', packages=['//...'])");
-
-    config.create(
-        "tools/allowlists/config_feature_flag/BUILD",
-        "package_group(name='config_feature_flag', packages=['//...'])");
+        "package_group(name='config_feature_flag', packages=['//...'])",
+        "package_group(name='config_feature_flag_Setter', packages=['//...'])");
 
     config.create(
         "embedded_tools/tools/zip/BUILD",
diff --git a/src/test/java/com/google/devtools/build/lib/rules/config/StarlarkConfigFeatureFlagTransitionFactoryTest.java b/src/test/java/com/google/devtools/build/lib/rules/config/StarlarkConfigFeatureFlagTransitionFactoryTest.java
new file mode 100644
index 0000000..27089ef
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/rules/config/StarlarkConfigFeatureFlagTransitionFactoryTest.java
@@ -0,0 +1,219 @@
+// Copyright 2021 The Bazel Authors. All rights reserved.
+//
+// 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
+
+package com.google.devtools.build.lib.rules.config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
+import com.google.devtools.build.lib.analysis.util.BuildViewTestCase;
+import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Starlark-integration Tests for the ConfigFeatureFlagTransitionFactory. */
+@RunWith(JUnit4.class)
+public final class StarlarkConfigFeatureFlagTransitionFactoryTest extends BuildViewTestCase {
+
+  @Override
+  protected ConfiguredRuleClassProvider createRuleClassProvider() {
+    ConfiguredRuleClassProvider.Builder builder =
+        new ConfiguredRuleClassProvider.Builder().addRuleDefinition(new FeatureFlagSetterRule());
+    TestRuleClassProvider.addStandardRules(builder);
+    return builder.build();
+  }
+
+  private void setupRulesBzl() throws Exception {
+    scratch.file("rules/BUILD", "");
+    scratch.file(
+        "rules/rule.bzl",
+        "def _blank_impl(ctx):",
+        "  return []",
+        "def _check_impl(ctx):",
+        "  if ctx.attr.succeed:",
+        "    return []",
+        "  else:",
+        "    fail('Rule has failed intentionally.')",
+        "feature_flag_setter = rule(",
+        "  attrs = {",
+        "      'flag_values': attr.label_keyed_string_dict(),",
+        "      'deps': attr.label_list(),",
+        "  },",
+        "  cfg = config_common.config_feature_flag_transition('flag_values'),",
+        "  implementation = _blank_impl,",
+        ")",
+        "check_something = rule(",
+        "  attrs = {",
+        "      'succeed': attr.bool(),",
+        "  },",
+        "  implementation = _check_impl,",
+        ")");
+  }
+
+  @Test
+  public void setsFeatureFlagSuccessfully() throws Exception {
+    setupRulesBzl();
+    scratch.file(
+        "foo/BUILD",
+        "load('//rules:rule.bzl', 'feature_flag_setter', 'check_something')",
+        "config_feature_flag(",
+        "    name = 'fruit',",
+        "    allowed_values = ['orange', 'apple', 'lemon'],",
+        "    default_value = 'orange',",
+        ")",
+        "config_setting(",
+        "  name = 'is_apple',",
+        "  flag_values = {':fruit': 'apple'},",
+        "  transitive_configs = [':fruit'],",
+        ")",
+        "feature_flag_setter(",
+        "  name = 'top',",
+        "  flag_values = {':fruit': 'apple'},",
+        "  deps = [':some_dep'],",
+        "  transitive_configs = [':fruit'],",
+        ")",
+        "check_something(",
+        "  name = 'some_dep',",
+        "  succeed = select({",
+        "    ':is_apple': True,",
+        "    '//conditions:default': False,",
+        "  }),",
+        "  transitive_configs = [':fruit'],",
+        ")");
+    scratch.overwriteFile(
+        "tools/allowlists/config_feature_flag/BUILD",
+        "package_group(",
+        "  name = 'config_feature_flag',",
+        "  packages = ['//foo/...'],",
+        ")",
+        "package_group(",
+        "  name = 'config_feature_flag_setter',",
+        "  packages = ['//rules/...'],",
+        ")");
+    assertThat(getConfiguredTarget("//foo:top")).isNotNull();
+    assertNoEvents();
+  }
+
+  @Test
+  public void failsWhenFeatureFlagSuccessfullySetToBadValue() throws Exception {
+    // This is mostly a test of the testing infrastructure itself.
+    // Want to ensure check_something isn't spuriously passing for whatever reason.
+    setupRulesBzl();
+    scratch.file(
+        "foo/BUILD",
+        "load('//rules:rule.bzl', 'feature_flag_setter', 'check_something')",
+        "config_feature_flag(",
+        "    name = 'fruit',",
+        "    allowed_values = ['orange', 'apple', 'lemon'],",
+        "    default_value = 'orange',",
+        ")",
+        "config_setting(",
+        "  name = 'is_apple',",
+        "  flag_values = {':fruit': 'apple'},",
+        "  transitive_configs = [':fruit'],",
+        ")",
+        "feature_flag_setter(",
+        "  name = 'top',",
+        "  flag_values = {':fruit': 'orange'},",
+        "  deps = [':some_dep'],",
+        "  transitive_configs = [':fruit'],",
+        ")",
+        "check_something(",
+        "  name = 'some_dep',",
+        "  succeed = select({",
+        "    ':is_apple': True,",
+        "    '//conditions:default': False,",
+        "  }),",
+        "  transitive_configs = [':fruit'],",
+        ")");
+    scratch.overwriteFile(
+        "tools/allowlists/config_feature_flag/BUILD",
+        "package_group(",
+        "  name = 'config_feature_flag',",
+        "  packages = ['//foo/...'],",
+        ")",
+        "package_group(",
+        "  name = 'config_feature_flag_setter',",
+        "  packages = ['//rules/...'],",
+        ")");
+    reporter.removeHandler(failFastHandler);
+    getConfiguredTarget("//foo:top");
+    assertContainsEvent("Error in fail: Rule has failed intentionally.");
+  }
+
+  @Test
+  public void failsWhenInstanceNotInAllowlist() throws Exception {
+    setupRulesBzl();
+    scratch.file(
+        "bar/BUILD",
+        "config_feature_flag(",
+        "    name = 'fruit',",
+        "    allowed_values = ['orange', 'apple', 'lemon'],",
+        "    default_value = 'orange',",
+        ")");
+    scratch.file(
+        "foo/BUILD",
+        "load('//rules:rule.bzl', 'feature_flag_setter')",
+        "feature_flag_setter(",
+        "  name = 'top',",
+        "  flag_values = {'//bar:fruit': 'apple'},",
+        "  transitive_configs = ['//bar:fruit'],",
+        ")");
+    scratch.overwriteFile(
+        "tools/allowlists/config_feature_flag/BUILD",
+        "package_group(",
+        "  name = 'config_feature_flag',",
+        "  packages = ['//bar/...'],",
+        ")",
+        "package_group(",
+        "  name = 'config_feature_flag_setter',",
+        "  packages = ['//rules/...'],",
+        ")");
+    reporter.removeHandler(failFastHandler);
+    getConfiguredTarget("//foo:top");
+    assertContainsEvent("the attribute flag_values is not available in this package");
+  }
+
+  @Test
+  public void failsWhenRuleClassNotInSetterAllowlist() throws Exception {
+    setupRulesBzl();
+    scratch.file(
+        "foo/BUILD",
+        "load('//rules:rule.bzl', 'feature_flag_setter')",
+        "config_feature_flag(",
+        "    name = 'fruit',",
+        "    allowed_values = ['orange', 'apple', 'lemon'],",
+        "    default_value = 'orange',",
+        ")",
+        "feature_flag_setter(",
+        "  name = 'top',",
+        "  flag_values = {':fruit': 'apple'},",
+        "  transitive_configs = [':fruit'],",
+        ")");
+    scratch.overwriteFile(
+        "tools/allowlists/config_feature_flag/BUILD",
+        "package_group(",
+        "  name = 'config_feature_flag',",
+        "  packages = ['//foo/...'],",
+        ")",
+        "package_group(",
+        "  name = 'config_feature_flag_setter',",
+        "  packages = [],",
+        ")");
+    reporter.removeHandler(failFastHandler);
+    getConfiguredTarget("//foo:top");
+    assertContainsEvent("rule class is not allowed access to feature flags setter transition");
+  }
+}