Validate build artifacts that originated remotely and can't be accessed locally, unless otherwise specified by the OutputService.

Previously, we assumed that if a file wasn't available locally and we know it was available remotely recently, the file is still accessible, but this isn't true when you've written a file to a remote OutputService and then switch to a local (or no) OutputService. This results in build failures when Bazel tries to read a file that it no longer can access that it should have validated during the analysis phase.

PiperOrigin-RevId: 302110160
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
index e4ad3c1..3eea94d 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
@@ -325,6 +325,10 @@
       }
 
       Profiler.instance().markPhase(ProfilePhase.EXECUTE);
+      boolean shouldTrustRemoteArtifacts =
+          env.getOutputService() == null
+              ? false
+              : env.getOutputService().shouldTrustRemoteArtifacts();
       builder.buildArtifacts(
           env.getReporter(),
           analysisResult.getTopLevelArtifactsToOwnerLabels().getArtifacts(),
@@ -338,7 +342,8 @@
           builtAspects,
           request,
           env.getBlazeWorkspace().getLastExecutionTimeRange(),
-          topLevelArtifactContext);
+          topLevelArtifactContext,
+          shouldTrustRemoteArtifacts);
       buildCompleted = true;
     } catch (BuildFailedException | TestExecException e) {
       buildCompleted = true;
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/SkyframeBuilder.java b/src/main/java/com/google/devtools/build/lib/buildtool/SkyframeBuilder.java
index 62f996ac..b6a0386 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/SkyframeBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/SkyframeBuilder.java
@@ -112,9 +112,11 @@
       Set<AspectKey> builtAspects,
       OptionsProvider options,
       @Nullable Range<Long> lastExecutionTimeRange,
-      TopLevelArtifactContext topLevelArtifactContext)
+      TopLevelArtifactContext topLevelArtifactContext,
+      boolean trustRemoteArtifacts)
       throws BuildFailedException, AbruptExitException, TestExecException, InterruptedException {
-    skyframeExecutor.detectModifiedOutputFiles(modifiedOutputFiles, lastExecutionTimeRange);
+    skyframeExecutor.detectModifiedOutputFiles(
+        modifiedOutputFiles, lastExecutionTimeRange, trustRemoteArtifacts);
     try (SilentCloseable c = Profiler.instance().profile("configureActionExecutor")) {
       skyframeExecutor.configureActionExecutor(fileCache, actionInputPrefetcher);
     }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/Builder.java b/src/main/java/com/google/devtools/build/lib/skyframe/Builder.java
index 916a926..7ad23c2 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/Builder.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/Builder.java
@@ -91,6 +91,7 @@
       Set<AspectKey> builtAspects,
       OptionsProvider options,
       @Nullable Range<Long> lastExecutionTimeRange,
-      TopLevelArtifactContext topLevelArtifactContext)
+      TopLevelArtifactContext topLevelArtifactContext,
+      boolean trustRemoteArtifacts)
       throws BuildFailedException, AbruptExitException, InterruptedException, TestExecException;
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java b/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java
index 75d5ee2..7eb39de 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/FilesystemValueChecker.java
@@ -88,12 +88,11 @@
   private AtomicInteger modifiedOutputFilesCounter = new AtomicInteger(0);
   private AtomicInteger modifiedOutputFilesIntraBuildCounter = new AtomicInteger(0);
 
