Change signatures to DerivedArtifact, and get Labels from non-Artifact sources when possible.

In tests, try to get artifacts directly from the actual configured target, rather than creating fresh ones.

This change should be a prod functional no-op, just changing signatures. Split out from the follow-up to reduce the diff.

PiperOrigin-RevId: 250527865
diff --git a/src/main/java/com/google/devtools/build/lib/actions/AbstractAction.java b/src/main/java/com/google/devtools/build/lib/actions/AbstractAction.java
index 379962f..51c994f 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/AbstractAction.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/AbstractAction.java
@@ -136,7 +136,7 @@
       Iterable<Artifact> tools,
       Iterable<Artifact> inputs,
       RunfilesSupplier runfilesSupplier,
-      Iterable<Artifact> outputs,
+      Iterable<? extends Artifact> outputs,
       ActionEnvironment env) {
     Preconditions.checkNotNull(owner);
     this.owner = owner;
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionInputHelper.java b/src/main/java/com/google/devtools/build/lib/actions/ActionInputHelper.java
index 1b0bf61..2d2ffd7 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionInputHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionInputHelper.java
@@ -142,14 +142,15 @@
    * Instantiates a concrete TreeFileArtifact with the given parent Artifact and path relative to
    * that Artifact.
    */
-  public static TreeFileArtifact treeFileArtifact(Artifact parent, PathFragment relativePath) {
+  public static TreeFileArtifact treeFileArtifact(
+      Artifact.SpecialArtifact parent, PathFragment relativePath) {
     Preconditions.checkState(parent.isTreeArtifact(),
         "Given parent %s must be a TreeArtifact", parent);
     return new TreeFileArtifact(parent, relativePath);
   }
 
   public static TreeFileArtifact treeFileArtifact(
-      Artifact parent, PathFragment relativePath, ArtifactOwner artifactOwner) {
+      Artifact.SpecialArtifact parent, PathFragment relativePath, ArtifactOwner artifactOwner) {
     Preconditions.checkState(parent.isTreeArtifact(),
         "Given parent %s must be a TreeArtifact", parent);
     return new TreeFileArtifact(
@@ -162,13 +163,14 @@
    * Instantiates a concrete TreeFileArtifact with the given parent Artifact and path relative to
    * that Artifact.
    */
-  public static TreeFileArtifact treeFileArtifact(Artifact parent, String relativePath) {
+  public static TreeFileArtifact treeFileArtifact(
+      Artifact.SpecialArtifact parent, String relativePath) {
     return treeFileArtifact(parent, PathFragment.create(relativePath));
   }
 
   /** Returns an Iterable of TreeFileArtifacts with the given parent and parent relative paths. */
   public static Iterable<TreeFileArtifact> asTreeFileArtifacts(
-      final Artifact parent, Iterable<? extends PathFragment> parentRelativePaths) {
+      final Artifact.SpecialArtifact parent, Iterable<? extends PathFragment> parentRelativePaths) {
     Preconditions.checkState(parent.isTreeArtifact(),
         "Given parent %s must be a TreeArtifact", parent);
     return Iterables.transform(
@@ -177,7 +179,7 @@
 
   /** Returns a Set of TreeFileArtifacts with the given parent and parent-relative paths. */
   public static Set<TreeFileArtifact> asTreeFileArtifacts(
-      final Artifact parent, Set<? extends PathFragment> parentRelativePaths) {
+      final Artifact.SpecialArtifact parent, Set<? extends PathFragment> parentRelativePaths) {
     Preconditions.checkState(parent.isTreeArtifact(),
         "Given parent %s must be a TreeArtifact", parent);
 
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionRegistry.java b/src/main/java/com/google/devtools/build/lib/actions/ActionRegistry.java
index df064e8..03f58e0 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionRegistry.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionRegistry.java
@@ -23,9 +23,6 @@
    */
   void registerAction(ActionAnalysisMetadata... actions);
 
-  /**
-   * Get the (Label and BuildConfiguration) of the ConfiguredTarget ultimately responsible for all
-   * these actions.
-   */
-  ArtifactOwner getOwner();
+  /** Get the key of the ConfiguredTarget/Aspect ultimately responsible for all these actions. */
+  ActionLookupValue.ActionLookupKey getOwner();
 }
diff --git a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
index b3801e7..673143b 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/Artifact.java
@@ -277,7 +277,6 @@
   }
 
   /** An artifact corresponding to a file in the output tree, generated by an {@link Action}. */
-  @VisibleForTesting
   public static class DerivedArtifact extends Artifact {
     private final PathFragment rootRelativePath;
 
@@ -304,10 +303,11 @@
   }
 
   /**
-   * Returns the parent Artifact containing this Artifact. Artifacts without parents shall
-   * return null.
+   * Returns the parent Artifact containing this Artifact. Artifacts without parents shall return
+   * null.
    */
-  @Nullable public Artifact getParent() {
+  @Nullable
+  public SpecialArtifact getParent() {
     return null;
   }
 
@@ -360,7 +360,7 @@
    * Returns the artifact owner. May be null.
    */
   @Nullable public final Label getOwner() {
-    return owner.getLabel();
+    return getOwnerLabel();
   }
 
   /**
@@ -409,6 +409,9 @@
    * Returns true iff this is a source Artifact as determined by its path and root relationships.
    * Note that this will report all Artifacts in the output tree, including in the include symlink
    * tree, as non-source.
+   *
+   * <p>An {@link Artifact} is a {@link SourceArtifact} iff this returns true, and a {@link
+   * DerivedArtifact} otherwise.
    */
   @Override
   public final boolean isSourceArtifact() {
@@ -417,6 +420,8 @@
 
   /**
    * Returns true iff this is a middleman Artifact as determined by its root.
+   *
+   * <p>If true, this artifact is necessarily a {@link DerivedArtifact}.
    */
   public final boolean isMiddlemanArtifact() {
     return getRoot().isMiddlemanRoot();
@@ -424,6 +429,9 @@
 
   /**
    * Returns true iff this is a TreeArtifact representing a directory tree containing Artifacts.
+   *
+   * <p>if true, this artifact is necessarily a {@link SpecialArtifact} with type {@link
+   * SpecialArtifactType#TREE}.
    */
   public boolean isTreeArtifact() {
     return false;
@@ -431,19 +439,25 @@
 
   /**
    * Returns whether the artifact represents a Fileset.
+   *
+   * <p>if true, this artifact is necessarily a {@link SpecialArtifact} with type {@link
+   * SpecialArtifactType#FILESET}.
    */
   public boolean isFileset() {
     return false;
   }
 
+  /** The disjunction of {@link #isTreeArtifact} and {@link #isFileset}. */
   @Override
   public boolean isDirectory() {
     return isTreeArtifact() || isFileset();
   }
 
   /**
-   * Returns true iff metadata cache must return constant metadata for the
-   * given artifact.
+   * Returns true iff metadata cache must return constant metadata for the given artifact.
+   *
+   * <p>If true, this artifact is necessarily a {@link SpecialArtifact} with type {@link
+   * SpecialArtifactType#CONSTANT_METADATA}.
    */
   public boolean isConstantMetadata() {
     return false;
@@ -496,12 +510,11 @@
   /**
    * A special kind of artifact that either is a fileset or needs special metadata caching behavior.
    *
-   * <p>We subclass {@link Artifact} instead of storing the special attributes inside in order to
-   * save memory. The proportion of artifacts that are special is very small, and by not having to
-   * keep around the attribute for the rest we save some memory.
+   * <p>We subclass {@link DerivedArtifact} instead of storing the special attributes inside in
+   * order to save memory. The proportion of artifacts that are special is very small, and by not
+   * having to keep around the attribute for the rest we save some memory.
    */
   @Immutable
-  @VisibleForTesting
   @AutoCodec
   public static final class SpecialArtifact extends DerivedArtifact {
     private final SpecialArtifactType type;
@@ -535,7 +548,7 @@
 
     @Override
     @Nullable
-    public Artifact getParent() {
+    public SpecialArtifact getParent() {
       return null;
     }
 
@@ -559,14 +572,14 @@
    * to support action inputs and outputs that are unpredictable at analysis time. TreeFileArtifacts
    * should not be created directly by any rules at analysis time.
    *
-   * <p>We subclass {@link Artifact} instead of storing the extra fields directly inside in order to
-   * save memory. The proportion of TreeFileArtifacts is very small, and by not having to keep
-   * around the extra fields for the rest we save some memory.
+   * <p>We subclass {@link DerivedArtifact} instead of storing the extra fields directly inside in
+   * order to save memory. The proportion of TreeFileArtifacts is very small, and by not having to
+   * keep around the extra fields for the rest we save some memory.
    */
   @Immutable
   @AutoCodec
   public static final class TreeFileArtifact extends DerivedArtifact {
-    private final Artifact parentTreeArtifact;
+    private final SpecialArtifact parentTreeArtifact;
     private final PathFragment parentRelativePath;
 
     /**
@@ -575,7 +588,7 @@
      * of the parent TreeArtifact.
      */
     @VisibleForTesting
-    public TreeFileArtifact(Artifact parent, PathFragment parentRelativePath) {
+    public TreeFileArtifact(SpecialArtifact parent, PathFragment parentRelativePath) {
       this(parent, parentRelativePath, parent.getArtifactOwner());
     }
 
@@ -585,7 +598,7 @@
      */
     @AutoCodec.Instantiator
     TreeFileArtifact(
-        Artifact parentTreeArtifact, PathFragment parentRelativePath, ArtifactOwner owner) {
+        SpecialArtifact parentTreeArtifact, PathFragment parentRelativePath, ArtifactOwner owner) {
       super(
           parentTreeArtifact.getRoot(),
           parentTreeArtifact.getExecPath().getRelative(parentRelativePath),
@@ -608,7 +621,7 @@
     }
 
     @Override
-    public Artifact getParent() {
+    public SpecialArtifact getParent() {
       return parentTreeArtifact;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java b/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
index 25904dc..3cc5e3b 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ArtifactFactory.java
@@ -224,10 +224,11 @@
    * computed as {@code root.getRelative(rootRelativePath).relativeTo(root.execRoot)}.
    */
   // TODO(bazel-team): Don't allow root == execRootParent.
-  public Artifact getDerivedArtifact(
+  public Artifact.DerivedArtifact getDerivedArtifact(
       PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
     validatePath(rootRelativePath, root);
-    return getArtifact(root, root.getExecPath().getRelative(rootRelativePath), owner, null);
+    return (Artifact.DerivedArtifact)
+        getArtifact(root, root.getExecPath().getRelative(rootRelativePath), owner, null);
   }
 
   /**
@@ -238,11 +239,15 @@
    * <p>The root must be below the execRootParent, and the execPath of the resulting Artifact is
    * computed as {@code root.getRelative(rootRelativePath).relativeTo(root.execRoot)}.
    */
-  public Artifact getFilesetArtifact(
+  public Artifact.DerivedArtifact getFilesetArtifact(
       PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
     validatePath(rootRelativePath, root);
-    return getArtifact(
-        root, root.getExecPath().getRelative(rootRelativePath), owner, SpecialArtifactType.FILESET);
+    return (Artifact.DerivedArtifact)
+        getArtifact(
+            root,
+            root.getExecPath().getRelative(rootRelativePath),
+            owner,
+            SpecialArtifactType.FILESET);
   }
 
   /**
@@ -252,21 +257,26 @@
    * <p>The root must be below the execRootParent, and the execPath of the resulting Artifact is
    * computed as {@code root.getRelative(rootRelativePath).relativeTo(root.execRoot)}.
    */
-  public Artifact getTreeArtifact(
+  public Artifact.SpecialArtifact getTreeArtifact(
       PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
     validatePath(rootRelativePath, root);
-    return getArtifact(
-        root, root.getExecPath().getRelative(rootRelativePath), owner, SpecialArtifactType.TREE);
+    return (Artifact.SpecialArtifact)
+        getArtifact(
+            root,
+            root.getExecPath().getRelative(rootRelativePath),
+            owner,
+            SpecialArtifactType.TREE);
   }
 
-  public Artifact getConstantMetadataArtifact(
+  public Artifact.DerivedArtifact getConstantMetadataArtifact(
       PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
     validatePath(rootRelativePath, root);
-    return getArtifact(
-        root,
-        root.getExecPath().getRelative(rootRelativePath),
-        owner,
-        SpecialArtifactType.CONSTANT_METADATA);
+    return (Artifact.DerivedArtifact)
+        getArtifact(
+            root,
+            root.getExecPath().getRelative(rootRelativePath),
+            owner,
+            SpecialArtifactType.CONSTANT_METADATA);
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/actions/BasicActionLookupValue.java b/src/main/java/com/google/devtools/build/lib/actions/BasicActionLookupValue.java
index d99fbee..3b4a153 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/BasicActionLookupValue.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/BasicActionLookupValue.java
@@ -30,10 +30,6 @@
   protected final ImmutableList<ActionAnalysisMetadata> actions;
   @VisibleForSerialization protected final ImmutableMap<Artifact, Integer> generatingActionIndex;
 
-  protected BasicActionLookupValue(ActionAnalysisMetadata action) {
-    this(Actions.GeneratingActions.fromSingleAction(action), /*nonceVersion=*/ null);
-  }
-
   protected BasicActionLookupValue(
       ImmutableList<ActionAnalysisMetadata> actions,
       ImmutableMap<Artifact, Integer> generatingActionIndex,
diff --git a/src/main/java/com/google/devtools/build/lib/actions/MiddlemanFactory.java b/src/main/java/com/google/devtools/build/lib/actions/MiddlemanFactory.java
index 43a9e37..e726a31 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/MiddlemanFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/MiddlemanFactory.java
@@ -181,7 +181,7 @@
    * <p>Note: there's no need to synchronize this method; the only use of a field is via a call to
    * another synchronized method (getArtifact()).
    */
-  public Artifact createMiddlemanAllowMultiple(
+  public Artifact.DerivedArtifact createMiddlemanAllowMultiple(
       ActionRegistry registry,
       ActionOwner owner,
       PathFragment packageDirectory,
@@ -192,19 +192,19 @@
     PathFragment stampName =
         PathFragment.create("_middlemen/" + (purpose.startsWith(escapedPackageDirectory)
                              ? purpose : (escapedPackageDirectory + purpose)));
-    Artifact stampFile = artifactFactory.getDerivedArtifact(stampName, middlemanDir,
-        actionRegistry.getOwner());
+    Artifact.DerivedArtifact stampFile =
+        artifactFactory.getDerivedArtifact(stampName, middlemanDir, actionRegistry.getOwner());
     MiddlemanAction.create(
         registry, owner, inputs, stampFile, purpose, MiddlemanType.AGGREGATING_MIDDLEMAN);
     return stampFile;
   }
 
-  private Artifact getStampFileArtifact(
+  private Artifact.DerivedArtifact getStampFileArtifact(
       String middlemanName, String purpose, ArtifactRoot middlemanDir) {
     String escapedFilename = Actions.escapedPath(middlemanName);
     PathFragment stampName = PathFragment.create("_middlemen/" + escapedFilename + "-" + purpose);
-    Artifact stampFile = artifactFactory.getDerivedArtifact(stampName, middlemanDir,
-        actionRegistry.getOwner());
+    Artifact.DerivedArtifact stampFile =
+        artifactFactory.getDerivedArtifact(stampName, middlemanDir, actionRegistry.getOwner());
     return stampFile;
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataInjector.java b/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataInjector.java
index 7f6abf6..78ed389 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataInjector.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataInjector.java
@@ -41,7 +41,8 @@
    * @param children the metadata of the files stored in the directory. The paths must be relative
    *     to the path of {@code output}.
    */
-  void injectRemoteDirectory(Artifact output, Map<PathFragment, RemoteFileArtifactValue> children);
+  void injectRemoteDirectory(
+      Artifact.SpecialArtifact output, Map<PathFragment, RemoteFileArtifactValue> children);
 
   /**
    * Marks an {@link Artifact} as intentionally omitted.
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
index a23e5fa..2f69670 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/AnalysisEnvironment.java
@@ -64,7 +64,7 @@
    * artifacts generated by two different rules to clash. To avoid this, use the artifact creation
    * method on {@link RuleContext} mentioned above.
    */
-  Artifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root);
+  Artifact.DerivedArtifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root);
 
   /**
    * Returns an artifact for the derived file {@code rootRelativePath} whose changes do not cause a
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
index 1556fc2..9e82277 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java
@@ -658,8 +658,8 @@
   }
 
   /**
-   * Returns a list of actions from 'provider' that were registered by an aspect from
-   * 'aspectClasses'. All actions in 'provider' are considered - both direct and transitive.
+   * Returns a list of artifacts from 'provider' that were registered by an aspect from
+   * 'aspectClasses'. All artifacts in 'provider' are considered - both direct and transitive.
    */
   private ImmutableList<Artifact> filterTransitiveExtraActions(
       ExtraActionArtifactsProvider provider, Set<AspectClass> aspectClasses) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
index a495377..d66dee7 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/CachingAnalysisEnvironment.java
@@ -20,10 +20,10 @@
 import com.google.common.collect.Lists;
 import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
 import com.google.devtools.build.lib.actions.ActionKeyContext;
+import com.google.devtools.build.lib.actions.ActionLookupValue;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
 import com.google.devtools.build.lib.actions.ArtifactFactory;
-import com.google.devtools.build.lib.actions.ArtifactOwner;
 import com.google.devtools.build.lib.actions.ArtifactRoot;
 import com.google.devtools.build.lib.actions.MiddlemanFactory;
 import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoCollection;
@@ -60,7 +60,7 @@
 public class CachingAnalysisEnvironment implements AnalysisEnvironment {
   private final ArtifactFactory artifactFactory;
 
-  private final ArtifactOwner owner;
+  private final ActionLookupValue.ActionLookupKey owner;
   /**
    * If this is the system analysis environment, then errors and warnings are directly reported
    * to the global reporter, rather than stored, i.e., we don't track here whether there are any
@@ -87,7 +87,7 @@
   public CachingAnalysisEnvironment(
       ArtifactFactory artifactFactory,
       ActionKeyContext actionKeyContext,
-      ArtifactOwner owner,
+      ActionLookupValue.ActionLookupKey owner,
       boolean isSystemEnv,
       boolean extendedSanityChecks,
       boolean allowAnalysisFailures,
@@ -240,7 +240,8 @@
    * sealed (disable()). For performance reasons we only track the originating stacktrace when
    * running with --experimental_extended_sanity_checks.
    */
-  private Artifact trackArtifactAndOrigin(Artifact a, @Nullable Throwable e) {
+  private Artifact.DerivedArtifact trackArtifactAndOrigin(
+      Artifact.DerivedArtifact a, @Nullable Throwable e) {
     if ((e != null) && !artifacts.containsKey(a)) {
       StringWriter sw = new StringWriter();
       e.printStackTrace(new PrintWriter(sw));
@@ -252,7 +253,8 @@
   }
 
   @Override
-  public Artifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
+  public Artifact.DerivedArtifact getDerivedArtifact(
+      PathFragment rootRelativePath, ArtifactRoot root) {
     Preconditions.checkState(enabled);
     return trackArtifactAndOrigin(
         artifactFactory.getDerivedArtifact(rootRelativePath, root, getOwner()),
@@ -269,7 +271,8 @@
   }
 
   @Override
-  public Artifact getFilesetArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
+  public Artifact.DerivedArtifact getFilesetArtifact(
+      PathFragment rootRelativePath, ArtifactRoot root) {
     Preconditions.checkState(enabled);
     return trackArtifactAndOrigin(
         artifactFactory.getFilesetArtifact(rootRelativePath, root, getOwner()),
@@ -351,7 +354,7 @@
   }
 
   @Override
-  public ArtifactOwner getOwner() {
+  public ActionLookupValue.ActionLookupKey getOwner() {
     return owner;
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionArtifactsProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionArtifactsProvider.java
index 1dd90cf..eedcc19 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionArtifactsProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionArtifactsProvider.java
@@ -28,12 +28,12 @@
 public final class ExtraActionArtifactsProvider implements TransitiveInfoProvider {
   public static final ExtraActionArtifactsProvider EMPTY =
       new ExtraActionArtifactsProvider(
-          NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER),
-          NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER));
+          NestedSetBuilder.emptySet(Order.STABLE_ORDER),
+          NestedSetBuilder.emptySet(Order.STABLE_ORDER));
 
   public static ExtraActionArtifactsProvider create(
-      NestedSet<Artifact> extraActionArtifacts,
-      NestedSet<Artifact> transitiveExtraActionArtifacts) {
+      NestedSet<Artifact.DerivedArtifact> extraActionArtifacts,
+      NestedSet<Artifact.DerivedArtifact> transitiveExtraActionArtifacts) {
     if (extraActionArtifacts.isEmpty() && transitiveExtraActionArtifacts.isEmpty()) {
       return EMPTY;
     }
@@ -42,8 +42,9 @@
 
   public static ExtraActionArtifactsProvider merge(
       Iterable<ExtraActionArtifactsProvider> providers) {
-    NestedSetBuilder<Artifact> artifacts = NestedSetBuilder.stableOrder();
-    NestedSetBuilder<Artifact> transitiveExtraActionArtifacts = NestedSetBuilder.stableOrder();
+    NestedSetBuilder<Artifact.DerivedArtifact> artifacts = NestedSetBuilder.stableOrder();
+    NestedSetBuilder<Artifact.DerivedArtifact> transitiveExtraActionArtifacts =
+        NestedSetBuilder.stableOrder();
 
     for (ExtraActionArtifactsProvider provider : providers) {
       artifacts.addTransitive(provider.getExtraActionArtifacts());
@@ -54,28 +55,27 @@
   }
 
   /** The outputs of the extra actions associated with this target. */
-  private final NestedSet<Artifact> extraActionArtifacts;
-  private final NestedSet<Artifact> transitiveExtraActionArtifacts;
+  private final NestedSet<Artifact.DerivedArtifact> extraActionArtifacts;
+
+  private final NestedSet<Artifact.DerivedArtifact> transitiveExtraActionArtifacts;
 
   /** Use {@link #create} instead. */
   @AutoCodec.Instantiator
   @VisibleForSerialization
   ExtraActionArtifactsProvider(
-      NestedSet<Artifact> extraActionArtifacts,
-      NestedSet<Artifact> transitiveExtraActionArtifacts) {
+      NestedSet<Artifact.DerivedArtifact> extraActionArtifacts,
+      NestedSet<Artifact.DerivedArtifact> transitiveExtraActionArtifacts) {
     this.extraActionArtifacts = extraActionArtifacts;
     this.transitiveExtraActionArtifacts = transitiveExtraActionArtifacts;
   }
 
-  /**
-   * The outputs of the extra actions associated with this target.
-   */
-  public NestedSet<Artifact> getExtraActionArtifacts() {
+  /** The outputs of the extra actions associated with this target. */
+  public NestedSet<Artifact.DerivedArtifact> getExtraActionArtifacts() {
     return extraActionArtifacts;
   }
 
   /** The outputs of the extra actions in the whole transitive closure. */
-  public NestedSet<Artifact> getTransitiveExtraActionArtifacts() {
+  public NestedSet<Artifact.DerivedArtifact> getTransitiveExtraActionArtifacts() {
     return transitiveExtraActionArtifacts;
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionUtils.java b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionUtils.java
index e43ba87..a736555 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionUtils.java
@@ -48,8 +48,8 @@
       return ExtraActionArtifactsProvider.EMPTY;
     }
 
-    ImmutableList<Artifact> extraActionArtifacts = ImmutableList.of();
-    NestedSetBuilder<Artifact> builder = NestedSetBuilder.stableOrder();
+    ImmutableList<Artifact.DerivedArtifact> extraActionArtifacts = ImmutableList.of();
+    NestedSetBuilder<Artifact.DerivedArtifact> builder = NestedSetBuilder.stableOrder();
 
     List<Label> actionListenerLabels = configuration.getActionListeners();
     if (!actionListenerLabels.isEmpty()
@@ -79,7 +79,9 @@
     }
 
     return ExtraActionArtifactsProvider.create(
-        NestedSetBuilder.<Artifact>stableOrder().addAll(extraActionArtifacts).build(),
+        NestedSetBuilder.<Artifact.DerivedArtifact>stableOrder()
+            .addAll(extraActionArtifacts)
+            .build(),
         builder.build());
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionsVisitor.java b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionsVisitor.java
index f6f41de..922bd72 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionsVisitor.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ExtraActionsVisitor.java
@@ -33,7 +33,7 @@
 final class ExtraActionsVisitor extends ActionGraphVisitor {
   private final RuleContext ruleContext;
   private final Multimap<String, ExtraActionSpec> mnemonicToExtraActionMap;
-  private final List<Artifact> extraArtifacts;
+  private final List<Artifact.DerivedArtifact> extraArtifacts;
 
   /** Creates a new visitor for the extra actions associated with the given target. */
   public ExtraActionsVisitor(RuleContext ruleContext,
@@ -63,8 +63,8 @@
   }
 
   /** Retrieves the collected artifacts since this method was last called and clears the list. */
-  public ImmutableList<Artifact> getAndResetExtraArtifacts() {
-    ImmutableList<Artifact> collected = ImmutableList.copyOf(extraArtifacts);
+  ImmutableList<Artifact.DerivedArtifact> getAndResetExtraArtifacts() {
+    ImmutableList<Artifact.DerivedArtifact> collected = ImmutableList.copyOf(extraArtifacts);
     extraArtifacts.clear();
     return collected;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
index 76e3b42..7779744 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java
@@ -40,7 +40,6 @@
 import com.google.devtools.build.lib.actions.ActionRegistry;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
-import com.google.devtools.build.lib.actions.ArtifactOwner;
 import com.google.devtools.build.lib.actions.ArtifactRoot;
 import com.google.devtools.build.lib.analysis.AliasProvider.TargetMode;
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.PrerequisiteValidator;
@@ -499,7 +498,7 @@
   }
 
   @Override
-  public ArtifactOwner getOwner() {
+  public ActionLookupValue.ActionLookupKey getOwner() {
     return getAnalysisEnvironment().getOwner();
   }
 
@@ -657,22 +656,6 @@
    * Creates an artifact in a directory that is unique to the package that contains the rule, thus
    * guaranteeing that it never clashes with artifacts created by rules in other packages.
    */
-  public Artifact getPackageRelativeArtifact(String relative, ArtifactRoot root) {
-    return getPackageRelativeArtifact(PathFragment.create(relative), root);
-  }
-
-  /**
-   * Creates an artifact in a directory that is unique to the package that contains the rule, thus
-   * guaranteeing that it never clashes with artifacts created by rules in other packages.
-   */
-  public Artifact getPackageRelativeTreeArtifact(String relative, ArtifactRoot root) {
-    return getPackageRelativeTreeArtifact(PathFragment.create(relative), root);
-  }
-
-  /**
-   * Creates an artifact in a directory that is unique to the package that contains the rule, thus
-   * guaranteeing that it never clashes with artifacts created by rules in other packages.
-   */
   public Artifact getBinArtifact(String relative) {
     return getBinArtifact(PathFragment.create(relative));
   }
@@ -701,10 +684,19 @@
   }
 
   @Override
-  public Artifact getPackageRelativeArtifact(PathFragment relative, ArtifactRoot root) {
+  public Artifact.DerivedArtifact getPackageRelativeArtifact(
+      PathFragment relative, ArtifactRoot root) {
     return getDerivedArtifact(getPackageDirectory().getRelative(relative), root);
   }
 
+  /**
+   * Creates an artifact in a directory that is unique to the package that contains the rule, thus
+   * guaranteeing that it never clashes with artifacts created by rules in other packages.
+   */
+  public Artifact getPackageRelativeArtifact(String relative, ArtifactRoot root) {
+    return getPackageRelativeArtifact(PathFragment.create(relative), root);
+  }
+
   @Override
   public PathFragment getPackageDirectory() {
     return getLabel().getPackageIdentifier().getSourceRoot();
@@ -718,7 +710,8 @@
    * method.
    */
   @Override
-  public Artifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
+  public Artifact.DerivedArtifact getDerivedArtifact(
+      PathFragment rootRelativePath, ArtifactRoot root) {
     Preconditions.checkState(rootRelativePath.startsWith(getPackageDirectory()),
         "Output artifact '%s' not under package directory '%s' for target '%s'",
         rootRelativePath, getPackageDirectory(), getLabel());
@@ -1421,7 +1414,8 @@
   }
 
   @Override
-  public final Artifact getRelatedArtifact(PathFragment pathFragment, String extension) {
+  public final Artifact.DerivedArtifact getRelatedArtifact(
+      PathFragment pathFragment, String extension) {
     PathFragment file = FileSystemUtils.replaceExtension(pathFragment, extension);
     return getDerivedArtifact(file, getConfiguration().getBinDirectory(rule.getRepository()));
   }
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
index ff15303..4f1d5c5 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TopLevelArtifactHelper.java
@@ -156,7 +156,7 @@
   }
 
   static void addArtifactsWithOwnerLabel(
-      Iterable<Artifact> artifacts,
+      Iterable<? extends Artifact> artifacts,
       @Nullable RegexFilter filter,
       Label ownerLabel,
       ArtifactsToOwnerLabels.Builder artifactsToOwnerLabelsBuilder) {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java
index 92d25f7..c9e600b 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/ActionConstructionContext.java
@@ -63,7 +63,7 @@
    * thus ensuring that it doesn't clash with other artifacts generated by other rules using this
    * method.
    */
-  Artifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root);
+  Artifact.DerivedArtifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root);
 
   /**
    * Creates a TreeArtifact under a given root with the given root-relative path.
@@ -94,7 +94,7 @@
    * Returns an artifact with a given file extension. All other path components are the same as in
    * {@code pathFragment}.
    */
-  Artifact getRelatedArtifact(PathFragment pathFragment, String extension);
+  Artifact.DerivedArtifact getRelatedArtifact(PathFragment pathFragment, String extension);
 
   /**
    * Creates an artifact in a directory that is unique to the rule, thus guaranteeing that it never
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
index 4c33832..fcc15da 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java
@@ -191,7 +191,7 @@
       ActionOwner owner,
       Iterable<Artifact> tools,
       Iterable<Artifact> inputs,
-      Iterable<Artifact> outputs,
+      Iterable<? extends Artifact> outputs,
       Artifact primaryOutput,
       ResourceSet resourceSet,
       CommandLines commandLines,
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraAction.java b/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraAction.java
index 7f7396b..4762ba7 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraAction.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraAction.java
@@ -67,7 +67,7 @@
   ExtraAction(
       ImmutableSet<Artifact> extraActionInputs,
       RunfilesSupplier runfilesSupplier,
-      Collection<Artifact> outputs,
+      Collection<Artifact.DerivedArtifact> outputs,
       Action shadowedAction,
       boolean createDummyOutput,
       CommandLine argv,
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraActionSpec.java b/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraActionSpec.java
index 9e4cdd2..5c17eff 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraActionSpec.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/extra/ExtraActionSpec.java
@@ -78,12 +78,11 @@
     return label;
   }
 
-  /**
-   * Adds an extra_action to the action graph based on the action to shadow.
-   */
-  public Collection<Artifact> addExtraAction(RuleContext owningRule, Action actionToShadow) {
-    Collection<Artifact> extraActionOutputs = new LinkedHashSet<>();
-    Collection<Artifact> protoOutputs = new ArrayList<>();
+  /** Adds an extra_action to the action graph based on the action to shadow. */
+  public Collection<Artifact.DerivedArtifact> addExtraAction(
+      RuleContext owningRule, Action actionToShadow) {
+    Collection<Artifact.DerivedArtifact> extraActionOutputs = new LinkedHashSet<>();
+    Collection<Artifact.DerivedArtifact> protoOutputs = new ArrayList<>();
     NestedSetBuilder<Artifact> extraActionInputs = NestedSetBuilder.stableOrder();
 
     Label ownerLabel = owningRule.getLabel();
@@ -111,8 +110,8 @@
 
     // We generate a file containing a protocol buffer describing the action that is being shadowed.
     // It is up to each action being shadowed to decide what contents to store here.
-    Artifact extraActionInfoFile = getExtraActionOutputArtifact(
-        owningRule, actionToShadow, "$(ACTION_ID).xa");
+    Artifact.DerivedArtifact extraActionInfoFile =
+        getExtraActionOutputArtifact(owningRule, actionToShadow, "$(ACTION_ID).xa");
     owningRule.registerAction(new ExtraActionInfoFileWriteAction(
         actionToShadow.getOwner(), extraActionInfoFile, actionToShadow));
     extraActionInputs.add(extraActionInfoFile);
@@ -152,7 +151,10 @@
             commandMessage,
             label.getName()));
 
-    return ImmutableSet.<Artifact>builder().addAll(extraActionOutputs).addAll(protoOutputs).build();
+    return ImmutableSet.<Artifact.DerivedArtifact>builder()
+        .addAll(extraActionOutputs)
+        .addAll(protoOutputs)
+        .build();
   }
 
   /**
@@ -177,22 +179,19 @@
   }
 
   /**
-   * Creates an output artifact for the extra_action based on the output_template.
-   * The path will be in the following form:
-   * <output dir>/<target-configuration-specific-path>/extra_actions/<extra_action_label>/ +
-   *   <configured_target_label>/<expanded_template>
+   * Creates an output artifact for the extra_action based on the output_template. The path will be
+   * in the following form: <output
+   * dir>/<target-configuration-specific-path>/extra_actions/<extra_action_label>/ +
+   * <configured_target_label>/<expanded_template>
    *
-   * The template can use the following variables:
-   * $(ACTION_ID): a unique id for the extra_action.
+   * <p>The template can use the following variables: $(ACTION_ID): a unique id for the
+   * extra_action.
    *
-   *  Sample:
-   *    extra_action: foo/bar:extra
-   *    template: $(ACTION_ID).analysis
-   *    target: foo/bar:main
-   *    expands to: output/configuration/extra_actions/\
-   *      foo/bar/extra/foo/bar/4683026f7ac1dd1a873ccc8c3d764132.analysis
+   * <p>Sample: extra_action: foo/bar:extra template: $(ACTION_ID).analysis target: foo/bar:main
+   * expands to: output/configuration/extra_actions/\
+   * foo/bar/extra/foo/bar/4683026f7ac1dd1a873ccc8c3d764132.analysis
    */
-  private Artifact getExtraActionOutputArtifact(
+  private Artifact.DerivedArtifact getExtraActionOutputArtifact(
       RuleContext ruleContext, Action action, String template) {
     String actionId =
         getActionId(ruleContext.getActionKeyContext(), ruleContext.getActionOwner(), action);
@@ -203,7 +202,7 @@
     return getRootRelativePath(template, ruleContext);
   }
 
-  private Artifact getRootRelativePath(String template, RuleContext ruleContext) {
+  private Artifact.DerivedArtifact getRootRelativePath(String template, RuleContext ruleContext) {
     PathFragment extraActionPackageFragment = label.getPackageIdentifier().getSourceRoot();
     PathFragment extraActionPrefix = extraActionPackageFragment.getRelative(label.getName());
     PathFragment rootRelativePath = PathFragment.create("extra_actions")
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
index 35d4bbe..df30e48 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/skylark/SkylarkActionFactory.java
@@ -22,9 +22,9 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.actions.Action;
 import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
+import com.google.devtools.build.lib.actions.ActionLookupValue;
 import com.google.devtools.build.lib.actions.ActionRegistry;
 import com.google.devtools.build.lib.actions.Artifact;
-import com.google.devtools.build.lib.actions.ArtifactOwner;
 import com.google.devtools.build.lib.actions.ArtifactRoot;
 import com.google.devtools.build.lib.actions.CommandLine;
 import com.google.devtools.build.lib.actions.CommandLineExpansionException;
@@ -117,7 +117,7 @@
       }
 
       @Override
-      public ArtifactOwner getOwner() {
+      public ActionLookupValue.ActionLookupKey getOwner() {
         return skylarkActionFactory
             .getActionConstructionContext()
             .getAnalysisEnvironment()
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java
index 6e5a58e..c702722 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestActionBuilder.java
@@ -321,7 +321,8 @@
 
     Iterable<Artifact> inputs = inputsBuilder.build();
     int shardRuns = (shards > 0 ? shards : 1);
-    List<Artifact> results = Lists.newArrayListWithCapacity(runsPerTest * shardRuns);
+    List<Artifact.DerivedArtifact> results =
+        Lists.newArrayListWithCapacity(runsPerTest * shardRuns);
     ImmutableList.Builder<Artifact> coverageArtifacts = ImmutableList.builder();
 
     for (int run = 0; run < runsPerTest; run++) {
@@ -337,14 +338,14 @@
           testRunDir += PathFragment.SEPARATOR_CHAR;
           shardRunDir = shardRunDir.isEmpty() ? testRunDir : shardRunDir + "_" + testRunDir;
         }
-        Artifact testLog =
+        Artifact.DerivedArtifact testLog =
             ruleContext.getPackageRelativeArtifact(
                 targetName.getRelative(shardRunDir + "test.log"), root);
-        Artifact cacheStatus =
+        Artifact.DerivedArtifact cacheStatus =
             ruleContext.getPackageRelativeArtifact(
                 targetName.getRelative(shardRunDir + "test.cache_status"), root);
 
-        Artifact coverageArtifact = null;
+        Artifact.DerivedArtifact coverageArtifact = null;
         if (collectCodeCoverage) {
           coverageArtifact = ruleContext.getPackageRelativeArtifact(
               targetName.getRelative(shardRunDir + "coverage.dat"), root);
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestProvider.java
index f356deb..cc64055 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestProvider.java
@@ -58,7 +58,8 @@
    * @param target the configured target. Should belong to a test rule.
    * @return the test status artifacts
    */
-  public static ImmutableList<Artifact> getTestStatusArtifacts(TransitiveInfoCollection target) {
+  public static ImmutableList<Artifact.DerivedArtifact> getTestStatusArtifacts(
+      TransitiveInfoCollection target) {
     return target.getProvider(TestProvider.class).getTestParams().getTestStatusArtifacts();
   }
 
@@ -70,16 +71,20 @@
     private final int shards;
     private final TestTimeout timeout;
     private final String testRuleClass;
-    private final ImmutableList<Artifact> testStatusArtifacts;
+    private final ImmutableList<Artifact.DerivedArtifact> testStatusArtifacts;
     private final ImmutableList<Artifact> coverageArtifacts;
     private final FilesToRunProvider coverageReportGenerator;
 
     /**
-     * Don't call this directly. Instead use
-     * {@link com.google.devtools.build.lib.analysis.test.TestActionBuilder}.
+     * Don't call this directly. Instead use {@link
+     * com.google.devtools.build.lib.analysis.test.TestActionBuilder}.
      */
-    TestParams(int runs, int shards, TestTimeout timeout, String testRuleClass,
-        ImmutableList<Artifact> testStatusArtifacts,
+    TestParams(
+        int runs,
+        int shards,
+        TestTimeout timeout,
+        String testRuleClass,
+        ImmutableList<Artifact.DerivedArtifact> testStatusArtifacts,
         ImmutableList<Artifact> coverageArtifacts,
         FilesToRunProvider coverageReportGenerator) {
       this.runs = runs;
@@ -123,7 +128,7 @@
      * Returns a list of test status artifacts that represent serialized test status protobuffers
      * produced by testing this target.
      */
-    public ImmutableList<Artifact> getTestStatusArtifacts() {
+    public ImmutableList<Artifact.DerivedArtifact> getTestStatusArtifacts() {
       return testStatusArtifacts;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java b/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java
index 0adb4e4..1e9b650 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/AbstractRemoteActionCache.java
@@ -463,7 +463,8 @@
                 /* locationIndex= */ 1);
         childMetadata.put(p, r);
       }
-      metadataInjector.injectRemoteDirectory(output, childMetadata.build());
+      metadataInjector.injectRemoteDirectory(
+          (Artifact.SpecialArtifact) output, childMetadata.build());
     } else {
       FileMetadata outputMetadata = metadata.file(execRoot.getRelative(output.getExecPathString()));
       if (outputMetadata == null) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAssets.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAssets.java
index 5978cc3..6f8da8a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAssets.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidAssets.java
@@ -84,8 +84,7 @@
 
     for (TransitiveInfoCollection target : assetTargets) {
       for (Artifact file : target.getProvider(FileProvider.class).getFilesToBuild()) {
-        PathFragment packageFragment =
-            file.getArtifactOwner().getLabel().getPackageIdentifier().getSourceRoot();
+        PathFragment packageFragment = file.getOwnerLabel().getPackageIdentifier().getSourceRoot();
         PathFragment packageRelativePath = file.getRootRelativePath().relativeTo(packageFragment);
         if (packageRelativePath.startsWith(assetsDir)) {
           PathFragment relativePath = packageRelativePath.relativeTo(assetsDir);
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResources.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResources.java
index 9af441e..7f09d2e 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResources.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResources.java
@@ -225,14 +225,13 @@
               "'%s' (generated by '%s') is not in the same directory '%s' (derived from %s)."
                   + " All resources must share a common directory.",
               file.getRootRelativePath(),
-              file.getArtifactOwner().getLabel(),
+              file.getOwnerLabel(),
               lastResourceDir,
               lastFile.getRootRelativePath()));
       throw new RuleErrorException();
     }
 
-    PathFragment packageFragment =
-        file.getArtifactOwner().getLabel().getPackageIdentifier().getSourceRoot();
+    PathFragment packageFragment = file.getOwnerLabel().getPackageIdentifier().getSourceRoot();
     PathFragment packageRelativePath = file.getRootRelativePath().relativeTo(packageFragment);
     try {
       PathFragment path = file.getExecPath();
@@ -246,7 +245,7 @@
           String.format(
               "'%s' (generated by '%s') is not under the directory '%s' (derived from %s).",
               file.getRootRelativePath(),
-              file.getArtifactOwner().getLabel(),
+              file.getOwnerLabel(),
               packageRelativePath,
               file.getRootRelativePath()));
       throw new RuleErrorException();
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java
index ab44ccc..ade3695 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationContext.java
@@ -293,7 +293,7 @@
   /** Simple container for a collection of headers and corresponding modules. */
   public static class HeadersAndModules {
     public final Collection<Artifact> headers;
-    public final Collection<Artifact> modules;
+    public final Collection<Artifact.DerivedArtifact> modules;
 
     HeadersAndModules(int expectedHeaderCount) {
       headers = CompactHashSet.createWithExpectedSize(expectedHeaderCount);
@@ -310,7 +310,7 @@
     HeadersAndModules result = new HeadersAndModules(includes.size());
     for (int c = 0; c < transitiveHeaderInfoList.size(); c++) {
       HeaderInfo transitiveHeaderInfo = transitiveHeaderInfoList.get(c);
-      Artifact module = transitiveHeaderInfo.getModule(usePic);
+      Artifact.DerivedArtifact module = transitiveHeaderInfo.getModule(usePic);
       // Not using range-based for loops here as often there is exactly one element in this list
       // and the amount of garbage created by SingletonImmutableList.iterator() is significant.
       for (int i = 0; i < transitiveHeaderInfo.modularHeaders.size(); i++) {
@@ -726,16 +726,17 @@
      *
      * @param headerModule The .pcm file generated for this library.
      */
-    public Builder setHeaderModule(Artifact headerModule) {
+    Builder setHeaderModule(Artifact.DerivedArtifact headerModule) {
       this.headerInfoBuilder.setHeaderModule(headerModule);
       return this;
     }
 
     /**
      * Sets the C++ header module in pic mode.
+     *
      * @param picHeaderModule The .pic.pcm file generated for this library.
      */
-    public Builder setPicHeaderModule(Artifact picHeaderModule) {
+    Builder setPicHeaderModule(Artifact.DerivedArtifact picHeaderModule) {
       this.headerInfoBuilder.setPicHeaderModule(picHeaderModule);
       return this;
     }
@@ -843,8 +844,9 @@
      * The modules built for this context. If null, then no module is being compiled for this
      * context.
      */
-    private final Artifact headerModule;
-    private final Artifact picHeaderModule;
+    private final Artifact.DerivedArtifact headerModule;
+
+    private final Artifact.DerivedArtifact picHeaderModule;
 
     /** All header files that are compiled into this module. */
     private final ImmutableList<Artifact> modularHeaders;
@@ -852,9 +854,9 @@
     /** All header files that are contained in this module. */
     private final ImmutableList<Artifact> textualHeaders;
 
-    public HeaderInfo(
-        Artifact headerModule,
-        Artifact picHeaderModule,
+    HeaderInfo(
+        Artifact.DerivedArtifact headerModule,
+        Artifact.DerivedArtifact picHeaderModule,
         ImmutableList<Artifact> modularHeaders,
         ImmutableList<Artifact> textualHeaders) {
       this.headerModule = headerModule;
@@ -863,7 +865,7 @@
       this.textualHeaders = textualHeaders;
     }
 
-    public Artifact getModule(boolean pic) {
+    Artifact.DerivedArtifact getModule(boolean pic) {
       return pic ? picHeaderModule : headerModule;
     }
 
@@ -871,17 +873,17 @@
      * Builder class for {@link HeaderInfo}.
      */
     public static class Builder {
-      private Artifact headerModule = null;
-      private Artifact picHeaderModule = null;
+      private Artifact.DerivedArtifact headerModule = null;
+      private Artifact.DerivedArtifact picHeaderModule = null;
       private final Set<Artifact> modularHeaders = new HashSet<>();
       private final Set<Artifact> textualHeaders = new HashSet<>();
 
-      public Builder setHeaderModule(Artifact headerModule) {
+      Builder setHeaderModule(Artifact.DerivedArtifact headerModule) {
         this.headerModule = headerModule;
         return this;
       }
 
-      public Builder setPicHeaderModule(Artifact headerModule) {
+      Builder setPicHeaderModule(Artifact.DerivedArtifact headerModule) {
         this.picHeaderModule = headerModule;
         return this;
       }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
index e65f09b..dda017a 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CcCompilationHelper.java
@@ -1121,7 +1121,7 @@
   }
 
   /** @return the no-PIC header module artifact for the current target. */
-  private Artifact getHeaderModule(Artifact moduleMapArtifact) {
+  private Artifact.DerivedArtifact getHeaderModule(Artifact moduleMapArtifact) {
     PathFragment objectDir = CppHelper.getObjDirectory(label);
     PathFragment outputName =
         objectDir.getRelative(moduleMapArtifact.getRootRelativePath().getBaseName());
@@ -1129,7 +1129,7 @@
   }
 
   /** @return the pic header module artifact for the current target. */
-  private Artifact getPicHeaderModule(Artifact moduleMapArtifact) {
+  private Artifact.DerivedArtifact getPicHeaderModule(Artifact moduleMapArtifact) {
     PathFragment objectDir = CppHelper.getObjDirectory(label);
     PathFragment outputName =
         objectDir.getRelative(moduleMapArtifact.getRootRelativePath().getBaseName());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
index d317dfd..43679ed 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileAction.java
@@ -167,10 +167,10 @@
   private Iterable<Artifact> additionalInputs = null;
 
   /**
-   * Used only during input discovery, when input discovery requires other actions
-   * to be executed first.
+   * Used only during input discovery, when input discovery requires other actions to be executed
+   * first.
    */
-  private Collection<Artifact> usedModules = null;
+  private Collection<Artifact.DerivedArtifact> usedModules = null;
 
   /**
    * This field is set only for C++ module compiles (compiling .cppmap files into .pcm files). It
@@ -549,7 +549,7 @@
       return additionalInputs;
     }
 
-    Map<Artifact, NestedSet<Artifact>> transitivelyUsedModules =
+    Map<Artifact, NestedSet<? extends Artifact>> transitivelyUsedModules =
         computeTransitivelyUsedModules(
             actionExecutionContext.getEnvironmentForDiscoveringInputs(), usedModules);
     if (transitivelyUsedModules == null) {
@@ -560,7 +560,7 @@
     // used modules. Combining the NestedSets of transitive deps of the top-level modules also
     // gives us an effective way to compute and store discoveredModules.
     Set<Artifact> topLevel = new LinkedHashSet<>(usedModules);
-    for (NestedSet<Artifact> transitive : transitivelyUsedModules.values()) {
+    for (NestedSet<? extends Artifact> transitive : transitivelyUsedModules.values()) {
       // It is better to iterate over each nested set here instead of creating a joint one and
       // iterating over it, as this makes use of NestedSet's memoization (each of them has likely
       // been iterated over before). Don't use Set.removeAll() here as that iterates over the
@@ -1562,13 +1562,14 @@
   /**
    * For the given {@code usedModules}, looks up modules discovered by their generating actions.
    *
-   * <p>The returned value only contains a map from elements of {@code usedModules} to the
-   * {@link #discoveredModules} required to use them. If dependent actions have not been executed
-   * yet (and thus {@link #discoveredModules} aren't known yet, returns null.
+   * <p>The returned value only contains a map from elements of {@code usedModules} to the {@link
+   * #discoveredModules} required to use them. If dependent actions have not been executed yet (and
+   * thus {@link #discoveredModules} aren't known yet, returns null.
    */
   @Nullable
-  private static Map<Artifact, NestedSet<Artifact>> computeTransitivelyUsedModules(
-      SkyFunction.Environment env, Collection<Artifact> usedModules) throws InterruptedException {
+  private static Map<Artifact, NestedSet<? extends Artifact>> computeTransitivelyUsedModules(
+      SkyFunction.Environment env, Collection<Artifact.DerivedArtifact> usedModules)
+      throws InterruptedException {
     // ActionLookupKey → ActionLookupValue
     Map<SkyKey, SkyValue> actionLookupValues =
         env.getValues(
@@ -1595,7 +1596,7 @@
     if (env.valuesMissing()) {
       return null;
     }
-    ImmutableMap.Builder<Artifact, NestedSet<Artifact>> transitivelyUsedModules =
+    ImmutableMap.Builder<Artifact, NestedSet<? extends Artifact>> transitivelyUsedModules =
         ImmutableMap.builderWithExpectedSize(usedModules.size());
     int pos = 0;
     for (Artifact module : usedModules) {
diff --git a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionTemplate.java b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionTemplate.java
index 353f70a..de30e93 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionTemplate.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/cpp/CppCompileActionTemplate.java
@@ -43,8 +43,8 @@
     implements ActionTemplate<CppCompileAction> {
   private final CppCompileActionBuilder cppCompileActionBuilder;
   private final Artifact sourceTreeArtifact;
-  private final Artifact outputTreeArtifact;
-  private final Artifact dotdTreeArtifact;
+  private final Artifact.SpecialArtifact outputTreeArtifact;
+  private final Artifact.SpecialArtifact dotdTreeArtifact;
   private final CcToolchainProvider toolchain;
   private final Iterable<ArtifactCategory> categories;
   private final ActionOwner actionOwner;
@@ -67,8 +67,8 @@
    */
   CppCompileActionTemplate(
       Artifact sourceTreeArtifact,
-      Artifact outputTreeArtifact,
-      Artifact dotdTreeArtifact,
+      Artifact.SpecialArtifact outputTreeArtifact,
+      Artifact.SpecialArtifact dotdTreeArtifact,
       CppCompileActionBuilder cppCompileActionBuilder,
       CcToolchainProvider toolchain,
       Iterable<ArtifactCategory> categories,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java
index 9ec5184..665a915 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/OneVersionCheckActionBuilder.java
@@ -127,7 +127,7 @@
           args.accept(jar.getExecPathString() + "," + getArtifactOwnerGeneralizedLabel(jar));
 
   private static String getArtifactOwnerGeneralizedLabel(Artifact artifact) {
-    Label label = checkNotNull(artifact.getArtifactOwner(), artifact).getLabel();
+    Label label = checkNotNull(artifact.getOwnerLabel(), artifact);
     return label.getPackageIdentifier().getRepository().isDefault()
             || label.getPackageIdentifier().getRepository().isMain()
         ? label.toString()
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/AggregatingTestListener.java b/src/main/java/com/google/devtools/build/lib/runtime/AggregatingTestListener.java
index e2a70d2..a3a4c4f 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/AggregatingTestListener.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/AggregatingTestListener.java
@@ -17,6 +17,7 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
@@ -90,7 +91,7 @@
     // Add all target runs to the map, assuming 1:1 status artifact <-> result.
     synchronized (summaryLock) {
       for (ConfiguredTarget target : event.getTestTargets()) {
-        Iterable<Artifact> statusArtifacts =
+        ImmutableList<Artifact.DerivedArtifact> statusArtifacts =
             target.getProvider(TestProvider.class).getTestParams().getTestStatusArtifacts();
         Preconditions.checkState(
             remainingRuns.putAll(asKey(target), statusArtifacts),
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComputer.java b/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComputer.java
index 042d490..fedcaa9 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComputer.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComputer.java
@@ -347,7 +347,8 @@
     CriticalPathComponent depComponent = outputArtifactToComponent.get(input);
     if (depComponent != null) {
       if (depComponent.isRunning()) {
-        checkCriticalPathInconsistency(input, depComponent.getAction(), actionStats);
+        checkCriticalPathInconsistency(
+            (Artifact.DerivedArtifact) input, depComponent.getAction(), actionStats);
         return;
       }
       actionStats.addDepInfo(depComponent);
@@ -355,7 +356,7 @@
   }
 
   protected void checkCriticalPathInconsistency(
-      Artifact input, Action action, CriticalPathComponent actionStats) {
+      Artifact.DerivedArtifact input, Action action, CriticalPathComponent actionStats) {
     // Rare case that an action depending on a previously-cached shared action sees a different
     // shared action that is in the midst of being an action cache hit.
     for (Artifact actionOutput : action.getOutputs()) {
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java
index f3e92ab..de99423 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java
@@ -394,7 +394,8 @@
 
     if (targetToRun.getProvider(TestProvider.class) != null) {
       // This is a test. Provide it with a reasonable approximation of the actual test environment
-      ImmutableList<Artifact> statusArtifacts = TestProvider.getTestStatusArtifacts(targetToRun);
+      ImmutableList<Artifact.DerivedArtifact> statusArtifacts =
+          TestProvider.getTestStatusArtifacts(targetToRun);
       if (statusArtifacts.size() != 1) {
         env.getReporter().handle(Event.error(MULTIPLE_TESTS_MESSAGE));
         return BlazeCommandResult.exitCode(ExitCode.COMMAND_LINE_ERROR);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
index 0cd2c56..0dce603 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
@@ -678,7 +678,7 @@
       }
 
       ImmutableList<FilesetOutputSymlink> mapping =
-          ActionInputMapHelper.getFilesets(env, actionInput);
+          ActionInputMapHelper.getFilesets(env, (Artifact.SpecialArtifact) actionInput);
       if (mapping == null) {
         return null;
       }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java
index 356ab9c..26ca7e2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java
@@ -50,7 +50,8 @@
         Artifact artifact = entry.first;
         inputMap.put(artifact, entry.second, /*depOwner=*/ key);
         if (artifact.isFileset()) {
-          ImmutableList<FilesetOutputSymlink> expandedFileset = getFilesets(env, artifact);
+          ImmutableList<FilesetOutputSymlink> expandedFileset =
+              getFilesets(env, (Artifact.SpecialArtifact) artifact);
           if (expandedFileset != null) {
             expandedFilesets.put(artifact, expandedFileset);
           }
@@ -89,8 +90,8 @@
     }
   }
 
-  static ImmutableList<FilesetOutputSymlink> getFilesets(Environment env,
-      Artifact actionInput) throws InterruptedException {
+  static ImmutableList<FilesetOutputSymlink> getFilesets(
+      Environment env, Artifact.SpecialArtifact actionInput) throws InterruptedException {
     Preconditions.checkState(actionInput.isFileset(), actionInput);
     ActionLookupKey filesetActionLookupKey = (ActionLookupKey) actionInput.getArtifactOwner();
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
index f82d075..e4fd0fb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
@@ -493,7 +493,7 @@
 
   @Override
   public void injectRemoteDirectory(
-      Artifact output, Map<PathFragment, RemoteFileArtifactValue> children) {
+      SpecialArtifact output, Map<PathFragment, RemoteFileArtifactValue> children) {
     Preconditions.checkArgument(
         isKnownOutput(output), output + " is not a declared output of this action");
     Preconditions.checkArgument(output.isTreeArtifact(), "output must be a tree artifact");
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionRewindStrategy.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionRewindStrategy.java
index 9d9cfeb..f3f5372 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionRewindStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionRewindStrategy.java
@@ -106,7 +106,7 @@
     // failedAction, which the caller of getRewindPlan already knows must be restarted).
     ImmutableList.Builder<Action> additionalActionsToRestart = ImmutableList.builder();
 
-    HashMultimap<Artifact, ActionInput> lostInputsByDepOwners =
+    HashMultimap<Artifact.DerivedArtifact, ActionInput> lostInputsByDepOwners =
         getLostInputsByDepOwners(
             lostInputs,
             lostInputsException.getInputOwners(),
@@ -114,9 +114,9 @@
             ImmutableSet.copyOf(failedActionDeps),
             failedAction);
 
-    for (Map.Entry<Artifact, Collection<ActionInput>> entry :
+    for (Map.Entry<Artifact.DerivedArtifact, Collection<ActionInput>> entry :
         lostInputsByDepOwners.asMap().entrySet()) {
-      Artifact lostArtifact = entry.getKey();
+      Artifact.DerivedArtifact lostArtifact = entry.getKey();
       checkIfLostArtifactIsSource(
           lostArtifact, failedAction, lostInputsException, entry.getValue());
 
@@ -207,14 +207,15 @@
     }
   }
 
-  private HashMultimap<Artifact, ActionInput> getLostInputsByDepOwners(
+  private HashMultimap<Artifact.DerivedArtifact, ActionInput> getLostInputsByDepOwners(
       ImmutableList<ActionInput> lostInputs,
       LostInputsExecException.InputOwners inputOwners,
       ActionInputDepOwners runfilesDepOwners,
       ImmutableSet<SkyKey> failedActionDeps,
       Action failedActionForLogging) {
 
-    HashMultimap<Artifact, ActionInput> lostInputsByDepOwners = HashMultimap.create();
+    HashMultimap<Artifact.DerivedArtifact, ActionInput> lostInputsByDepOwners =
+        HashMultimap.create();
     for (ActionInput lostInput : lostInputs) {
       boolean foundLostInputDepOwner = false;
       Artifact owner = inputOwners.getOwner(lostInput);
@@ -231,7 +232,8 @@
           if (failedActionDeps.contains(runfilesTransitiveOwner)) {
             // The lost input's owning tree artifact or fileset is included in a runfiles middleman
             // that the action directly depends on.
-            lostInputsByDepOwners.put(runfilesTransitiveOwner, lostInput);
+            lostInputsByDepOwners.put(
+                (Artifact.DerivedArtifact) runfilesTransitiveOwner, lostInput);
             foundLostInputDepOwner = true;
           }
         }
@@ -240,7 +242,7 @@
       Collection<Artifact> runfilesOwners = runfilesDepOwners.getDepOwners(lostInput);
       for (Artifact runfilesOwner : runfilesOwners) {
         if (failedActionDeps.contains(runfilesOwner)) {
-          lostInputsByDepOwners.put(runfilesOwner, lostInput);
+          lostInputsByDepOwners.put((Artifact.DerivedArtifact) runfilesOwner, lostInput);
           foundLostInputDepOwner = true;
         }
       }
@@ -248,7 +250,7 @@
       if (owner != null && failedActionDeps.contains(owner)) {
         // The lost input is included in a tree artifact or fileset that the action directly depends
         // on.
-        lostInputsByDepOwners.put(owner, lostInput);
+        lostInputsByDepOwners.put((Artifact.DerivedArtifact) owner, lostInput);
         foundLostInputDepOwner = true;
       }
 
@@ -259,7 +261,7 @@
                 + "lostInput: %s, failedAction: %.10000s",
             lostInput,
             failedActionForLogging);
-        lostInputsByDepOwners.put((Artifact) lostInput, lostInput);
+        lostInputsByDepOwners.put((Artifact.DerivedArtifact) lostInput, lostInput);
         foundLostInputDepOwner = true;
       }
 
@@ -302,7 +304,7 @@
       ActionAndLookupData actionAndLookupData = uncheckedActions.removeFirst();
       ActionLookupData actionKey = actionAndLookupData.lookupData();
       Action action = actionAndLookupData.action();
-      HashSet<Artifact> artifactsToCheck = new HashSet<>();
+      HashSet<Artifact.DerivedArtifact> artifactsToCheck = new HashSet<>();
 
       if (action instanceof SkyframeAwareAction) {
         // This action depends on more than just its input artifact values. We need to also restart
@@ -319,7 +321,7 @@
             addPropagatingActionDepsAndGetNewlyVisitedArtifacts(rewindGraph, actionKey, action));
       }
 
-      for (Artifact artifact : artifactsToCheck) {
+      for (Artifact.DerivedArtifact artifact : artifactsToCheck) {
         Map<ActionLookupData, Action> actionMap = getActionsForLostArtifact(artifact, env);
         if (actionMap == null) {
           continue;
@@ -338,7 +340,7 @@
    *
    * <p>Returns a list of artifacts which were newly added to the graph.
    */
-  private ImmutableList<Artifact> addSkyframeAwareDepsAndGetNewlyVisitedArtifacts(
+  private ImmutableList<Artifact.DerivedArtifact> addSkyframeAwareDepsAndGetNewlyVisitedArtifacts(
       MutableGraph<SkyKey> rewindGraph, ActionLookupData actionKey, SkyframeAwareAction action) {
 
     ImmutableGraph<SkyKey> graph = action.getSkyframeDependenciesForRewinding(actionKey);
@@ -347,12 +349,12 @@
     }
     assertSkyframeAwareRewindingGraph(graph, actionKey);
 
-    ImmutableList.Builder<Artifact> newlyVisitedArtifacts = ImmutableList.builder();
+    ImmutableList.Builder<Artifact.DerivedArtifact> newlyVisitedArtifacts = ImmutableList.builder();
     Set<EndpointPair<SkyKey>> edges = graph.edges();
     for (EndpointPair<SkyKey> edge : edges) {
       SkyKey target = edge.target();
       if (target instanceof Artifact && rewindGraph.addNode(target)) {
-        newlyVisitedArtifacts.add(((Artifact) target));
+        newlyVisitedArtifacts.add(((Artifact.DerivedArtifact) target));
       }
       rewindGraph.putEdge(edge.source(), edge.target());
     }
@@ -365,10 +367,11 @@
    *
    * <p>Returns a list of artifacts which were newly added to the graph.
    */
-  private ImmutableList<Artifact> addPropagatingActionDepsAndGetNewlyVisitedArtifacts(
-      MutableGraph<SkyKey> rewindGraph, ActionLookupData actionKey, Action action) {
+  private ImmutableList<Artifact.DerivedArtifact>
+      addPropagatingActionDepsAndGetNewlyVisitedArtifacts(
+          MutableGraph<SkyKey> rewindGraph, ActionLookupData actionKey, Action action) {
 
-    ImmutableList.Builder<Artifact> newlyVisitedArtifacts = ImmutableList.builder();
+    ImmutableList.Builder<Artifact.DerivedArtifact> newlyVisitedArtifacts = ImmutableList.builder();
     for (Artifact input : action.getInputs()) {
       if (input.isSourceArtifact()) {
         continue;
@@ -380,7 +383,7 @@
       //
       // Rewinding is expected to be rare, so refining this may not be necessary.
       if (rewindGraph.addNode(input)) {
-        newlyVisitedArtifacts.add(input);
+        newlyVisitedArtifacts.add((Artifact.DerivedArtifact) input);
       }
       rewindGraph.putEdge(actionKey, input);
     }
@@ -432,7 +435,7 @@
    */
   @Nullable
   private Map<ActionLookupData, Action> getActionsForLostArtifact(
-      Artifact lostInput, Environment env) throws InterruptedException {
+      Artifact.DerivedArtifact lostInput, Environment env) throws InterruptedException {
     Set<ActionLookupData> actionExecutionDeps = getActionExecutionDeps(lostInput, env);
     if (actionExecutionDeps == null) {
       return null;
@@ -451,8 +454,8 @@
    * or {@code null} if any of those dependencies are not done.
    */
   @Nullable
-  private Set<ActionLookupData> getActionExecutionDeps(Artifact lostInput, Environment env)
-      throws InterruptedException {
+  private Set<ActionLookupData> getActionExecutionDeps(
+      Artifact.DerivedArtifact lostInput, Environment env) throws InterruptedException {
     ArtifactFunction.ArtifactDependencies artifactDependencies =
         ArtifactFunction.ArtifactDependencies.discoverDependencies(lostInput, env);
     if (artifactDependencies == null) {
@@ -519,6 +522,13 @@
           graph,
           actionKey,
           target);
+      Preconditions.checkState(
+          !(target instanceof Artifact) || target instanceof Artifact.DerivedArtifact,
+          "A non-source artifact must be derived. graph: %s, rootActionNode: %s, sourceArtifact:"
+              + " %s",
+          graph,
+          actionKey,
+          target);
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
index 71de21a..bfec907 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
@@ -83,7 +83,7 @@
     }
 
     ArtifactDependencies artifactDependencies =
-        ArtifactDependencies.discoverDependencies(artifact, env);
+        ArtifactDependencies.discoverDependencies((Artifact.DerivedArtifact) artifact, env);
     if (artifactDependencies == null) {
       return null;
     }
@@ -446,16 +446,13 @@
     }
 
     /**
-     * Constructs an {@link ArtifactDependencies} for the provided {@code derivedArtifact}, which
-     * must not be a source artifact. Returns {@code null} if any dependencies are not yet ready.
+     * Constructs an {@link ArtifactDependencies} for the provided {@code derivedArtifact}. Returns
+     * {@code null} if any dependencies are not yet ready.
      */
     @Nullable
     static ArtifactDependencies discoverDependencies(
-        Artifact derivedArtifact, SkyFunction.Environment env) throws InterruptedException {
-      Preconditions.checkArgument(
-          !derivedArtifact.isSourceArtifact(),
-          "derivedArtifact is not derived: %s",
-          derivedArtifact);
+        Artifact.DerivedArtifact derivedArtifact, SkyFunction.Environment env)
+        throws InterruptedException {
 
       ActionLookupKey actionLookupKey = ArtifactFunction.getActionLookupKey(derivedArtifact);
       ActionLookupValue actionLookupValue =
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
index 10a63fb..44a9325 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
@@ -387,7 +387,8 @@
               artifactValue,
               env);
           if (input.isFileset()) {
-            expandedFilesets.put(input, ActionInputMapHelper.getFilesets(env, input));
+            expandedFilesets.put(
+                input, ActionInputMapHelper.getFilesets(env, (Artifact.SpecialArtifact) input));
           }
         }
       } catch (MissingInputFileException e) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
index f2d6021..bc740eb 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeBuildView.java
@@ -32,7 +32,6 @@
 import com.google.devtools.build.lib.actions.ActionKeyContext;
 import com.google.devtools.build.lib.actions.ActionLookupValue;
 import com.google.devtools.build.lib.actions.ArtifactFactory;
-import com.google.devtools.build.lib.actions.ArtifactOwner;
 import com.google.devtools.build.lib.actions.ArtifactPrefixConflictException;
 import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
 import com.google.devtools.build.lib.actions.PackageRoots;
@@ -727,7 +726,7 @@
   /** Returns null if any build-info values are not ready. */
   @Nullable
   CachingAnalysisEnvironment createAnalysisEnvironment(
-      ArtifactOwner owner,
+      ActionLookupValue.ActionLookupKey owner,
       boolean isSystemEnv,
       ExtendedEventHandler eventHandler,
       Environment env,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java
index 503a124..112fd7e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TestCompletionFunction.java
@@ -77,7 +77,7 @@
         }
       }
     } else {
-      Multimap<ActionLookupValue.ActionLookupKey, Artifact> keyToArtifactMap =
+      Multimap<ActionLookupValue.ActionLookupKey, Artifact.DerivedArtifact> keyToArtifactMap =
           Multimaps.index(
               TestProvider.getTestStatusArtifacts(ct), ArtifactFunction::getActionLookupKey);
       Map<SkyKey, SkyValue> actionLookupValues = env.getValues(keyToArtifactMap.keySet());
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceStatusValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceStatusValue.java
index d54379a4..55876e3 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceStatusValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/WorkspaceStatusValue.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.skyframe;
 
+import com.google.devtools.build.lib.actions.Actions;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.BasicActionLookupValue;
 import com.google.devtools.build.lib.analysis.WorkspaceStatusAction;
@@ -37,7 +38,8 @@
       Artifact stableArtifact,
       Artifact volatileArtifact,
       WorkspaceStatusAction workspaceStatusAction) {
-    super(workspaceStatusAction);
+    super(
+        Actions.GeneratingActions.fromSingleAction(workspaceStatusAction), /*nonceVersion=*/ null);
     this.stableArtifact = stableArtifact;
     this.volatileArtifact = volatileArtifact;
   }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java
index 2fbbf55..1befe4c 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactFactoryTest.java
@@ -222,8 +222,10 @@
 
   @Test
   public void testSetGeneratingActionIdempotenceNewActionGraph() throws Exception {
-    Artifact a = artifactFactory.getDerivedArtifact(fooRelative, outRoot, NULL_ARTIFACT_OWNER);
-    Artifact b = artifactFactory.getDerivedArtifact(barRelative, outRoot, NULL_ARTIFACT_OWNER);
+    Artifact.DerivedArtifact a =
+        artifactFactory.getDerivedArtifact(fooRelative, outRoot, NULL_ARTIFACT_OWNER);
+    Artifact.DerivedArtifact b =
+        artifactFactory.getDerivedArtifact(barRelative, outRoot, NULL_ARTIFACT_OWNER);
     MutableActionGraph actionGraph = new MapBasedActionGraph(actionKeyContext);
     Action originalAction = new ActionsTestUtil.NullAction(NULL_ACTION_OWNER, a);
     actionGraph.registerAction(originalAction);
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
index 2020379..aef63fe 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactTest.java
@@ -338,14 +338,16 @@
 
   @Test
   public void testCodec() throws Exception {
+    Artifact.DerivedArtifact artifact =
+        (Artifact.DerivedArtifact) ActionsTestUtil.createArtifact(rootDir, "src/a");
     ArtifactRoot anotherRoot =
         ArtifactRoot.asDerivedRoot(scratch.getFileSystem().getPath("/"), scratch.dir("/src"));
-    new SerializationTester(
-            ActionsTestUtil.createArtifact(rootDir, "src/a"),
-            new Artifact.DerivedArtifact(
-                anotherRoot,
-                anotherRoot.getExecPath().getRelative("src/c"),
-                new LabelArtifactOwner(Label.parseAbsoluteUnchecked("//foo:bar"))))
+    Artifact.DerivedArtifact anotherArtifact =
+        new Artifact.DerivedArtifact(
+            anotherRoot,
+            anotherRoot.getExecPath().getRelative("src/c"),
+            new LabelArtifactOwner(Label.parseAbsoluteUnchecked("//foo:bar")));
+    new SerializationTester(artifact, anotherArtifact)
         .addDependency(FileSystem.class, scratch.getFileSystem())
         .runTests();
   }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java b/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
index 46d5bf6..7721533 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ExecutableSymlinkActionTest.java
@@ -122,9 +122,12 @@
     Path file = inputRoot.getRoot().getRelative("some-file");
     FileSystemUtils.createEmptyFile(file);
     file.setExecutable(/*executable=*/ false);
-    Artifact input = ActionsTestUtil.createArtifact(inputRoot, file);
-    Artifact output =
-        ActionsTestUtil.createArtifact(outputRoot, outputRoot.getRoot().getRelative("some-output"));
+    Artifact.DerivedArtifact input =
+        (Artifact.DerivedArtifact) ActionsTestUtil.createArtifact(inputRoot, file);
+    Artifact.DerivedArtifact output =
+        (Artifact.DerivedArtifact)
+            ActionsTestUtil.createArtifact(
+                outputRoot, outputRoot.getRoot().getRelative("some-output"));
     SymlinkAction action = SymlinkAction.toExecutable(NULL_ACTION_OWNER, input, output, "progress");
     new SerializationTester(action)
         .addDependency(FileSystem.class, scratch.getFileSystem())
diff --git a/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java b/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
index 5ad450c..863f4cd 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
@@ -58,6 +58,7 @@
 import com.google.devtools.build.lib.analysis.actions.CustomCommandLine;
 import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate;
 import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate.OutputPathMapper;
+import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.EventHandler;
@@ -96,6 +97,7 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 
@@ -225,6 +227,15 @@
         : new Artifact.DerivedArtifact(root, execPath, ArtifactOwner.NullArtifactOwner.INSTANCE);
   }
 
+  public static void assertNoArtifactEndingWith(RuleConfiguredTarget target, String path) {
+    Pattern endPattern = Pattern.compile(path + "$");
+    for (ActionAnalysisMetadata action : target.getActions()) {
+      for (Artifact output : action.getOutputs()) {
+        assertThat(output.getExecPathString()).doesNotMatch(endPattern);
+      }
+    }
+  }
+
   /**
    * {@link SkyFunction.Environment} that internally makes a full Skyframe evaluate call for the
    * requested keys, blocking until the values are ready.
@@ -560,7 +571,7 @@
    * suffix and returns the Artifact.
    */
   public static Artifact getFirstArtifactEndingWith(
-      Iterable<Artifact> artifacts, String suffix) {
+      Iterable<? extends Artifact> artifacts, String suffix) {
     for (Artifact a : artifacts) {
       if (a.getExecPath().getPathString().endsWith(suffix)) {
         return a;
@@ -766,7 +777,7 @@
 
     @Override
     public void injectRemoteDirectory(
-        Artifact treeArtifact, Map<PathFragment, RemoteFileArtifactValue> children) {
+        SpecialArtifact treeArtifact, Map<PathFragment, RemoteFileArtifactValue> children) {
       throw new UnsupportedOperationException();
     }
 
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java b/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java
index 2b80208..cf4b1a3 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/AspectTest.java
@@ -536,7 +536,7 @@
     update();
 
     ConfiguredTarget a = getConfiguredTarget("//a:a");
-    NestedSet<Artifact> extraActionArtifacts =
+    NestedSet<Artifact.DerivedArtifact> extraActionArtifacts =
         a.getProvider(ExtraActionArtifactsProvider.class).getTransitiveExtraActionArtifacts();
     for (Artifact artifact : extraActionArtifacts) {
       assertThat(artifact.getOwnerLabel()).isEqualTo(Label.create("@//a", "b"));
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java b/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
index 37ac7d0..444eb68 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/actions/SymlinkActionTest.java
@@ -45,7 +45,7 @@
   private Path input;
   private Artifact inputArtifact;
   private Path output;
-  private Artifact outputArtifact;
+  private Artifact.DerivedArtifact outputArtifact;
   private SymlinkAction action;
 
   @Before
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
index 5ef6c9f..374f98e 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestCase.java
@@ -29,6 +29,7 @@
 import com.google.devtools.build.lib.actions.ActionAnalysisMetadata;
 import com.google.devtools.build.lib.actions.ActionGraph;
 import com.google.devtools.build.lib.actions.ActionKeyContext;
+import com.google.devtools.build.lib.actions.ActionLookupValue;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.AnalysisOptions;
 import com.google.devtools.build.lib.analysis.AnalysisResult;
@@ -528,6 +529,25 @@
   protected Artifact getBinArtifact(String packageRelativePath, ConfiguredTarget owner)
       throws InterruptedException {
     Label label = owner.getLabel();
+    ActionLookupValue.ActionLookupKey actionLookupKey =
+        ConfiguredTargetKey.of(label, owner.getConfigurationKey(), /*isHostConfiguration=*/ false);
+    ActionLookupValue actionLookupValue;
+    try {
+      actionLookupValue =
+          (ActionLookupValue)
+              skyframeExecutor.getEvaluatorForTesting().getExistingValue(actionLookupKey);
+    } catch (InterruptedException e) {
+      throw new IllegalStateException(e);
+    }
+    PathFragment rootRelativePath = label.getPackageFragment().getRelative(packageRelativePath);
+    for (ActionAnalysisMetadata action : actionLookupValue.getActions()) {
+      for (Artifact output : action.getOutputs()) {
+        if (output.getRootRelativePath().equals(rootRelativePath)) {
+          return output;
+        }
+      }
+    }
+    // Fall back: some tests don't actually need the right owner.
     return buildView
         .getArtifactFactory()
         .getDerivedArtifact(
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
index 71d9c08..09caee3 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisTestUtil.java
@@ -23,11 +23,11 @@
 import com.google.devtools.build.lib.actions.ActionExecutionContext;
 import com.google.devtools.build.lib.actions.ActionExecutionException;
 import com.google.devtools.build.lib.actions.ActionKeyContext;
+import com.google.devtools.build.lib.actions.ActionLookupValue;
 import com.google.devtools.build.lib.actions.ActionOwner;
 import com.google.devtools.build.lib.actions.ActionResult;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
-import com.google.devtools.build.lib.actions.ArtifactOwner;
 import com.google.devtools.build.lib.actions.ArtifactRoot;
 import com.google.devtools.build.lib.actions.ExecutionStrategy;
 import com.google.devtools.build.lib.actions.MiddlemanFactory;
@@ -53,6 +53,7 @@
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunctionName;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -119,7 +120,8 @@
     }
 
     @Override
-    public Artifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
+    public Artifact.DerivedArtifact getDerivedArtifact(
+        PathFragment rootRelativePath, ArtifactRoot root) {
       return original.getDerivedArtifact(rootRelativePath, root);
     }
 
@@ -180,7 +182,7 @@
     }
 
     @Override
-    public ArtifactOwner getOwner() {
+    public ActionLookupValue.ActionLookupKey getOwner() {
       return original.getOwner();
     }
 
@@ -300,6 +302,14 @@
 
   /** An AnalysisEnvironment with stubbed-out methods. */
   public static class StubAnalysisEnvironment implements AnalysisEnvironment {
+    private static final ActionLookupValue.ActionLookupKey DUMMY_KEY =
+        new ActionLookupValue.ActionLookupKey() {
+          @Override
+          public SkyFunctionName functionName() {
+            return null;
+          }
+        };
+
     @Override
     public void registerAction(ActionAnalysisMetadata... action) {
     }
@@ -355,7 +365,8 @@
     }
 
     @Override
-    public Artifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
+    public Artifact.DerivedArtifact getDerivedArtifact(
+        PathFragment rootRelativePath, ArtifactRoot root) {
       return null;
     }
 
@@ -376,8 +387,8 @@
     }
 
     @Override
-    public ArtifactOwner getOwner() {
-      return ArtifactOwner.NullArtifactOwner.INSTANCE;
+    public ActionLookupValue.ActionLookupKey getOwner() {
+      return DUMMY_KEY;
     }
 
     @Override
@@ -403,14 +414,13 @@
       Pattern.compile("(?<=" + TestConstants.PRODUCT_NAME + "-out/)gcc[^/]*-grte-\\w+-");
 
   /**
-   * Given a collection of Artifacts, returns a corresponding set of strings of
-   * the form "{root} {relpath}", such as "bin x/libx.a".  Such strings make
-   * assertions easier to write.
+   * Given a collection of Artifacts, returns a corresponding set of strings of the form "{root}
+   * {relpath}", such as "bin x/libx.a". Such strings make assertions easier to write.
    *
    * <p>The returned set preserves the order of the input.
    */
-  public static Set<String> artifactsToStrings(BuildConfigurationCollection configurations,
-      Iterable<Artifact> artifacts) {
+  public static Set<String> artifactsToStrings(
+      BuildConfigurationCollection configurations, Iterable<? extends Artifact> artifacts) {
     Map<String, String> rootMap = new HashMap<>();
     BuildConfiguration targetConfiguration =
         Iterables.getOnlyElement(configurations.getTargetConfigurations());
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index f7f44d9..0a85f26 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -38,6 +38,7 @@
 import com.google.devtools.build.lib.actions.ActionInput;
 import com.google.devtools.build.lib.actions.ActionKeyContext;
 import com.google.devtools.build.lib.actions.ActionLogBufferPathGenerator;
+import com.google.devtools.build.lib.actions.ActionLookupValue;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
 import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
@@ -158,6 +159,8 @@
 import com.google.devtools.build.skyframe.ErrorInfo;
 import com.google.devtools.build.skyframe.MemoizingEvaluator;
 import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.common.options.Options;
 import com.google.devtools.common.options.OptionsParser;
 import com.google.devtools.common.options.OptionsParsingException;
@@ -566,7 +569,12 @@
     return new CachingAnalysisEnvironment(
         view.getArtifactFactory(),
         actionKeyContext,
-        ArtifactOwner.NullArtifactOwner.INSTANCE,
+        new ActionLookupValue.ActionLookupKey() {
+          @Override
+          public SkyFunctionName functionName() {
+            return null;
+          }
+        },
         /*isSystemEnv=*/ true,
         /*extendedSanityChecks=*/ false,
         /*allowAnalysisFailures=*/ false,
@@ -1130,13 +1138,12 @@
   }
 
   /**
-   * Given a collection of Artifacts, returns a corresponding set of strings of
-   * the form "[root] [relpath]", such as "bin x/libx.a".  Such strings make
-   * assertions easier to write.
+   * Given a collection of Artifacts, returns a corresponding set of strings of the form "[root]
+   * [relpath]", such as "bin x/libx.a". Such strings make assertions easier to write.
    *
    * <p>The returned set preserves the order of the input.
    */
-  protected Set<String> artifactsToStrings(Iterable<Artifact> artifacts) {
+  protected Set<String> artifactsToStrings(Iterable<? extends Artifact> artifacts) {
     return AnalysisTestUtil.artifactsToStrings(masterConfig, artifacts);
   }
 
@@ -1196,8 +1203,27 @@
    * that does not exercise the analysis phase, the convenience methods {@link
    * #getBinArtifactWithNoOwner} or {@link #getGenfilesArtifactWithNoOwner} should be used instead.
    */
-  protected Artifact getDerivedArtifact(
+  protected final Artifact.DerivedArtifact getDerivedArtifact(
       PathFragment rootRelativePath, ArtifactRoot root, ArtifactOwner owner) {
+    if ((owner instanceof ActionLookupValue.ActionLookupKey)) {
+      ActionLookupValue actionLookupValue;
+      try {
+        actionLookupValue =
+            (ActionLookupValue)
+                skyframeExecutor.getEvaluatorForTesting().getExistingValue((SkyKey) owner);
+      } catch (InterruptedException e) {
+        throw new IllegalStateException(e);
+      }
+      for (ActionAnalysisMetadata action : actionLookupValue.getActions()) {
+        for (Artifact output : action.getOutputs()) {
+          if (output.getRootRelativePath().equals(rootRelativePath)
+              && output.getRoot().equals(root)) {
+            return (Artifact.DerivedArtifact) output;
+          }
+        }
+      }
+    }
+    // Fall back: some tests don't actually need an artifact with an owner.
     return view.getArtifactFactory().getDerivedArtifact(rootRelativePath, root, owner);
   }
 
@@ -1274,7 +1300,7 @@
    * to own this artifact. If the test runs the analysis phase, {@link #getBinArtifact(String,
    * ConfiguredTarget)} or its convenience methods should be used instead.
    */
-  protected Artifact getBinArtifactWithNoOwner(String rootRelativePath) {
+  protected Artifact.DerivedArtifact getBinArtifactWithNoOwner(String rootRelativePath) {
     return getDerivedArtifact(PathFragment.create(rootRelativePath),
         targetConfig.getBinDirectory(RepositoryName.MAIN),
         ActionsTestUtil.NULL_ARTIFACT_OWNER);
@@ -1674,7 +1700,7 @@
         : provider.getOutputGroup(outputGroup);
   }
 
-  protected NestedSet<Artifact> getExtraActionArtifacts(ConfiguredTarget target) {
+  protected NestedSet<Artifact.DerivedArtifact> getExtraActionArtifacts(ConfiguredTarget target) {
     return target.getProvider(ExtraActionArtifactsProvider.class).getExtraActionArtifacts();
   }
 
@@ -1997,7 +2023,8 @@
     }
 
     @Override
-    public Artifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
+    public Artifact.DerivedArtifact getDerivedArtifact(
+        PathFragment rootRelativePath, ArtifactRoot root) {
       throw new UnsupportedOperationException();
     }
 
@@ -2018,7 +2045,7 @@
     }
 
     @Override
-    public ArtifactOwner getOwner() {
+    public ActionLookupValue.ActionLookupKey getOwner() {
       throw new UnsupportedOperationException();
     }
 
diff --git a/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java b/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java
index 4e8a3e6..88e5b67 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java
@@ -148,7 +148,7 @@
 
   private TestRunnerAction getTestAction(String target) throws Exception {
     ConfiguredTarget configuredTarget = getConfiguredTarget(target);
-    List<Artifact> testStatusArtifacts =
+    ImmutableList<Artifact.DerivedArtifact> testStatusArtifacts =
         configuredTarget.getProvider(TestProvider.class).getTestParams().getTestStatusArtifacts();
     Artifact testStatusArtifact = Iterables.getOnlyElement(testStatusArtifacts);
     TestRunnerAction action = (TestRunnerAction) getGeneratingAction(testStatusArtifact);
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java
index 49c4f31..c1e712a 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java
@@ -182,7 +182,8 @@
 
             @Override
             public void injectRemoteDirectory(
-                Artifact output, Map<PathFragment, RemoteFileArtifactValue> children) {
+                Artifact.SpecialArtifact output,
+                Map<PathFragment, RemoteFileArtifactValue> children) {
               throw new UnsupportedOperationException();
             }
 
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
index 66021a3..d1fb3de 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
@@ -1069,7 +1069,7 @@
 
         @Override
         public void injectRemoteDirectory(
-            Artifact output, Map<PathFragment, RemoteFileArtifactValue> children) {
+            Artifact.SpecialArtifact output, Map<PathFragment, RemoteFileArtifactValue> children) {
           throw new UnsupportedOperationException();
         }
 
diff --git a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
index 74a854d..9992c70 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/cpp/CppLinkActionTest.java
@@ -92,7 +92,8 @@
           }
 
           @Override
-          public Artifact getDerivedArtifact(PathFragment rootRelativePath, ArtifactRoot root) {
+          public Artifact.DerivedArtifact getDerivedArtifact(
+              PathFragment rootRelativePath, ArtifactRoot root) {
             return CppLinkActionTest.this.getDerivedArtifact(
                 rootRelativePath, root, ActionsTestUtil.NULL_ARTIFACT_OWNER);
           }
diff --git a/src/test/java/com/google/devtools/build/lib/rules/java/proto/SkylarkJavaLiteProtoLibraryTest.java b/src/test/java/com/google/devtools/build/lib/rules/java/proto/SkylarkJavaLiteProtoLibraryTest.java
index 159f659..72f0a0e 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/java/proto/SkylarkJavaLiteProtoLibraryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/java/proto/SkylarkJavaLiteProtoLibraryTest.java
@@ -312,7 +312,7 @@
 
     useConfiguration("--experimental_action_listener=//xa:al");
     ConfiguredTarget ct = getConfiguredTarget("//x:lite_pb2");
-    Iterable<Artifact> artifacts =
+    Iterable<Artifact.DerivedArtifact> artifacts =
         ct.getProvider(ExtraActionArtifactsProvider.class).getTransitiveExtraActionArtifacts();
 
     Iterable<String> extraActionOwnerLabels =
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
index 98ec1e7..08fd7de 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
@@ -114,6 +114,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -148,7 +149,7 @@
     inMemoryCache = new InMemoryActionCache();
     tsgm = new TimestampGranularityMonitor(clock);
     ResourceManager.instance().setAvailableResources(ResourceSet.createWithRamCpu(100, 1));
-    actions = new HashSet<>();
+    actions = new LinkedHashSet<>();
     actionTemplateExpansionFunction = new ActionTemplateExpansionFunction(actionKeyContext);
   }