diff --git a/core/modules/dblog/tests/src/Functional/DbLogResourceTest.php b/core/modules/dblog/tests/src/Functional/DbLogResourceTest.php
index 0ec297bb9d..4d36e79ee0 100644
--- a/core/modules/dblog/tests/src/Functional/DbLogResourceTest.php
+++ b/core/modules/dblog/tests/src/Functional/DbLogResourceTest.php
@@ -77,7 +77,7 @@ public function testWatchdog() {
     $request_options = $this->getAuthenticationRequestOptions('GET');
 
     $response = $this->request('GET', $url, $request_options);
-    $this->assertResourceErrorResponse(403, "The 'restful get dblog' permission is required.", $response, ['4xx-response', 'http_response'], ['user.permissions'], FALSE, FALSE);
+    $this->assertResourceErrorResponse(403, "The 'restful get dblog' permission is required.", $response, ['4xx-response', 'http_response'], ['user.permissions'], 'UNCACHEABLE (request policy)', FALSE);
 
     // Create a user account that has the required permissions to read
     // the watchdog resource via the REST API.
diff --git a/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php b/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php
index 22b51b9ea5..f355dd7c2d 100644
--- a/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php
+++ b/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php
@@ -151,11 +151,18 @@ public function onRequest(RequestEvent $event) {
   public function onResponse(ResponseEvent $event) {
     $response = $event->getResponse();
 
+    // Don't indicate non-cacheability on responses to uncacheable requests.
+    // @see https://tools.ietf.org/html/rfc7231#section-4.2.3
+    if (!$event->getRequest()->isMethodCacheable()) {
+      return;
+    }
+
     // Dynamic Page Cache only works with cacheable responses. It does not work
     // with plain Response objects. (Dynamic Page Cache needs to be able to
     // access and modify the cacheability metadata associated with the
     // response.)
     if (!$response instanceof CacheableResponseInterface) {
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (no cacheability)');
       return;
     }
 
@@ -167,7 +174,7 @@ public function onResponse(ResponseEvent $event) {
     // There's no work left to be done if this is an uncacheable response.
     if (!$this->shouldCacheResponse($response)) {
       // The response is uncacheable, mark it as such.
-      $response->headers->set(self::HEADER, 'UNCACHEABLE');
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (poor cacheability)');
       return;
     }
 
@@ -190,7 +197,12 @@ public function onResponse(ResponseEvent $event) {
     // Don't cache the response if the Dynamic Page Cache request & response
     // policies are not met.
     // @see onRequest()
-    if ($this->requestPolicyResults[$request] === RequestPolicyInterface::DENY || $this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
+    if ($this->requestPolicyResults[$request] === RequestPolicyInterface::DENY) {
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (request policy)');
+      return;
+    }
+    if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (response policy)');
       return;
     }
 
diff --git a/core/modules/editor/tests/src/Kernel/QuickEditIntegrationTest.php b/core/modules/editor/tests/src/Kernel/QuickEditIntegrationTest.php
index 0f3a03165e..97406aa113 100644
--- a/core/modules/editor/tests/src/Kernel/QuickEditIntegrationTest.php
+++ b/core/modules/editor/tests/src/Kernel/QuickEditIntegrationTest.php
@@ -169,7 +169,7 @@ public function testMetadata() {
     $this->editorManager = $this->container->get('plugin.manager.quickedit.editor');
     $this->accessChecker = new MockQuickEditEntityFieldAccessCheck();
     $this->editorSelector = $this->container->get('quickedit.editor.selector');
-    $this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
+    $this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager, new AnonymousUserSession());
 
     // Create an entity with values for the field.
     $entity = EntityTest::create();
diff --git a/core/modules/jsonapi/tests/src/Functional/NodeTest.php b/core/modules/jsonapi/tests/src/Functional/NodeTest.php
index 6800460182..1d33acccc6 100644
--- a/core/modules/jsonapi/tests/src/Functional/NodeTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/NodeTest.php
@@ -342,7 +342,7 @@ public function testGetIndividual() {
       $response,
       ['4xx-response', 'http_response', 'node:1'],
       ['url.query_args:resourceVersion', 'url.site', 'user.permissions'],
-      FALSE,
+      'UNCACHEABLE (request policy)',
       'MISS'
     );
     /* $this->assertResourceErrorResponse(403, 'The current user is not allowed to GET the selected resource.', $response, '/data'); */
@@ -354,7 +354,7 @@ public function testGetIndividual() {
     // context to be optimized away.
     $expected_cache_contexts = Cache::mergeContexts($this->getExpectedCacheContexts(), ['user']);
     $expected_cache_contexts = array_diff($expected_cache_contexts, ['user.permissions']);
-    $this->assertResourceResponse(200, FALSE, $response, $this->getExpectedCacheTags(), $expected_cache_contexts, FALSE, 'UNCACHEABLE');
+    $this->assertResourceResponse(200, FALSE, $response, $this->getExpectedCacheTags(), $expected_cache_contexts, 'UNCACHEABLE (request policy)', 'UNCACHEABLE (poor cacheability)');
   }
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php b/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php
index 7d29c2057e..40bc74a184 100644
--- a/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php
+++ b/core/modules/jsonapi/tests/src/Functional/ResourceTestBase.php
@@ -920,7 +920,7 @@ public function testGetIndividual() {
       $expected_403_cacheability = $this->getExpectedUnauthorizedAccessCacheability();
       $reason = $this->getExpectedUnauthorizedAccessMessage('GET');
       $message = trim("The current user is not allowed to GET the selected resource. $reason");
-      $this->assertResourceErrorResponse(403, $message, $url, $response, '/data', $expected_403_cacheability->getCacheTags(), $expected_403_cacheability->getCacheContexts(), FALSE, 'MISS');
+      $this->assertResourceErrorResponse(403, $message, $url, $response, '/data', $expected_403_cacheability->getCacheTags(), $expected_403_cacheability->getCacheContexts(), 'UNCACHEABLE (request policy)', 'MISS');
       $this->assertArrayNotHasKey('Link', $response->getHeaders());
     }
     else {
@@ -929,8 +929,8 @@ public function testGetIndividual() {
       $expected_document['data']['attributes'] = array_intersect_key($expected_document['data']['attributes'], [$label_field_name => TRUE]);
       unset($expected_document['data']['relationships']);
       // MISS or UNCACHEABLE depends on data. It must not be HIT.
-      $dynamic_cache_label_only = !empty(array_intersect(['user', 'session'], $this->getExpectedCacheContexts([$label_field_name]))) ? 'UNCACHEABLE' : 'MISS';
-      $this->assertResourceResponse(200, $expected_document, $response, $this->getExpectedCacheTags(), $this->getExpectedCacheContexts([$label_field_name]), FALSE, $dynamic_cache_label_only);
+      $dynamic_cache_label_only = !empty(array_intersect(['user', 'session'], $this->getExpectedCacheContexts([$label_field_name]))) ? 'UNCACHEABLE (poor cacheability)' : 'MISS';
+      $this->assertResourceResponse(200, $expected_document, $response, $this->getExpectedCacheTags(), $this->getExpectedCacheContexts([$label_field_name]), 'UNCACHEABLE (request policy)', $dynamic_cache_label_only);
     }
 
     $this->setUpAuthorization('GET');
@@ -956,20 +956,20 @@ public function testGetIndividual() {
         ],
       ],
     ];
-    $this->assertResourceResponse(400, $expected_document, $response, ['4xx-response', 'http_response'], ['url.query_args', 'url.site'], FALSE, 'MISS');
+    $this->assertResourceResponse(400, $expected_document, $response, ['4xx-response', 'http_response'], ['url.query_args', 'url.site'], 'UNCACHEABLE (request policy)', 'MISS');
 
     // 200 for well-formed HEAD request.
     $response = $this->request('HEAD', $url, $request_options);
     // MISS or UNCACHEABLE depends on data. It must not be HIT.
-    $dynamic_cache = !empty(array_intersect(['user', 'session'], $this->getExpectedCacheContexts())) ? 'UNCACHEABLE' : 'MISS';
-    $this->assertResourceResponse(200, NULL, $response, $this->getExpectedCacheTags(), $this->getExpectedCacheContexts(), FALSE, $dynamic_cache);
+    $dynamic_cache = !empty(array_intersect(['user', 'session'], $this->getExpectedCacheContexts())) ? 'UNCACHEABLE (poor cacheability)' : 'MISS';
+    $this->assertResourceResponse(200, NULL, $response, $this->getExpectedCacheTags(), $this->getExpectedCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache);
     $head_headers = $response->getHeaders();
 
     // 200 for well-formed GET request. Page Cache hit because of HEAD request.
     // Same for Dynamic Page Cache hit.
     $response = $this->request('GET', $url, $request_options);
 
-    $this->assertResourceResponse(200, $this->getExpectedDocument(), $response, $this->getExpectedCacheTags(), $this->getExpectedCacheContexts(), FALSE, $dynamic_cache === 'MISS' ? 'HIT' : 'UNCACHEABLE');
+    $this->assertResourceResponse(200, $this->getExpectedDocument(), $response, $this->getExpectedCacheTags(), $this->getExpectedCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache === 'MISS' ? 'HIT' : 'UNCACHEABLE (poor cacheability)');
     // Assert that Dynamic Page Cache did not store a ResourceResponse object,
     // which needs serialization after every cache hit. Instead, it should
     // contain a flattened response. Otherwise performance suffers.
@@ -1051,12 +1051,12 @@ public function testGetIndividual() {
     $message_url = clone $url;
     $path = str_replace($random_uuid, '{entity}', $message_url->setAbsolute()->setOptions(['base_url' => '', 'query' => []])->toString());
     $message = 'The "entity" parameter was not converted for the path "' . $path . '" (route name: "jsonapi.' . static::$resourceTypeName . '.individual")';
-    $this->assertResourceErrorResponse(404, $message, $url, $response, FALSE, ['4xx-response', 'http_response'], ['url.site'], FALSE, 'UNCACHEABLE');
+    $this->assertResourceErrorResponse(404, $message, $url, $response, FALSE, ['4xx-response', 'http_response'], ['url.site'], 'UNCACHEABLE (request policy)', 'UNCACHEABLE (poor cacheability)');
 
     // DX: when Accept request header is missing, still 404, same response.
     unset($request_options[RequestOptions::HEADERS]['Accept']);
     $response = $this->request('GET', $url, $request_options);
-    $this->assertResourceErrorResponse(404, $message, $url, $response, FALSE, ['4xx-response', 'http_response'], ['url.site'], FALSE, 'UNCACHEABLE');
+    $this->assertResourceErrorResponse(404, $message, $url, $response, FALSE, ['4xx-response', 'http_response'], ['url.site'], 'UNCACHEABLE (request policy)', 'UNCACHEABLE (poor cacheability)');
   }
 
   /**
@@ -1078,7 +1078,7 @@ public function testCollection() {
     $response = $this->request('HEAD', $collection_url, $request_options);
     // MISS or UNCACHEABLE depends on the collection data. It must not be HIT.
     $dynamic_cache = $expected_cacheability->getCacheMaxAge() === 0 ? 'UNCACHEABLE' : 'MISS';
-    $this->assertResourceResponse(200, NULL, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache);
+    $this->assertResourceResponse(200, NULL, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache);
 
     // Different databases have different sort orders, so a sort is required so
     // test expectations do not need to vary per database.
@@ -1092,7 +1092,7 @@ public function testCollection() {
     $expected_cacheability = $expected_response->getCacheableMetadata();
     $expected_document = $expected_response->getResponseData();
     $response = $this->request('GET', $collection_url, $request_options);
-    $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache);
+    $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache);
 
     $this->setUpAuthorization('GET');
 
@@ -1100,7 +1100,7 @@ public function testCollection() {
     $expected_response = $this->getExpectedCollectionResponse($entity_collection, $collection_url->toString(), $request_options);
     $expected_cacheability = $expected_response->getCacheableMetadata();
     $response = $this->request('HEAD', $collection_url, $request_options);
-    $this->assertResourceResponse(200, NULL, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache);
+    $this->assertResourceResponse(200, NULL, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache);
 
     // 200 for well-formed GET request.
     $expected_response = $this->getExpectedCollectionResponse($entity_collection, $collection_url->toString(), $request_options);
@@ -1108,8 +1108,8 @@ public function testCollection() {
     $expected_document = $expected_response->getResponseData();
     $response = $this->request('GET', $collection_url, $request_options);
     // Dynamic Page Cache HIT unless the HEAD request was UNCACHEABLE.
-    $dynamic_cache = $dynamic_cache === 'UNCACHEABLE' ? 'UNCACHEABLE' : 'HIT';
-    $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache);
+    $dynamic_cache = $expected_cacheability->getCacheMaxAge() === 0 || !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts())) ? 'UNCACHEABLE (poor cacheability)' : 'MISS';
+    $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache);
 
     if ($this->entity instanceof FieldableEntityInterface) {
       // 403 for filtering on an unauthorized field on the base resource type.
@@ -1132,14 +1132,14 @@ public function testCollection() {
         'url.site',
         'user.permissions',
       ];
-      $this->assertResourceErrorResponse(403, $expected_error_message, $unauthorized_filter_url, $response, FALSE, $expected_cache_tags, $expected_cache_contexts, FALSE, 'MISS');
+      $this->assertResourceErrorResponse(403, $expected_error_message, $unauthorized_filter_url, $response, FALSE, $expected_cache_tags, $expected_cache_contexts, 'UNCACHEABLE (request policy)', 'MISS');
 
       $this->grantPermissionsToTestedRole(['field_jsonapi_test_entity_ref view access']);
 
       // 403 for filtering on an unauthorized field on a related resource type.
       $response = $this->request('GET', $unauthorized_filter_url, $request_options);
       $expected_error_message = "The current user is not authorized to filter by the `status` field, given in the path `field_jsonapi_test_entity_ref.entity:user.status`.";
-      $this->assertResourceErrorResponse(403, $expected_error_message, $unauthorized_filter_url, $response, FALSE, $expected_cache_tags, $expected_cache_contexts, FALSE, 'MISS');
+      $this->assertResourceErrorResponse(403, $expected_error_message, $unauthorized_filter_url, $response, FALSE, $expected_cache_tags, $expected_cache_contexts, 'UNCACHEABLE (request policy)', 'MISS');
     }
 
     // Remove an entity from the collection, then filter it out.
@@ -1163,8 +1163,8 @@ public function testCollection() {
     $expected_document = $expected_response->getResponseData();
     $response = $this->request('GET', $filtered_collection_url, $request_options);
     // MISS or UNCACHEABLE depends on the collection data. It must not be HIT.
-    $dynamic_cache = $expected_cacheability->getCacheMaxAge() === 0 || !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts())) ? 'UNCACHEABLE' : 'MISS';
-    $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache);
+    $dynamic_cache = $expected_cacheability->getCacheMaxAge() === 0 || !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts())) ? 'UNCACHEABLE (poor cacheability)' : 'MISS';
+    $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache);
 
     // Filtered collection with includes.
     $relationship_field_names = array_reduce($filtered_entity_collection, function ($relationship_field_names, $entity) {
@@ -1179,8 +1179,8 @@ public function testCollection() {
     $expected_document = $expected_response->getResponseData();
     $response = $this->request('GET', $filtered_collection_include_url, $request_options);
     // MISS or UNCACHEABLE depends on the included data. It must not be HIT.
-    $dynamic_cache = $expected_cacheability->getCacheMaxAge() === 0 || !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts())) ? 'UNCACHEABLE' : 'MISS';
-    $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache);
+    $dynamic_cache = $expected_cacheability->getCacheMaxAge() === 0 || !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts())) ? 'UNCACHEABLE (poor cacheability)' : 'MISS';
+    $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache);
 
     // If the response should vary by a user's authorizations, grant permissions
     // for the included resources and execute another request.
@@ -1199,8 +1199,8 @@ public function testCollection() {
       $response = $this->request('GET', $filtered_collection_include_url, $request_options);
       $requires_include_only_permissions = !empty($flattened_permissions);
       $uncacheable = $expected_cacheability->getCacheMaxAge() === 0 || !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts()));
-      $dynamic_cache = !$uncacheable ? $requires_include_only_permissions ? 'MISS' : 'HIT' : 'UNCACHEABLE';
-      $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache);
+      $dynamic_cache = !$uncacheable ? $requires_include_only_permissions ? 'MISS' : 'HIT' : 'UNCACHEABLE (poor cacheability)';
+      $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache);
     }
 
     // Sorted collection with includes.
@@ -1217,8 +1217,8 @@ public function testCollection() {
     $expected_document = $expected_response->getResponseData();
     $response = $this->request('GET', $sorted_collection_include_url, $request_options);
     // MISS or UNCACHEABLE depends on the included data. It must not be HIT.
-    $dynamic_cache = $expected_cacheability->getCacheMaxAge() === 0 ? 'UNCACHEABLE' : 'MISS';
-    $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache);
+    $dynamic_cache = $expected_cacheability->getCacheMaxAge() === 0 || !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts())) ? 'UNCACHEABLE (poor cacheability)' : 'MISS';
+    $this->assertResourceResponse(200, $expected_document, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache);
   }
 
   /**
@@ -1409,7 +1409,7 @@ protected function doTestRelationshipGet(array $request_options) {
         $actual_response,
         $expected_cacheability->getCacheTags(),
         $expected_cacheability->getCacheContexts(),
-        FALSE,
+        'UNCACHEABLE (request policy)',
         $expected_resource_response->isSuccessful() ? 'MISS' : FALSE
       );
     }
@@ -2610,21 +2610,21 @@ protected function doTestSparseFieldSets(Url $url, array $request_options) {
       $response = $this->request('GET', $url, $request_options);
       // Dynamic Page Cache MISS because cache should vary based on the 'field'
       // query param. (Or uncacheable if expensive cache context.)
-      $dynamic_cache = !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts())) ? 'UNCACHEABLE' : 'MISS';
+      $dynamic_cache = !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts())) ? 'UNCACHEABLE (poor cacheability)' : 'MISS';
       $this->assertResourceResponse(
         200,
         $expected_document,
         $response,
         $expected_cacheability->getCacheTags(),
         $expected_cacheability->getCacheContexts(),
-        FALSE,
+        'UNCACHEABLE (request policy)',
         $dynamic_cache
       );
     }
     // Test Dynamic Page Cache HIT for a query with the same field set (unless
     // expensive cache context is present).
     $response = $this->request('GET', $url, $request_options);
-    $this->assertResourceResponse(200, FALSE, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache === 'MISS' ? 'HIT' : 'UNCACHEABLE');
+    $this->assertResourceResponse(200, FALSE, $response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), 'UNCACHEABLE (request policy)', $dynamic_cache === 'MISS' ? 'HIT' : 'UNCACHEABLE (poor cacheability)');
   }
 
   /**
@@ -2676,7 +2676,7 @@ protected function doTestIncluded(Url $url, array $request_options) {
         $actual_response,
         $expected_cacheability->getCacheTags(),
         $expected_cacheability->getCacheContexts(),
-        FALSE,
+        'UNCACHEABLE (request policy)',
         $dynamic_cache
       );
     }
@@ -3117,7 +3117,7 @@ public function testRevisions() {
       $expected_cacheability = $expected_response->getCacheableMetadata();
       $expected_document['links']['self']['href'] = $related_url->toString();
       // MISS or UNCACHEABLE depends on data. It must not be HIT.
-      $dynamic_cache = !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts())) ? 'UNCACHEABLE' : 'MISS';
+      $dynamic_cache = !empty(array_intersect(['user', 'session'], $expected_cacheability->getCacheContexts())) ? 'UNCACHEABLE (poor cacheability)' : 'MISS';
       $this->assertResourceResponse(200, $expected_document, $actual_response, $expected_cacheability->getCacheTags(), $expected_cacheability->getCacheContexts(), FALSE, $dynamic_cache);
     }
 
diff --git a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
index fadfe3b99a..552101e4c2 100644
--- a/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
+++ b/core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.php
@@ -83,7 +83,7 @@ public function providerTestLayoutSectionFormatter() {
       ],
       'user',
       'user:2',
-      'UNCACHEABLE',
+      'UNCACHEABLE (poor cacheability)',
     ];
     $data['block_with_entity_context'] = [
       [
@@ -177,7 +177,7 @@ public function testLayoutSectionFormatter($layout_data, $expected_selector, $ex
     $this->assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, $expected_dynamic_cache);
 
     $this->drupalGet($canonical_url->toString() . '/layout');
-    $this->assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, 'UNCACHEABLE');
+    $this->assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts, $expected_cache_tags, 'UNCACHEABLE (poor cacheability)');
   }
 
   /**
@@ -198,14 +198,14 @@ public function testLayoutSectionFormatterAccess() {
     $this->container->get('state')->set('test_block_access', FALSE);
 
     $this->drupalGet($node->toUrl('canonical'));
-    $this->assertLayoutSection('.layout--onecol', NULL, '', '', 'UNCACHEABLE');
+    $this->assertLayoutSection('.layout--onecol', NULL, '', '', 'UNCACHEABLE (poor cacheability)');
     // Ensure the block was not rendered.
     $this->assertSession()->pageTextNotContains('Hello test world');
 
     // Grant access to the block, and ensure it was rendered.
     $this->container->get('state')->set('test_block_access', TRUE);
     $this->drupalGet($node->toUrl('canonical'));
-    $this->assertLayoutSection('.layout--onecol', 'Hello test world', '', '', 'UNCACHEABLE');
+    $this->assertLayoutSection('.layout--onecol', 'Hello test world', '', '', 'UNCACHEABLE (poor cacheability)');
   }
 
   /**
@@ -291,7 +291,8 @@ public function testLayoutDeletingBundle() {
    * @param string $expected_cache_tags
    *   A string of cache tags to be found in the header.
    * @param string $expected_dynamic_cache
-   *   The expected dynamic cache header. Either 'HIT', 'MISS' or 'UNCACHEABLE'.
+   *   The expected dynamic cache header. Either 'HIT', 'MISS' or
+   *   'UNCACHEABLE (poor cacheability)'.
    */
   protected function assertLayoutSection($expected_selector, $expected_content, $expected_cache_contexts = '', $expected_cache_tags = '', $expected_dynamic_cache = 'MISS') {
     $assert_session = $this->assertSession();
diff --git a/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php b/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php
index 8567298011..8a2fd73b5a 100644
--- a/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php
+++ b/core/modules/node/tests/src/Functional/NodeBlockFunctionalTest.php
@@ -177,7 +177,7 @@ public function testRecentNodeBlock() {
     $this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'session', 'theme', 'url.path', 'url.query_args', 'user', 'route']);
 
     // The node/add/article page is an admin path and currently uncacheable.
-    $this->assertSession()->responseHeaderEquals('X-Drupal-Dynamic-Cache', 'UNCACHEABLE');
+    $this->assertSame('UNCACHEABLE (poor cacheability)', $this->getSession()->getResponseHeader('X-Drupal-Dynamic-Cache'));
 
     $this->drupalGet('node/' . $node1->id());
     // Check that block is displayed on the node page when node is of type
diff --git a/core/modules/page_cache/src/StackMiddleware/PageCache.php b/core/modules/page_cache/src/StackMiddleware/PageCache.php
index ba07414d63..655f4e8bf6 100644
--- a/core/modules/page_cache/src/StackMiddleware/PageCache.php
+++ b/core/modules/page_cache/src/StackMiddleware/PageCache.php
@@ -19,6 +19,11 @@
  */
 class PageCache implements HttpKernelInterface {
 
+  /**
+   * Name of Page Cache's response header.
+   */
+  const HEADER = 'X-Drupal-Cache';
+
   /**
    * The wrapped HTTP kernel.
    *
@@ -83,6 +88,11 @@ public function handle(Request $request, $type = self::MASTER_REQUEST, $catch =
     }
     else {
       $response = $this->pass($request, $type, $catch);
+      // Don't indicate non-cacheability on responses to uncacheable requests.
+      // @see https://tools.ietf.org/html/rfc7231#section-4.2.3
+      if ($request->isMethodCacheable()) {
+        $response->headers->set(self::HEADER, 'UNCACHEABLE (request policy)');
+      }
     }
 
     return $response;
@@ -122,7 +132,7 @@ protected function pass(Request $request, $type = self::MASTER_REQUEST, $catch =
    */
   protected function lookup(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
     if ($response = $this->get($request)) {
-      $response->headers->set('X-Drupal-Cache', 'HIT');
+      $response->headers->set(static::HEADER, 'HIT');
     }
     else {
       $response = $this->fetch($request, $type, $catch);
@@ -193,7 +203,7 @@ protected function fetch(Request $request, $type = self::MASTER_REQUEST, $catch
     // Only set the 'X-Drupal-Cache' header if caching is allowed for this
     // response.
     if ($this->storeResponse($request, $response)) {
-      $response->headers->set('X-Drupal-Cache', 'MISS');
+      $response->headers->set(static::HEADER, 'MISS');
     }
 
     return $response;
@@ -232,6 +242,7 @@ protected function storeResponse(Request $request, Response $response) {
     //   so by replacing/extending this middleware service or adding another
     //   one.
     if (!$response instanceof CacheableResponseInterface) {
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (no cacheability)');
       return FALSE;
     }
 
@@ -245,6 +256,7 @@ protected function storeResponse(Request $request, Response $response) {
 
     // Allow policy rules to further restrict which responses to cache.
     if ($this->responsePolicy->check($response, $request) === ResponsePolicyInterface::DENY) {
+      $response->headers->set(self::HEADER, 'UNCACHEABLE (response policy)');
       return FALSE;
     }
 
diff --git a/core/modules/quickedit/quickedit.services.yml b/core/modules/quickedit/quickedit.services.yml
index 3d7d934378..d4830b4d39 100644
--- a/core/modules/quickedit/quickedit.services.yml
+++ b/core/modules/quickedit/quickedit.services.yml
@@ -11,4 +11,4 @@ services:
     arguments: ['@plugin.manager.quickedit.editor', '@plugin.manager.field.formatter']
   quickedit.metadata.generator:
     class: Drupal\quickedit\MetadataGenerator
-    arguments: ['@access_check.quickedit.entity_field', '@quickedit.editor.selector', '@plugin.manager.quickedit.editor']
+    arguments: ['@access_check.quickedit.entity_field', '@quickedit.editor.selector', '@plugin.manager.quickedit.editor', '@current_user']
diff --git a/core/modules/quickedit/src/Access/QuickEditEntityFieldAccessCheck.php b/core/modules/quickedit/src/Access/QuickEditEntityFieldAccessCheck.php
index 5404b04885..f3dc2b8f21 100644
--- a/core/modules/quickedit/src/Access/QuickEditEntityFieldAccessCheck.php
+++ b/core/modules/quickedit/src/Access/QuickEditEntityFieldAccessCheck.php
@@ -26,22 +26,22 @@ class QuickEditEntityFieldAccessCheck implements AccessInterface, QuickEditEntit
    *
    * @return \Drupal\Core\Access\AccessResultInterface
    *   The access result.
-   *
-   * @todo Use the $account argument: https://www.drupal.org/node/2266809.
    */
   public function access(EntityInterface $entity, $field_name, $langcode, AccountInterface $account) {
     if (!$this->validateRequestAttributes($entity, $field_name, $langcode)) {
       return AccessResult::forbidden();
     }
 
-    return $this->accessEditEntityField($entity, $field_name);
+    return $this->accessEditEntityField($entity, $field_name, $account);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function accessEditEntityField(EntityInterface $entity, $field_name) {
-    return $entity->access('update', NULL, TRUE)->andIf($entity->get($field_name)->access('edit', NULL, TRUE));
+  public function accessEditEntityField(EntityInterface $entity, $field_name, AccountInterface $account = NULL) {
+    return $entity
+      ->access('update', $account, TRUE)
+      ->andIf($entity->get($field_name)->access('edit', $account, TRUE));
   }
 
   /**
diff --git a/core/modules/quickedit/src/Access/QuickEditEntityFieldAccessCheckInterface.php b/core/modules/quickedit/src/Access/QuickEditEntityFieldAccessCheckInterface.php
index 1428b4829a..0a8ca8028a 100644
--- a/core/modules/quickedit/src/Access/QuickEditEntityFieldAccessCheckInterface.php
+++ b/core/modules/quickedit/src/Access/QuickEditEntityFieldAccessCheckInterface.php
@@ -3,6 +3,7 @@
 namespace Drupal\quickedit\Access;
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Session\AccountInterface;
 
 /**
  * Access check for in-place editing entity fields.
@@ -16,10 +17,12 @@ interface QuickEditEntityFieldAccessCheckInterface {
    *   The entity.
    * @param string $field_name
    *   The field name.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   (optional) The user for which to check access.
    *
    * @return \Drupal\Core\Access\AccessResultInterface
    *   The access result.
    */
-  public function accessEditEntityField(EntityInterface $entity, $field_name);
+  public function accessEditEntityField(EntityInterface $entity, $field_name, AccountInterface $account = NULL);
 
 }
diff --git a/core/modules/quickedit/src/MetadataGenerator.php b/core/modules/quickedit/src/MetadataGenerator.php
index f01256fcbb..a9e2883027 100644
--- a/core/modules/quickedit/src/MetadataGenerator.php
+++ b/core/modules/quickedit/src/MetadataGenerator.php
@@ -4,9 +4,10 @@
 
 use Drupal\Component\Plugin\PluginManagerInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
 use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\quickedit\Access\QuickEditEntityFieldAccessCheckInterface;
-use Drupal\Core\Entity\Entity\EntityViewDisplay;
 
 /**
  * Generates in-place editing metadata for an entity field.
@@ -34,6 +35,13 @@ class MetadataGenerator implements MetadataGeneratorInterface {
    */
   protected $editorManager;
 
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
   /**
    * Constructs a new MetadataGenerator.
    *
@@ -43,11 +51,14 @@ class MetadataGenerator implements MetadataGeneratorInterface {
    *   An object that determines which editor to attach to a given field.
    * @param \Drupal\Component\Plugin\PluginManagerInterface $editor_manager
    *   The manager for editor plugins.
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   (optional) The current user.
    */
-  public function __construct(QuickEditEntityFieldAccessCheckInterface $access_checker, EditorSelectorInterface $editor_selector, PluginManagerInterface $editor_manager) {
+  public function __construct(QuickEditEntityFieldAccessCheckInterface $access_checker, EditorSelectorInterface $editor_selector, PluginManagerInterface $editor_manager, AccountInterface $current_user = NULL) {
     $this->accessChecker = $access_checker;
     $this->editorSelector = $editor_selector;
     $this->editorManager = $editor_manager;
+    $this->currentUser = $current_user;
   }
 
   /**
@@ -67,7 +78,7 @@ public function generateFieldMetadata(FieldItemListInterface $items, $view_mode)
     $field_name = $items->getFieldDefinition()->getName();
 
     // Early-return if user does not have access.
-    $access = $this->accessChecker->accessEditEntityField($entity, $field_name);
+    $access = $this->accessChecker->accessEditEntityField($entity, $field_name, $this->currentUser);
     if (!$access) {
       return ['access' => FALSE];
     }
diff --git a/core/modules/quickedit/tests/modules/src/MockQuickEditEntityFieldAccessCheck.php b/core/modules/quickedit/tests/modules/src/MockQuickEditEntityFieldAccessCheck.php
index 2459c50f08..515650a804 100644
--- a/core/modules/quickedit/tests/modules/src/MockQuickEditEntityFieldAccessCheck.php
+++ b/core/modules/quickedit/tests/modules/src/MockQuickEditEntityFieldAccessCheck.php
@@ -13,7 +13,7 @@ class MockQuickEditEntityFieldAccessCheck implements QuickEditEntityFieldAccessC
   /**
    * {@inheritdoc}
    */
-  public function accessEditEntityField(EntityInterface $entity, $field_name) {
+  public function accessEditEntityField(EntityInterface $entity, $field_name, AccountInterface $account = NULL) {
     return TRUE;
   }
 
diff --git a/core/modules/quickedit/tests/src/Kernel/MetadataGeneratorTest.php b/core/modules/quickedit/tests/src/Kernel/MetadataGeneratorTest.php
index fce2834a1d..bb2c5603a9 100644
--- a/core/modules/quickedit/tests/src/Kernel/MetadataGeneratorTest.php
+++ b/core/modules/quickedit/tests/src/Kernel/MetadataGeneratorTest.php
@@ -4,6 +4,7 @@
 
 use Drupal\entity_test\Entity\EntityTest;
 use Drupal\quickedit\EditorSelector;
+use Drupal\Core\Session\AnonymousUserSession;
 use Drupal\quickedit\MetadataGenerator;
 use Drupal\quickedit_test\MockQuickEditEntityFieldAccessCheck;
 use Drupal\filter\Entity\FilterFormat;
@@ -41,6 +42,13 @@ class MetadataGeneratorTest extends QuickEditTestBase {
    */
   protected $editorSelector;
 
+  /**
+   * The mocked current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
   /**
    * The access checker object to be used by the metadata generator object.
    *
@@ -54,7 +62,8 @@ protected function setUp(): void {
     $this->editorManager = $this->container->get('plugin.manager.quickedit.editor');
     $this->accessChecker = new MockQuickEditEntityFieldAccessCheck();
     $this->editorSelector = new EditorSelector($this->editorManager, $this->container->get('plugin.manager.field.formatter'));
-    $this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
+    $this->currentUser = new AnonymousUserSession();
+    $this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager, $this->currentUser);
   }
 
   /**
@@ -122,11 +131,11 @@ public function testSimpleEntityType() {
   public function testEditorWithCustomMetadata() {
     $this->editorManager = $this->container->get('plugin.manager.quickedit.editor');
     $this->editorSelector = new EditorSelector($this->editorManager, $this->container->get('plugin.manager.field.formatter'));
-    $this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
+    $this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager, $this->currentUser);
 
     $this->editorManager = $this->container->get('plugin.manager.quickedit.editor');
     $this->editorSelector = new EditorSelector($this->editorManager, $this->container->get('plugin.manager.field.formatter'));
-    $this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
+    $this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager, $this->currentUser);
 
     // Create a rich text field.
     $field_name = 'field_rich';
diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
index 898f6770e4..91764f5376 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
@@ -479,7 +479,7 @@ public function testGet() {
     else {
       $this->assertResourceErrorResponse(403, FALSE, $response, $expected_403_cacheability->getCacheTags(), $expected_403_cacheability->getCacheContexts(), static::$auth ? FALSE : 'MISS', FALSE);
     }
-    $this->assertSame(static::$auth ? [] : ['MISS'], $response->getHeader('X-Drupal-Cache'));
+    $this->assertSame(static::$auth ? ['UNCACHEABLE (request policy)'] : ['MISS'], $response->getHeader('X-Drupal-Cache'));
     // DX: 403 because unauthorized.
     $url->setOption('query', ['_format' => static::$format]);
     $response = $this->request('GET', $url, $request_options);
@@ -496,10 +496,10 @@ public function testGet() {
     }
     else {
       $this->assertSame(406, $response->getStatusCode());
-      $this->assertSame(['UNCACHEABLE'], $response->getHeader('X-Drupal-Dynamic-Cache'));
+      $this->assertSame(['UNCACHEABLE (poor cacheability)'], $response->getHeader('X-Drupal-Dynamic-Cache'));
     }
     $this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type'));
-    $this->assertSame(static::$auth ? [] : ['MISS'], $response->getHeader('X-Drupal-Cache'));
+    $this->assertSame(static::$auth ? ['UNCACHEABLE (request policy)'] : ['MISS'], $response->getHeader('X-Drupal-Cache'));
     // DX: 403 because unauthorized.
     $url->setOption('query', ['_format' => static::$format]);
     $response = $this->request('GET', $url, $request_options);
@@ -818,7 +818,7 @@ public function testPost() {
     else {
       $this->assertSame([], $response->getHeader('Location'));
     }
-    $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
+    $this->assertSame(['UNCACHEABLE (request policy)'], $response->getHeader('X-Drupal-Cache'));
     // If the entity is stored, perform extra checks.
     if (get_class($this->entityStorage) !== ContentEntityNullStorage::class) {
       // Assert that the entity was indeed created, and that the response body
@@ -1068,7 +1068,7 @@ public function testPatch() {
     // 200 for well-formed request.
     $response = $this->request('PATCH', $url, $request_options);
     $this->assertResourceResponse(200, FALSE, $response);
-    $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
+    $this->assertSame(['UNCACHEABLE (request policy)'], $response->getHeader('X-Drupal-Cache'));
     // Assert that the entity was indeed updated, and that the response body
     // contains the serialized updated entity.
     $updated_entity = $this->entityStorage->loadUnchanged($this->entity->id());
diff --git a/core/modules/rest/tests/src/Functional/ResourceTestBase.php b/core/modules/rest/tests/src/Functional/ResourceTestBase.php
index 531226dc7c..690e343dbc 100644
--- a/core/modules/rest/tests/src/Functional/ResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/ResourceTestBase.php
@@ -412,6 +412,8 @@ protected function assertResourceResponse($expected_status_code, $expected_body,
     }
     else {
       $this->assertFalse($response->hasHeader('X-Drupal-Cache'));
+      $this->assertTrue($response->hasHeader('X-Drupal-Cache'));
+      $this->stringStartsWith('UNCACHEABLE (', $response->getHeader('X-Drupal-Cache')[0]);
     }
 
     // Expected Dynamic Page Cache header value: X-Drupal-Dynamic-Cache header.
@@ -420,7 +422,16 @@ protected function assertResourceResponse($expected_status_code, $expected_body,
       $this->assertSame($expected_dynamic_page_cache_header_value, $response->getHeader('X-Drupal-Dynamic-Cache')[0]);
     }
     else {
-      $this->assertFalse($response->hasHeader('X-Drupal-Dynamic-Cache'));
+      if ($expected_status_code === 403) {
+        if ($response->hasHeader('X-Drupal-Dynamic-Cache')) {
+          $this->assertTrue($response->hasHeader('X-Drupal-Dynamic-Cache'));
+          $this->stringStartsWith('UNCACHEABLE (', $response->getHeader('X-Drupal-Dynamic-Cache')[0]);
+        }
+      }
+      else {
+        $this->assertTrue($response->hasHeader('X-Drupal-Dynamic-Cache'));
+        $this->stringStartsWith('UNCACHEABLE (', $response->getHeader('X-Drupal-Dynamic-Cache')[0]);
+      }
     }
   }
 