-  public FilesystemValueChecker(@Nullable TimestampGranularityMonitor tsgm,
-      @Nullable Range<Long> lastExecutionTimeRange) {
+  public FilesystemValueChecker(
+      @Nullable TimestampGranularityMonitor tsgm, @Nullable Range<Long> lastExecutionTimeRange) {
     this.tsgm = tsgm;
     this.lastExecutionTimeRange = lastExecutionTimeRange;
   }
-
   /**
    * Returns a {@link Differencer.DiffWithDelta} containing keys from the give map that are dirty
    * according to the passed-in {@code dirtinessChecker}.
@@ -167,12 +166,15 @@
   }
 
   /**
-   * Return a collection of action values which have output files that are not in-sync with
-   * the on-disk file value (were modified externally).
+   * Return a collection of action values which have output files that are not in-sync with the
+   * on-disk file value (were modified externally).
    */
-  Collection<SkyKey> getDirtyActionValues(Map<SkyKey, SkyValue> valuesMap,
-      @Nullable final BatchStat batchStatter, ModifiedFileSet modifiedOutputFiles)
-          throws InterruptedException {
+  Collection<SkyKey> getDirtyActionValues(
+      Map<SkyKey, SkyValue> valuesMap,
+      @Nullable final BatchStat batchStatter,
+      ModifiedFileSet modifiedOutputFiles,
+      boolean trustRemoteArtifacts)
+      throws InterruptedException {
     if (modifiedOutputFiles == ModifiedFileSet.NOTHING_MODIFIED) {
       logger.info("Not checking for dirty actions since nothing was modified");
       return ImmutableList.of();
@@ -230,13 +232,18 @@
         Runnable job =
             (batchStatter == null)
                 ? outputStatJob(
-                    dirtyKeys, shard, knownModifiedOutputFiles, sortedKnownModifiedOutputFiles)
+                    dirtyKeys,
+                    shard,
+                    knownModifiedOutputFiles,
+                    sortedKnownModifiedOutputFiles,
+                    trustRemoteArtifacts)
                 : batchStatJob(
                     dirtyKeys,
                     shard,
                     batchStatter,
                     knownModifiedOutputFiles,
-                    sortedKnownModifiedOutputFiles);
+                    sortedKnownModifiedOutputFiles,
+                    trustRemoteArtifacts);
         Future<?> unused = executor.submit(wrapper.wrap(job));
       }
 
@@ -250,10 +257,13 @@
     return dirtyKeys;
   }
 
-  private Runnable batchStatJob(final Collection<SkyKey> dirtyKeys,
-          final List<Pair<SkyKey, ActionExecutionValue>> shard,
-          final BatchStat batchStatter, final ImmutableSet<PathFragment> knownModifiedOutputFiles,
-          final Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles) {
+  private Runnable batchStatJob(
+      final Collection<SkyKey> dirtyKeys,
+      final List<Pair<SkyKey, ActionExecutionValue>> shard,
+      final BatchStat batchStatter,
+      final ImmutableSet<PathFragment> knownModifiedOutputFiles,
+      final Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles,
+      boolean trustRemoteArtifacts) {
     return new Runnable() {
       @Override
       public void run() {
@@ -292,7 +302,12 @@
           // Batch stat did not work. Log an exception and fall back on system calls.
           LoggingUtil.logToRemote(Level.WARNING, "Unable to process batch stat", e);
           logger.log(Level.WARNING, "Unable to process batch stat", e);
-          outputStatJob(dirtyKeys, shard, knownModifiedOutputFiles, sortedKnownModifiedOutputFiles)
+          outputStatJob(
+                  dirtyKeys,
+                  shard,
+                  knownModifiedOutputFiles,
+                  sortedKnownModifiedOutputFiles,
+                  trustRemoteArtifacts)
               .run();
           return;
         } catch (InterruptedException e) {
@@ -357,10 +372,12 @@
     }
   }
 
-  private Runnable outputStatJob(final Collection<SkyKey> dirtyKeys,
+  private Runnable outputStatJob(
+      final Collection<SkyKey> dirtyKeys,
       final List<Pair<SkyKey, ActionExecutionValue>> shard,
       final ImmutableSet<PathFragment> knownModifiedOutputFiles,
-      final Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles) {
+      final Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles,
+      boolean trustRemoteArtifacts) {
     return new Runnable() {
       @Override
       public void run() {
@@ -368,7 +385,10 @@
           ActionExecutionValue value = keyAndValue.getSecond();
           if (value == null
               || actionValueIsDirtyWithDirectSystemCalls(
-                  value, knownModifiedOutputFiles, sortedKnownModifiedOutputFiles)) {
+                  value,
+                  knownModifiedOutputFiles,
+                  sortedKnownModifiedOutputFiles,
+                  trustRemoteArtifacts)) {
             dirtyKeys.add(keyAndValue.getFirst());
           }
         }
@@ -406,9 +426,11 @@
     }
   }
 
-  private boolean actionValueIsDirtyWithDirectSystemCalls(ActionExecutionValue actionValue,
+  private boolean actionValueIsDirtyWithDirectSystemCalls(
+      ActionExecutionValue actionValue,
       ImmutableSet<PathFragment> knownModifiedOutputFiles,
-      Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles) {
+      Supplier<NavigableSet<PathFragment>> sortedKnownModifiedOutputFiles,
+      boolean trustRemoteArtifacts) {
     boolean isDirty = false;
     for (Map.Entry<Artifact, FileArtifactValue> entry : actionValue.getAllFileValues().entrySet()) {
       Artifact file = entry.getKey();
@@ -420,7 +442,9 @@
           FileArtifactValue fileValue = actionValue.getArtifactValue(file);
           boolean lastSeenRemotely = (fileValue != null) && fileValue.isRemote();
           boolean trustRemoteValue =
-              fileMetadata.getType() == FileStateType.NONEXISTENT && lastSeenRemotely;
+              fileMetadata.getType() == FileStateType.NONEXISTENT
+                  && lastSeenRemotely
+                  && trustRemoteArtifacts;
           if (!trustRemoteValue && fileMetadata.couldBeModifiedSince(lastKnownData)) {
             updateIntraBuildModifiedCounter(
                 fileMetadata.getType() != FileStateType.NONEXISTENT
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
index 2323c4e..af79235 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutor.java
@@ -673,8 +673,9 @@
     TimestampGranularityMonitor tsgm = this.tsgm.get();
     Differencer.Diff diff;
     if (modifiedFileSet.treatEverythingAsModified()) {
-      diff = new FilesystemValueChecker(tsgm, null).getDirtyKeys(memoizingEvaluator.getValues(),
-          new BasicFilesystemDirtinessChecker());
+      diff =
+          new FilesystemValueChecker(tsgm, null)
+              .getDirtyKeys(memoizingEvaluator.getValues(), new BasicFilesystemDirtinessChecker());
     } else {
       diff = getDiff(tsgm, modifiedFileSet.modifiedSourceFiles(), pathEntry);
     }
@@ -693,7 +694,9 @@
 
   @Override
   public void detectModifiedOutputFiles(
-      ModifiedFileSet modifiedOutputFiles, @Nullable Range<Long> lastExecutionTimeRange)
+      ModifiedFileSet modifiedOutputFiles,
+      @Nullable Range<Long> lastExecutionTimeRange,
+      boolean trustRemoteArtifacts)
       throws InterruptedException {
     long startTime = System.nanoTime();
     FilesystemValueChecker fsvc =
@@ -701,7 +704,10 @@
     BatchStat batchStatter = outputService == null ? null : outputService.getBatchStatter();
     recordingDiffer.invalidate(
         fsvc.getDirtyActionValues(
-            memoizingEvaluator.getValues(), batchStatter, modifiedOutputFiles));
+            memoizingEvaluator.getValues(),
+            batchStatter,
+            modifiedOutputFiles,
+            trustRemoteArtifacts));
     modifiedFiles += fsvc.getNumberOfModifiedOutputFiles();
     outputDirtyFiles += fsvc.getNumberOfModifiedOutputFiles();
     modifiedFilesDuringPreviousBuild += fsvc.getNumberOfModifiedOutputFilesDuringPreviousBuild();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index f49e829..ca32009 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -2816,7 +2816,9 @@
   }
 
   public abstract void detectModifiedOutputFiles(
-      ModifiedFileSet modifiedOutputFiles, @Nullable Range<Long> lastExecutionTimeRange)
+      ModifiedFileSet modifiedOutputFiles,
+      @Nullable Range<Long> lastExecutionTimeRange,
+      boolean trustRemoteArtifacts)
       throws AbruptExitException, InterruptedException;
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java b/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
index 3bb5234..03b894c 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/OutputService.java
@@ -81,11 +81,19 @@
   String getFilesSystemName();
 
   /**
+   * Returns true if Bazel should trust (and not verify) build artifacts that were last seen
+   * remotely and do not exist locally.
+   */
+  public default boolean shouldTrustRemoteArtifacts() {
+    return true;
+  }
+
+  /**
    * Start the build.
    *
    * @param buildId the UUID build identifier
-   * @param finalizeActions whether this build is finalizing actions so that the output service
-   *                        can track output tree modifications
+   * @param finalizeActions whether this build is finalizing actions so that the output service can
+   *     track output tree modifications
    * @return a ModifiedFileSet of changed output files.
    * @throws BuildFailedException if build preparation failed
    * @throws InterruptedException
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
index 2f75fb4..3dd22de 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/FilesystemValueCheckerTest.java
@@ -430,7 +430,10 @@
     assertThat(
             new FilesystemValueChecker(tsgm, null)
                 .getDirtyActionValues(
-                    evaluator.getValues(), batchStatter, ModifiedFileSet.EVERYTHING_MODIFIED))
+                    evaluator.getValues(),
+                    batchStatter,
+                    ModifiedFileSet.EVERYTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ false))
         .isEmpty();
 
     tsgm.waitForTimestampGranularity(OutErr.SYSTEM_OUT_ERR);
@@ -442,14 +445,18 @@
     assertThat(
             new FilesystemValueChecker(tsgm, null)
                 .getDirtyActionValues(
-                    evaluator.getValues(), batchStatter, ModifiedFileSet.EVERYTHING_MODIFIED))
+                    evaluator.getValues(),
+                    batchStatter,
+                    ModifiedFileSet.EVERYTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKey);
     assertThat(
             new FilesystemValueChecker(tsgm, null)
                 .getDirtyActionValues(
                     evaluator.getValues(),
                     batchStatter,
-                    new ModifiedFileSet.Builder().modify(file.getExecPath()).build()))
+                    new ModifiedFileSet.Builder().modify(file.getExecPath()).build(),
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKey);
     assertThat(
             new FilesystemValueChecker(tsgm, null)
@@ -458,12 +465,16 @@
                     batchStatter,
                     new ModifiedFileSet.Builder()
                         .modify(file.getExecPath().getParentDirectory())
-                        .build()))
+                        .build(),
+                    /* trustRemoteArtifacts= */ false))
         .isEmpty();
     assertThat(
             new FilesystemValueChecker(tsgm, null)
                 .getDirtyActionValues(
-                    evaluator.getValues(), batchStatter, ModifiedFileSet.NOTHING_MODIFIED))
+                    evaluator.getValues(),
+                    batchStatter,
+                    ModifiedFileSet.NOTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ false))
         .isEmpty();
   }
 
@@ -528,19 +539,35 @@
             .setEventHander(NullEventHandler.INSTANCE)
             .build();
     assertThat(driver.evaluate(ImmutableList.<SkyKey>of(), evaluationContext).hasError()).isFalse();
-    assertThat(new FilesystemValueChecker(null, null).getDirtyActionValues(evaluator.getValues(),
-        batchStatter, ModifiedFileSet.EVERYTHING_MODIFIED)).isEmpty();
+    assertThat(
+            new FilesystemValueChecker(null, null)
+                .getDirtyActionValues(
+                    evaluator.getValues(),
+                    batchStatter,
+                    ModifiedFileSet.EVERYTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ false))
+        .isEmpty();
 
     // Touching the TreeArtifact directory should have no effect
     FileSystemUtils.touchFile(out1.getPath());
     assertThat(
-        new FilesystemValueChecker(null, null).getDirtyActionValues(evaluator.getValues(),
-            batchStatter, ModifiedFileSet.EVERYTHING_MODIFIED)).isEmpty();
+            new FilesystemValueChecker(null, null)
+                .getDirtyActionValues(
+                    evaluator.getValues(),
+                    batchStatter,
+                    ModifiedFileSet.EVERYTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ false))
+        .isEmpty();
     // Neither should touching a subdirectory.
     FileSystemUtils.touchFile(out2.getPath().getChild("subdir"));
     assertThat(
-        new FilesystemValueChecker(null, null).getDirtyActionValues(evaluator.getValues(),
-            batchStatter, ModifiedFileSet.EVERYTHING_MODIFIED)).isEmpty();
+            new FilesystemValueChecker(null, null)
+                .getDirtyActionValues(
+                    evaluator.getValues(),
+                    batchStatter,
+                    ModifiedFileSet.EVERYTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ false))
+        .isEmpty();
 
     /* **** Tests for directories **** */
 
@@ -551,7 +578,8 @@
                 .getDirtyActionValues(
                     evaluator.getValues(),
                     batchStatter,
-                    new ModifiedFileSet.Builder().modify(outEmpty.getExecPath()).build()))
+                    new ModifiedFileSet.Builder().modify(outEmpty.getExecPath()).build(),
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKeyEmpty);
     // Symbolic links should count as dirty
     Path dummyEmptyDir = fs.getPath("/bin").getRelative("symlink");
@@ -562,7 +590,8 @@
                 .getDirtyActionValues(
                     evaluator.getValues(),
                     batchStatter,
-                    new ModifiedFileSet.Builder().modify(outEmpty.getExecPath()).build()))
+                    new ModifiedFileSet.Builder().modify(outEmpty.getExecPath()).build(),
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKeyEmpty);
 
     // We're done fiddling with this... restore the original state
@@ -580,7 +609,8 @@
                 .getDirtyActionValues(
                     evaluator.getValues(),
                     batchStatter,
-                    new ModifiedFileSet.Builder().modify(file11.getExecPath()).build()))
+                    new ModifiedFileSet.Builder().modify(file11.getExecPath()).build(),
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKey1);
 
     // Test that directory contents (and nested contents) matter
@@ -598,7 +628,10 @@
     assertThat(
             new FilesystemValueChecker(null, null)
                 .getDirtyActionValues(
-                    evaluator.getValues(), batchStatter, ModifiedFileSet.EVERYTHING_MODIFIED))
+                    evaluator.getValues(),
+                    batchStatter,
+                    ModifiedFileSet.EVERYTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKey1, actionKey2, actionKeyEmpty);
     assertThat(
             new FilesystemValueChecker(null, null)
@@ -609,7 +642,8 @@
                         .modify(file21.getExecPath())
                         .modify(out1new.getExecPath())
                         .modify(outEmptyNew.getExecPath())
-                        .build()))
+                        .build(),
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKey1, actionKey2, actionKeyEmpty);
     // We also check that if the modified file set does not contain our modified files on disk,
     // we are not going to check and return them.
@@ -621,7 +655,8 @@
                     new ModifiedFileSet.Builder()
                         .modify(file21.getExecPath())
                         .modify(outEmptyNew.getExecPath())
-                        .build()))
+                        .build(),
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKey2, actionKeyEmpty);
     assertThat(
             new FilesystemValueChecker(null, null)
@@ -631,7 +666,8 @@
                     new ModifiedFileSet.Builder()
                         .modify(file21.getExecPath())
                         .modify(out1new.getExecPath())
-                        .build()))
+                        .build(),
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKey1, actionKey2);
     // Check modifying the last (lexicographically) tree artifact.
     last.getPath().delete();
@@ -644,7 +680,8 @@
                         .modify(file21.getExecPath())
                         .modify(out1new.getExecPath())
                         .modify(last.getExecPath())
-                        .build()))
+                        .build(),
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKey1, actionKey2, actionKeyLast);
     // Check ModifiedFileSet without the last (lexicographically) tree artifact.
     assertThat(
@@ -655,7 +692,8 @@
                     new ModifiedFileSet.Builder()
                         .modify(file21.getExecPath())
                         .modify(out1new.getExecPath())
-                        .build()))
+                        .build(),
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKey1, actionKey2);
     // Restore
     last.getPath().delete();
@@ -663,8 +701,13 @@
     // We add a test for NOTHING_MODIFIED, because FileSystemValueChecker doesn't
     // pay attention to file sets for TreeArtifact directory listings.
     assertThat(
-        new FilesystemValueChecker(null, null).getDirtyActionValues(evaluator.getValues(),
-            batchStatter, ModifiedFileSet.NOTHING_MODIFIED)).isEmpty();
+            new FilesystemValueChecker(null, null)
+                .getDirtyActionValues(
+                    evaluator.getValues(),
+                    batchStatter,
+                    ModifiedFileSet.NOTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ false))
+        .isEmpty();
   }
 
   private Artifact createDerivedArtifact(String relPath) throws IOException {
@@ -890,7 +933,8 @@
   @Test
   public void testRemoteAndLocalArtifacts() throws Exception {
     // Test that injected remote artifacts are trusted by the FileSystemValueChecker
-    // and that local files always takes preference over remote files.
+    // if it is configured to trust remote artifacts, and that local files always take precedence
+    // over remote files.
     ActionLookupKey actionLookupKey =
         new ActionLookupKey() {
           @Override
@@ -926,7 +970,8 @@
                 .getDirtyActionValues(
                     evaluator.getValues(),
                     /* batchStatter= */ null,
-                    ModifiedFileSet.EVERYTHING_MODIFIED))
+                    ModifiedFileSet.EVERYTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ true))
         .isEmpty();
 
     // Create the "out1" artifact on the filesystem and test that it invalidates the generating
@@ -937,7 +982,8 @@
                 .getDirtyActionValues(
                     evaluator.getValues(),
                     /* batchStatter= */ null,
-                    ModifiedFileSet.EVERYTHING_MODIFIED))
+                    ModifiedFileSet.EVERYTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ true))
         .containsExactly(actionKey1);
   }
 
@@ -980,7 +1026,8 @@
                 .getDirtyActionValues(
                     evaluator.getValues(),
                     /* batchStatter= */ null,
-                    ModifiedFileSet.EVERYTHING_MODIFIED))
+                    ModifiedFileSet.EVERYTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ false))
         .isEmpty();
 
     // Create dir/foo on the local disk and test that it invalidates the associated sky key.
@@ -992,7 +1039,8 @@
                 .getDirtyActionValues(
                     evaluator.getValues(),
                     /* batchStatter= */ null,
-                    ModifiedFileSet.EVERYTHING_MODIFIED))
+                    ModifiedFileSet.EVERYTHING_MODIFIED,
+                    /* trustRemoteArtifacts= */ false))
         .containsExactly(actionKey);
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
index 901a0c1..2866ae1 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorTest.java
@@ -449,10 +449,9 @@
 
   private Collection<SkyKey> dirtyValues() throws InterruptedException {
     Diff diff =
-        new FilesystemValueChecker(
-                new TimestampGranularityMonitor(BlazeClock.instance()),
-                null)
-            .getDirtyKeys(skyframeExecutor.getEvaluatorForTesting().getValues(),
+        new FilesystemValueChecker(new TimestampGranularityMonitor(BlazeClock.instance()), null)
+            .getDirtyKeys(
+                skyframeExecutor.getEvaluatorForTesting().getValues(),
                 new BasicFilesystemDirtinessChecker());
     return ImmutableList.<SkyKey>builder()
         .addAll(diff.changedKeysWithoutNewValues())
@@ -1584,7 +1583,8 @@
                     builtAspects,
                     options,
                     null,
-                    null));
+                    null,
+                    /* trustRemoteArtifacts= */ false));
     // The catastrophic exception should be propagated into the BuildFailedException whether or not
     // --keep_going is set.
     assertThat(e.getDetailedExitCode().getExitCode())
@@ -1718,7 +1718,8 @@
                     builtAspects,
                     options,
                     null,
-                    null));
+                    null,
+                    /* trustRemoteArtifacts= */ false));
     // The catastrophic exception should be propagated into the BuildFailedException whether or not
     // --keep_going is set.
     assertThat(e.getDetailedExitCode().getExitCode())
@@ -1849,7 +1850,8 @@
                     new TopLevelArtifactContext(
                         /*runTestsExclusively=*/ false,
                         false,
-                        OutputGroupInfo.determineOutputGroups(ImmutableList.of(), true))));
+                        OutputGroupInfo.determineOutputGroups(ImmutableList.of(), true)),
+                    /* trustRemoteArtifacts= */ false));
     // The catastrophic exception should be propagated into the BuildFailedException whether or not
     // --keep_going is set.
     assertThat(e.getDetailedExitCode().getExitCode())
@@ -1957,7 +1959,8 @@
                     builtAspects,
                     options,
                     null,
-                    null));
+                    null,
+                    /* trustRemoteArtifacts= */ false));
     // The catastrophic exception should be propagated into the BuildFailedException whether or not
     // --keep_going is set.
     assertThat(e.getDetailedExitCode().getExitCode())
@@ -2070,7 +2073,8 @@
                     builtAspects,
                     options,
                     null,
-                    null));
+                    null,
+                    /* trustRemoteArtifacts= */ false));
     // The exit code should be propagated into the BuildFailedException whether or not --keep_going
     // is set.
     assertThat(e.getDetailedExitCode().getExitCode()).isEqualTo(USER_EXIT_CODE);
@@ -2167,7 +2171,8 @@
                     builtAspects,
                     options,
                     null,
-                    null));
+                    null,
+                    /* trustRemoteArtifacts= */ false));
     // The exit code should be propagated into the BuildFailedException whether or not --keep_going
     // is set.
     assertThat(e.getDetailedExitCode().getExitCode()).isEqualTo(INFRA_EXIT_CODE);
@@ -2242,7 +2247,8 @@
         ImmutableSet.of(),
         options,
         null,
-        null);
+        null,
+        /* trustRemoteArtifacts= */ false);
     assertContainsEventRegex(eventCollector, ".*during scanning.*\n.*Scanning.*\n.*Test dir/top.*");
     assertNotContainsEventRegex(
         eventCollector, ".*after scanning.*\n.*Scanning.*\n.*Test dir/top.*");
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeAwareActionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeAwareActionTest.java
index a87adb8..6c64bc9 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeAwareActionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/SkyframeAwareActionTest.java
@@ -438,7 +438,8 @@
         null,
         options,
         null,
-        null);
+        null,
+        /* trustRemoteArtifacts= */ false);
 
     // Sanity check that our invalidation receiver is working correctly. We'll rely on it again.
     SkyKey actionKey = ActionLookupData.create(ACTION_LOOKUP_KEY, 0);
@@ -467,7 +468,8 @@
         null,
         options,
         null,
-        null);
+        null,
+        /* trustRemoteArtifacts= */ false);
 
     if (expectActionIs.dirtied()) {
       assertThat(progressReceiver.wasInvalidated(actionKey)).isTrue();
@@ -880,6 +882,7 @@
         null,
         options,
         null,
-        null);
+        null,
+        /* trustRemoteArtifacts= */ false);
   }
 }
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 5bf03cb..1177827 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
@@ -309,7 +309,8 @@
           Set<AspectKey> builtAspects,
           OptionsProvider options,
           Range<Long> lastExecutionTimeRange,
-          TopLevelArtifactContext topLevelArtifactContext)
+          TopLevelArtifactContext topLevelArtifactContext,
+          boolean trustRemoteArtifacts)
           throws BuildFailedException, AbruptExitException, InterruptedException,
               TestExecException {
         skyframeActionExecutor.prepareForExecution(
@@ -473,7 +474,8 @@
           builtAspects,
           options,
           null,
-          null);
+          null,
+          /* trustRemoteArtifacts= */ false);
     } finally {
       tsgm.waitForTimestampGranularity(reporter.getOutErr());
     }