Serialize `updated` value of entity document in response (#4646)

* Add updated field on WhiskAction json format

* Update custom serdes

* Add updated field at case class

* Fix package api test

* Modified test case name

* Add annotation

* Add singleton method to get current timestamp

* Modify test case

* Update annotation

* Add updated field on package entity

* Add updated field on trigger entity

* Serialize updated field in rules entity

* Fix scheme tests

* Update apiv1swagger.json

* Refactor test code

* Fix test case

* Fix typo

* Refactor test code
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala
index 887adc3..1b9fa4e 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala
@@ -19,6 +19,7 @@
 
 import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
 import java.nio.charset.StandardCharsets.UTF_8
+import java.time.Instant
 import java.util.Base64
 
 import akka.http.scaladsl.model.ContentTypes
@@ -122,7 +123,8 @@
  * @param limits the limits to impose on the action
  * @param version the semantic version
  * @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the action
+ * @param annotations the set of annotations to attribute to the action
+ * @param updated the timestamp when the action is updated
  * @throws IllegalArgumentException if any argument is undefined
  */
 @throws[IllegalArgumentException]
@@ -133,7 +135,8 @@
                        limits: ActionLimits = ActionLimits(),
                        version: SemVer = SemVer(),
                        publish: Boolean = false,
-                       annotations: Parameters = Parameters())
+                       annotations: Parameters = Parameters(),
+                       override val updated: Instant = WhiskEntity.currentMillis())
     extends WhiskActionLike(name) {
 
   require(exec != null, "exec undefined")
@@ -200,6 +203,7 @@
                                version: SemVer = SemVer(),
                                publish: Boolean = false,
                                annotations: Parameters = Parameters(),
+                               override val updated: Instant = WhiskEntity.currentMillis(),
                                binding: Option[EntityPath] = None)
     extends WhiskActionLikeMetaData(name) {
 
@@ -264,7 +268,7 @@
  * @param limits the limits to impose on the action
  * @param version the semantic version
  * @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the action
+ * @param annotations the set of annotations to attribute to the action
  * @param binding the path of the package binding if any
  * @throws IllegalArgumentException if any argument is undefined
  */
@@ -330,7 +334,7 @@
   require(limits != null, "limits undefined")
 
   def toWhiskAction =
-    WhiskActionMetaData(namespace, name, exec, parameters, limits, version, publish, annotations)
+    WhiskActionMetaData(namespace, name, exec, parameters, limits, version, publish, annotations, updated)
       .revision[WhiskActionMetaData](rev)
 
   /**
@@ -342,11 +346,13 @@
 }
 
 object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[WhiskAction] with DefaultJsonProtocol {
+  import WhiskActivation.instantSerdes
 
   val execFieldName = "exec"
   val requireWhiskAuthHeader = "x-require-whisk-auth"
 
   override val collectionName = "actions"
+  override val cacheEnabled = true
 
   override implicit val serdes = jsonFormat(
     WhiskAction.apply,
@@ -357,9 +363,8 @@
     "limits",
     "version",
     "publish",
-    "annotations")
-
-  override val cacheEnabled = true
+    "annotations",
+    "updated")
 
   // overriden to store attached code
   override def put[A >: WhiskAction](db: ArtifactStore[A], doc: WhiskAction, old: Option[WhiskAction])(
@@ -547,7 +552,10 @@
     with WhiskEntityQueries[WhiskActionMetaData]
     with DefaultJsonProtocol {
 
+  import WhiskActivation.instantSerdes
+
   override val collectionName = "actions"
+  override val cacheEnabled = true
 
   override implicit val serdes = jsonFormat(
     WhiskActionMetaData.apply,
@@ -559,10 +567,9 @@
     "version",
     "publish",
     "annotations",
+    "updated",
     "binding")
 
-  override val cacheEnabled = true
-
   /**
    * Resolves an action name if it is contained in a package.
    * Look up the package to determine if it is a binding or the actual package.
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala
index baa6462..0fd81d3 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskEntity.scala
@@ -19,10 +19,9 @@
 
 import java.time.Clock
 import java.time.Instant
+import java.time.temporal.ChronoUnit
 
-import scala.Stream
 import scala.util.Try
-
 import spray.json._
 import org.apache.openwhisk.core.database.DocumentUnreadable
 import org.apache.openwhisk.core.database.DocumentTypeMismatchException
@@ -37,7 +36,7 @@
  * @param namespace the namespace for the entity as an abstract field
  * @param version the semantic version as an abstract field
  * @param publish true to share the entity and false to keep it private as an abstract field
- * @param annotation the set of annotations to attribute to the entity
+ * @param annotations the set of annotations to attribute to the entity
  *
  * @throws IllegalArgumentException if any argument is undefined
  */
@@ -49,7 +48,7 @@
   val version: SemVer
   val publish: Boolean
   val annotations: Parameters
-  val updated = Instant.now(Clock.systemUTC())
+  val updated = WhiskEntity.currentMillis()
 
   /**
    * The name of the entity qualified with its namespace and version for
@@ -112,6 +111,15 @@
   def qualifiedName(namespace: EntityPath, activationId: ActivationId) = {
     s"$namespace${EntityPath.PATHSEP}$activationId"
   }
+
+  /**
+   * Get Instant object with a millisecond precision
+   * timestamp of whisk entity is stored in milliseconds in the db
+   */
+  def currentMillis() = {
+    Instant.now(Clock.systemUTC()).truncatedTo(ChronoUnit.MILLIS)
+  }
+
 }
 
 object WhiskDocumentReader extends DocumentReader {
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala
index c3c5050..c606867 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskPackage.scala
@@ -17,11 +17,12 @@
 
 package org.apache.openwhisk.core.entity
 
+import java.time.Instant
+
 import scala.concurrent.ExecutionContext
 import scala.concurrent.Future
 import scala.language.postfixOps
 import scala.util.Try
-
 import spray.json.DefaultJsonProtocol
 import spray.json.DefaultJsonProtocol._
 import spray.json._
@@ -60,7 +61,8 @@
  * @param parameters the set of parameters to bind to the action environment
  * @param version the semantic version
  * @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the package
+ * @param annotations the set of annotations to attribute to the package
+ * @param updated the timestamp when the package is updated
  * @throws IllegalArgumentException if any argument is undefined
  */
 @throws[IllegalArgumentException]
@@ -70,7 +72,8 @@
                         parameters: Parameters = Parameters(),
                         version: SemVer = SemVer(),
                         publish: Boolean = false,
-                        annotations: Parameters = Parameters())
+                        annotations: Parameters = Parameters(),
+                        override val updated: Instant = WhiskEntity.currentMillis())
     extends WhiskEntity(name, "package") {
 
   require(binding != null || (binding map { _ != null } getOrElse true), "binding undefined")
@@ -159,6 +162,8 @@
     with WhiskEntityQueries[WhiskPackage]
     with DefaultJsonProtocol {
 
+  import WhiskActivation.instantSerdes
+
   val bindingFieldName = "binding"
   override val collectionName = "packages"
 
@@ -197,7 +202,7 @@
       override def write(b: Option[Binding]) = Binding.optionalBindingSerializer.write(b)
       override def read(js: JsValue) = Binding.optionalBindingDeserializer.read(js)
     }
-    jsonFormat7(WhiskPackage.apply)
+    jsonFormat8(WhiskPackage.apply)
   }
 
   override val cacheEnabled = true
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala
index 6a7e929..259a612 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskRule.scala
@@ -17,10 +17,11 @@
 
 package org.apache.openwhisk.core.entity
 
+import java.time.Instant
+
 import scala.util.Failure
 import scala.util.Success
 import scala.util.Try
-
 import spray.json.DefaultJsonProtocol
 import spray.json.DeserializationException
 import spray.json.JsObject
@@ -65,7 +66,8 @@
  * @param action the action name to invoke invoke when trigger is fired
  * @param version the semantic version
  * @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the rule
+ * @param annotations the set of annotations to attribute to the rule
+ * @param updated the timestamp when the rule is updated
  * @throws IllegalArgumentException if any argument is undefined
  */
 @throws[IllegalArgumentException]
@@ -75,10 +77,12 @@
                      action: FullyQualifiedEntityName,
                      version: SemVer = SemVer(),
                      publish: Boolean = false,
-                     annotations: Parameters = Parameters())
+                     annotations: Parameters = Parameters(),
+                     override val updated: Instant = WhiskEntity.currentMillis())
     extends WhiskEntity(name, "rule") {
 
-  def withStatus(s: Status) = WhiskRuleResponse(namespace, name, s, trigger, action, version, publish, annotations)
+  def withStatus(s: Status) =
+    WhiskRuleResponse(namespace, name, s, trigger, action, version, publish, annotations, updated)
 
   def toJson = WhiskRule.serdes.write(this).asJsObject
 }
@@ -95,7 +99,7 @@
  * @param action the action name to invoke invoke when trigger is fired
  * @param version the semantic version
  * @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the rule
+ * @param annotations the set of annotations to attribute to the rule
  */
 case class WhiskRuleResponse(namespace: EntityPath,
                              name: EntityName,
@@ -104,7 +108,8 @@
                              action: FullyQualifiedEntityName,
                              version: SemVer = SemVer(),
                              publish: Boolean = false,
-                             annotations: Parameters = Parameters()) {
+                             annotations: Parameters = Parameters(),
+                             updated: Instant) {
 
   def toWhiskRule = WhiskRule(namespace, name, trigger, action, version, publish, annotations)
 }
@@ -195,11 +200,12 @@
 }
 
 object WhiskRule extends DocumentFactory[WhiskRule] with WhiskEntityQueries[WhiskRule] with DefaultJsonProtocol {
+  import WhiskActivation.instantSerdes
 
   override val collectionName = "rules"
 
   private implicit val fqnSerdes = FullyQualifiedEntityName.serdes
-  private val caseClassSerdes = jsonFormat7(WhiskRule.apply)
+  private val caseClassSerdes = jsonFormat8(WhiskRule.apply)
 
   override implicit val serdes = new RootJsonFormat[WhiskRule] {
     def write(r: WhiskRule) = caseClassSerdes.write(r)
@@ -233,8 +239,9 @@
 }
 
 object WhiskRuleResponse extends DefaultJsonProtocol {
+  import WhiskActivation.instantSerdes
   private implicit val fqnSerdes = FullyQualifiedEntityName.serdes
-  implicit val serdes = jsonFormat8(WhiskRuleResponse.apply)
+  implicit val serdes = jsonFormat9(WhiskRuleResponse.apply)
 }
 
 object WhiskRulePut extends DefaultJsonProtocol {
diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala
index d6668a9..0470980 100644
--- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala
+++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskTrigger.scala
@@ -17,6 +17,8 @@
 
 package org.apache.openwhisk.core.entity
 
+import java.time.Instant
+
 import spray.json.DefaultJsonProtocol
 import org.apache.openwhisk.core.database.DocumentFactory
 import spray.json._
@@ -54,8 +56,9 @@
  * @param limits the limits to impose on the trigger
  * @param version the semantic version
  * @param publish true to share the action or false otherwise
- * @param annotation the set of annotations to attribute to the trigger
+ * @param annotations the set of annotations to attribute to the trigger
  * @param rules the map of the rules that are associated with this trigger. Key is the rulename and value is the ReducedRule
+ * @param updated the timestamp when the trigger is updated
  * @throws IllegalArgumentException if any argument is undefined
  */
 @throws[IllegalArgumentException]
@@ -66,7 +69,8 @@
                         version: SemVer = SemVer(),
                         publish: Boolean = false,
                         annotations: Parameters = Parameters(),
-                        rules: Option[Map[FullyQualifiedEntityName, ReducedRule]] = None)
+                        rules: Option[Map[FullyQualifiedEntityName, ReducedRule]] = None,
+                        override val updated: Instant = WhiskEntity.currentMillis())
     extends WhiskEntity(name, "trigger") {
 
   require(limits != null, "limits undefined")
@@ -108,11 +112,12 @@
     extends DocumentFactory[WhiskTrigger]
     with WhiskEntityQueries[WhiskTrigger]
     with DefaultJsonProtocol {
+  import WhiskActivation.instantSerdes
 
   override val collectionName = "triggers"
 
   private implicit val fqnSerdesAsDocId = FullyQualifiedEntityName.serdesAsDocId
-  override implicit val serdes = jsonFormat8(WhiskTrigger.apply)
+  override implicit val serdes = jsonFormat9(WhiskTrigger.apply)
 
   override val cacheEnabled = true
 }
diff --git a/core/controller/src/main/resources/apiv1swagger.json b/core/controller/src/main/resources/apiv1swagger.json
index 4a3aca7..b9c1060 100644
--- a/core/controller/src/main/resources/apiv1swagger.json
+++ b/core/controller/src/main/resources/apiv1swagger.json
@@ -1998,6 +1998,10 @@
             "deactivating"
           ]
         },
+        "updated": {
+          "type": "integer",
+          "description": "Time when the rule was updated"
+        },
         "trigger": {
           "$ref": "#/definitions/PathName"
         },
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala
index 102ca36..b46a3a4 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ActionsApiTests.scala
@@ -194,6 +194,7 @@
     implicit val tid = transid()
     val action = WhiskAction(namespace, aname(), jsDefault("??"), Parameters("x", "b"))
     put(entityStore, action)
+
     Get(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)) ~> check {
       status should be(OK)
       val response = responseAs[WhiskAction]
@@ -201,20 +202,42 @@
     }
   }
 
-  it should "get action by name in explicit namespace" in {
+  it should "get action with updated field" in {
     implicit val tid = transid()
+
     val action = WhiskAction(namespace, aname(), jsDefault("??"), Parameters("x", "b"))
     put(entityStore, action)
-    Get(s"/$namespace/${collection.path}/${action.name}") ~> Route.seal(routes(creds)) ~> check {
+
+    // `updated` field should be compared with a document in DB
+    val a = get(entityStore, action.docid, WhiskAction)
+
+    Get(s"/$namespace/${collection.path}/${action.name}?code=false") ~> Route.seal(routes(creds)) ~> check {
       status should be(OK)
-      val response = responseAs[WhiskAction]
-      response should be(action)
+      val responseJson = responseAs[JsObject]
+      responseJson.fields("updated").convertTo[Long] should be(a.updated.toEpochMilli)
     }
 
-    // it should "reject get action by name in explicit namespace not owned by subject" in
-    val auser = WhiskAuthHelpers.newIdentity()
-    Get(s"/$namespace/${collection.path}/${action.name}") ~> Route.seal(routes(auser)) ~> check {
-      status should be(Forbidden)
+    Get(s"/$namespace/${collection.path}/${action.name}") ~> Route.seal(routes(creds)) ~> check {
+      status should be(OK)
+      val responseJson = responseAs[JsObject]
+      responseJson.fields("updated").convertTo[Long] should be(a.updated.toEpochMilli)
+    }
+  }
+
+  it should "ignore updated field when updating action" in {
+    implicit val tid = transid()
+
+    val action = WhiskAction(namespace, aname(), jsDefault(""))
+    val dummyUpdated = WhiskEntity.currentMillis().toEpochMilli
+
+    val content = JsObject(
+      "exec" -> JsObject("code" -> "".toJson, "kind" -> action.exec.kind.toJson),
+      "updated" -> dummyUpdated.toJson)
+
+    Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)) ~> check {
+      status should be(OK)
+      val response = responseAs[WhiskAction]
+      response.updated.toEpochMilli should be > dummyUpdated
     }
   }
 
@@ -324,7 +347,7 @@
         Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(expectedWhiskAction)
+          checkWhiskEntityResponse(response, expectedWhiskAction)
         }
 
         Get(s"$collectionPath/${action.name}?code=false") ~> Route.seal(routes(creds)) ~> check {
@@ -332,21 +355,21 @@
           val responseJson = responseAs[JsObject]
           responseJson.fields("exec").asJsObject.fields should not(contain key "code")
           val response = responseAs[WhiskActionMetaData]
-          response should be(expectedWhiskActionMetaData)
+          checkWhiskEntityResponse(response, expectedWhiskActionMetaData)
         }
 
         Seq(s"$collectionPath/${action.name}", s"$collectionPath/${action.name}?code=true").foreach { path =>
           Get(path) ~> Route.seal(routes(creds)) ~> check {
             status should be(OK)
             val response = responseAs[WhiskAction]
-            response should be(expectedWhiskAction)
+            checkWhiskEntityResponse(response, expectedWhiskAction)
           }
         }
 
         Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(expectedWhiskAction)
+          checkWhiskEntityResponse(response, expectedWhiskAction)
         }
     }
   }
@@ -545,7 +568,8 @@
       deleteAction(action.docid)
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -566,7 +590,8 @@
       deleteAction(action.docid)
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -589,7 +614,8 @@
       deleteAction(action.docid)
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -617,7 +643,8 @@
     Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> Route.seal(routes(creds)) ~> check {
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -635,7 +662,8 @@
       deleteAction(action.docid)
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -706,7 +734,8 @@
       deleteAction(action.docid)
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -746,7 +775,8 @@
       deleteAction(action.docid)
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -760,6 +790,7 @@
   }
 
   it should "put and then get an action from cache" in {
+    implicit val tid = transid()
     val javaAction =
       WhiskAction(namespace, aname(), javaDefault("ZHViZWU=", Some("hello")), annotations = Parameters("exec", "java"))
     val nodeAction = WhiskAction(namespace, aname(), jsDefault("??"), Parameters("x", "b"))
@@ -781,7 +812,8 @@
         Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)(transid())) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(
+          checkWhiskEntityResponse(
+            response,
             WhiskAction(
               action.namespace,
               action.name,
@@ -800,7 +832,8 @@
         Get(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(
+          checkWhiskEntityResponse(
+            response,
             WhiskAction(
               action.namespace,
               action.name,
@@ -818,7 +851,8 @@
         Put(s"$collectionPath/${action.name}?overwrite=true", content) ~> Route.seal(routes(creds)(transid())) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be {
+          checkWhiskEntityResponse(
+            response,
             WhiskAction(
               action.namespace,
               action.name,
@@ -827,8 +861,7 @@
               action.limits,
               action.version.upPatch,
               action.publish,
-              action.annotations ++ systemAnnotations(kind))
-          }
+              action.annotations ++ systemAnnotations(kind)))
         }
         stream.toString should include(s"entity exists, will try to update '$action'")
         stream.toString should include(s"invalidating ${CacheKey(action)}")
@@ -839,7 +872,8 @@
         Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(
+          checkWhiskEntityResponse(
+            response,
             WhiskAction(
               action.namespace,
               action.name,
@@ -893,7 +927,8 @@
         Put(s"$collectionPath/${action.name}", content) ~> Route.seal(routes(creds)(transid())) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(
+          checkWhiskEntityResponse(
+            response,
             WhiskAction(
               action.namespace,
               action.name,
@@ -913,7 +948,8 @@
         Get(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(
+          checkWhiskEntityResponse(
+            response,
             WhiskAction(
               action.namespace,
               action.name,
@@ -932,7 +968,8 @@
         Delete(s"$collectionPath/${action.name}") ~> Route.seal(routes(creds)(transid())) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(
+          checkWhiskEntityResponse(
+            response,
             WhiskAction(
               action.namespace,
               action.name,
@@ -976,7 +1013,8 @@
     Put(s"$collectionPath/$name", content) ~> Route.seal(routes(creds)(transid())) ~> check {
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -996,7 +1034,8 @@
     Get(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check {
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -1016,7 +1055,8 @@
     Delete(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check {
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -1065,7 +1105,8 @@
         Get(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(
+          checkWhiskEntityResponse(
+            response,
             WhiskAction(
               action.namespace,
               action.name,
@@ -1124,7 +1165,7 @@
       Get(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check {
         status should be(OK)
         val response = responseAs[WhiskAction]
-        response should be(expectedAction)
+        checkWhiskEntityResponse(response, expectedAction)
       }
     }
 
@@ -1172,7 +1213,8 @@
         Put(s"$collectionPath/$name?overwrite=true", content) ~> Route.seal(routes(creds)(transid())) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(
+          checkWhiskEntityResponse(
+            response,
             WhiskAction(
               action.namespace,
               action.name,
@@ -1190,7 +1232,8 @@
         Delete(s"$collectionPath/$name") ~> Route.seal(routes(creds)(transid())) ~> check {
           status should be(OK)
           val response = responseAs[WhiskAction]
-          response should be(
+          checkWhiskEntityResponse(
+            response,
             WhiskAction(
               action.namespace,
               action.name,
@@ -1237,7 +1280,8 @@
 
     Put(s"$collectionPath/${actionOldSchema.name}?overwrite=true", content) ~> Route.seal(routes(creds)) ~> check {
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           actionOldSchema.namespace,
           actionOldSchema.name,
@@ -1261,7 +1305,8 @@
     Delete(s"$collectionPath/${actionOldSchema.name}") ~> Route.seal(routes(creds)) ~> check {
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           actionOldSchema.namespace,
           actionOldSchema.name,
@@ -1301,7 +1346,10 @@
       deleteAction(action.docid)
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be {
+
+      response.updated should not be action.updated
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
@@ -1313,8 +1361,7 @@
             content.limits.get.logs.get,
             content.limits.get.concurrency.get),
           version = action.version.upPatch,
-          annotations = action.annotations ++ systemAnnotations(NODEJS10, create = false))
-      }
+          annotations = action.annotations ++ systemAnnotations(NODEJS10, create = false)))
     }
   }
 
@@ -1327,15 +1374,15 @@
       deleteAction(action.docid)
       status should be(OK)
       val response = responseAs[WhiskAction]
-      response should be {
+      checkWhiskEntityResponse(
+        response,
         WhiskAction(
           action.namespace,
           action.name,
           action.exec,
           content.parameters.get,
           version = action.version.upPatch,
-          annotations = action.annotations ++ systemAnnotations(NODEJS10, false))
-      }
+          annotations = action.annotations ++ systemAnnotations(NODEJS10, false)))
     }
   }
 
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala
index fca9faf..9817bef 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/ControllerTestCommon.scala
@@ -85,6 +85,18 @@
     }
   }
 
+  def checkWhiskEntityResponse(response: WhiskEntity, expected: WhiskEntity): Unit = {
+    // Used to ignore `updated` field because timestamp is not known before inserting into the DB
+    // If you use this method, test case that checks timestamp must be added
+    val r = response match {
+      case whiskAction: WhiskAction                 => whiskAction.copy(updated = expected.updated)
+      case whiskActionMetaData: WhiskActionMetaData => whiskActionMetaData.copy(updated = expected.updated)
+      case whiskTrigger: WhiskTrigger               => whiskTrigger.copy(updated = expected.updated)
+      case whiskPackage: WhiskPackage               => whiskPackage.copy(updated = expected.updated)
+    }
+    r should be(expected)
+  }
+
   def systemAnnotations(kind: String, create: Boolean = true): Parameters = {
     val base = if (create && FeatureFlags.requireApiKeyAnnotation) {
       Parameters(Annotations.ProvideApiKeyAnnotationName, JsFalse)
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala
index d1033b0..a645e87 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackageActionsApiTests.scala
@@ -162,7 +162,8 @@
           action.limits,
           action.version,
           action.publish,
-          action.annotations ++ systemAnnotations(NODEJS10)))
+          action.annotations ++ systemAnnotations(NODEJS10),
+          updated = response.updated))
     }
   }
 
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala
index 5d1ecca..5ea915d 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/PackagesApiTests.scala
@@ -297,6 +297,21 @@
     }
   }
 
+  it should "get package with updated field" in {
+    implicit val tid = transid()
+    val provider = WhiskPackage(namespace, aname(), None)
+    put(entityStore, provider)
+
+    // `updated` field should be compared with a document in DB
+    val pkg = get(entityStore, provider.docid, WhiskPackage)
+
+    Get(s"$collectionPath/${provider.name}") ~> Route.seal(routes(creds)) ~> check {
+      status should be(OK)
+      val response = responseAs[WhiskPackageWithActions]
+      response should be(provider copy (updated = pkg.updated) withActions ())
+    }
+  }
+
   it should "get package reference for private package in same namespace" in {
     implicit val tid = transid()
     val provider = WhiskPackage(namespace, aname(), None, Parameters("a", "A") ++ Parameters("b", "B"))
@@ -422,7 +437,7 @@
       deletePackage(provider.docid)
       status should be(OK)
       val response = responseAs[WhiskPackage]
-      response should be(provider)
+      checkWhiskEntityResponse(response, provider)
     }
   }
 
@@ -492,7 +507,7 @@
       deletePackage(reference.docid)
       status should be(OK)
       val response = responseAs[WhiskPackage]
-      response should be(reference)
+      checkWhiskEntityResponse(response, reference)
     }
   }
 
@@ -522,13 +537,13 @@
       deletePackage(reference.docid)
       status should be(OK)
       val response = responseAs[WhiskPackage]
-      response should be {
+      checkWhiskEntityResponse(
+        response,
         WhiskPackage(
           reference.namespace,
           reference.name,
           provider.bind,
-          annotations = bindingAnnotation(provider.bind.get))
-      }
+          annotations = bindingAnnotation(provider.bind.get)))
     }
   }
 
@@ -632,7 +647,8 @@
     Put(s"$collectionPath/${provider.name}?overwrite=true", content) ~> Route.seal(routes(creds)) ~> check {
       deletePackage(provider.docid)
       val response = responseAs[WhiskPackage]
-      response should be(
+      checkWhiskEntityResponse(
+        response,
         WhiskPackage(namespace, provider.name, None, version = provider.version.upPatch, publish = true))
     }
   }
@@ -657,15 +673,15 @@
       deletePackage(reference.docid)
       status should be(OK)
       val response = responseAs[WhiskPackage]
-      response should be {
+      checkWhiskEntityResponse(
+        response,
         WhiskPackage(
           reference.namespace,
           reference.name,
           reference.binding,
           version = reference.version.upPatch,
           publish = true,
-          annotations = reference.annotations ++ Parameters("a", "b"))
-      }
+          annotations = reference.annotations ++ Parameters("a", "b")))
     }
   }
 
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala
index d497593..9aee5da 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/RulesApiTests.scala
@@ -17,6 +17,8 @@
 
 package org.apache.openwhisk.core.controller.test
 
+import java.time.Instant
+
 import scala.language.postfixOps
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
@@ -27,7 +29,7 @@
 import spray.json._
 import org.apache.openwhisk.core.controller.WhiskRulesApi
 import org.apache.openwhisk.core.entitlement.Collection
-import org.apache.openwhisk.core.entity._
+import org.apache.openwhisk.core.entity.{WhiskRuleResponse, _}
 import org.apache.openwhisk.core.entity.test.OldWhiskTrigger
 import org.apache.openwhisk.http.ErrorResponse
 
@@ -61,6 +63,11 @@
   val activeStatus = s"""{"status":"${Status.ACTIVE}"}""".parseJson.asJsObject
   val inactiveStatus = s"""{"status":"${Status.INACTIVE}"}""".parseJson.asJsObject
   val parametersLimit = Parameters.sizeLimit
+  val dummyInstant = Instant.now()
+
+  def checkResponse(response: WhiskRuleResponse, expected: WhiskRuleResponse) =
+    // ignore `updated` field because another test covers it
+    response should be(expected copy (updated = response.updated))
 
   //// GET /rules
   it should "list rules by default/explicit namespace" in {
@@ -164,14 +171,14 @@
     Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.INACTIVE))
+      checkResponse(response, rule.withStatus(Status.INACTIVE))
     }
 
     // it should "get trigger by name in explicit namespace owned by subject" in
     Get(s"/$namespace/${collection.path}/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.INACTIVE))
+      checkResponse(response, rule.withStatus(Status.INACTIVE))
     }
 
     // it should "reject get trigger by name in explicit namespace not owned by subject" in
@@ -207,7 +214,32 @@
     Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.ACTIVE))
+      checkResponse(response, rule.withStatus(Status.ACTIVE))
+    }
+  }
+
+  it should "get rule with updated field" in {
+    implicit val tid = transid()
+
+    val rule = WhiskRule(
+      namespace,
+      EntityName("get_active_rule"),
+      afullname(namespace, "get_active_rule trigger"),
+      afullname(namespace, "an action"))
+    val trigger = WhiskTrigger(rule.trigger.path, rule.trigger.name, rules = Some {
+      Map(rule.fullyQualifiedName(false) -> ReducedRule(rule.action, Status.ACTIVE))
+    })
+
+    put(entityStore, trigger)
+    put(entityStore, rule)
+
+    // `updated` field should be compared with a document in DB
+    val r = get(entityStore, rule.docid, WhiskRule)
+
+    Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
+      status should be(OK)
+      val response = responseAs[WhiskRuleResponse]
+      response should be(rule.withStatus(Status.ACTIVE) copy (updated = r.updated))
     }
   }
 
@@ -227,7 +259,7 @@
     Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.INACTIVE))
+      checkResponse(response, rule.withStatus(Status.INACTIVE))
     }
   }
 
@@ -264,7 +296,7 @@
 
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.INACTIVE))
+      checkResponse(response, rule.withStatus(Status.INACTIVE))
     }
   }
 
@@ -292,7 +324,7 @@
       status should be(OK)
       t.rules.get.get(rule.fullyQualifiedName(false)) shouldBe None
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.INACTIVE))
+      checkResponse(response, rule.withStatus(Status.INACTIVE))
     }
   }
 
@@ -310,7 +342,7 @@
     Delete(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.INACTIVE))
+      checkResponse(response, rule.withStatus(Status.INACTIVE))
     }
   }
 
@@ -329,7 +361,7 @@
 
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.INACTIVE))
+      checkResponse(response, rule.withStatus(Status.INACTIVE))
     }
   }
 
@@ -356,7 +388,7 @@
 
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.ACTIVE))
+      checkResponse(response, rule.withStatus(Status.ACTIVE))
       t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE)
     }
   }
@@ -385,7 +417,7 @@
 
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.ACTIVE))
+      checkResponse(response, rule.withStatus(Status.ACTIVE))
       t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE)
     }
   }
@@ -436,7 +468,7 @@
 
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.ACTIVE))
+      checkResponse(response, rule.withStatus(Status.ACTIVE))
       t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE)
     }
   }
@@ -468,7 +500,7 @@
 
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.ACTIVE))
+      checkResponse(response, rule.withStatus(Status.ACTIVE))
       t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE)
     }
   }
@@ -592,14 +624,16 @@
 
       t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false))
       val response = responseAs[WhiskRuleResponse]
-      response should be(
+      checkResponse(
+        response,
         WhiskRuleResponse(
           namespace,
           rule.name,
           Status.INACTIVE,
           trigger.fullyQualifiedName(false),
           action.fullyQualifiedName(false),
-          version = SemVer().upPatch))
+          version = SemVer().upPatch,
+          updated = dummyInstant))
     }
   }
 
@@ -623,14 +657,16 @@
       status should be(OK)
       t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false))
       val response = responseAs[WhiskRuleResponse]
-      response should be(
+      checkResponse(
+        response,
         WhiskRuleResponse(
           namespace,
           rule.name,
           Status.INACTIVE,
           trigger.fullyQualifiedName(false),
           action.fullyQualifiedName(false),
-          version = SemVer().upPatch))
+          version = SemVer().upPatch,
+          updated = dummyInstant))
     }
   }
 
@@ -654,14 +690,16 @@
       status should be(OK)
       t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false))
       val response = responseAs[WhiskRuleResponse]
-      response should be(
+      checkResponse(
+        response,
         WhiskRuleResponse(
           namespace,
           rule.name,
           Status.INACTIVE,
           trigger.fullyQualifiedName(false),
           action.fullyQualifiedName(false),
-          version = SemVer().upPatch))
+          version = SemVer().upPatch,
+          updated = dummyInstant))
     }
   }
 
@@ -685,14 +723,16 @@
       status should be(OK)
       t.rules.get.get(rule.fullyQualifiedName(false)) shouldBe a[Some[_]]
       val response = responseAs[WhiskRuleResponse]
-      response should be(
+      checkResponse(
+        response,
         WhiskRuleResponse(
           namespace,
           rule.name,
           Status.INACTIVE,
           trigger.fullyQualifiedName(false),
           action.fullyQualifiedName(false),
-          version = SemVer().upPatch))
+          version = SemVer().upPatch,
+          updated = dummyInstant))
     }
   }
 
@@ -713,14 +753,16 @@
 
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(
+      checkResponse(
+        response,
         WhiskRuleResponse(
           namespace,
           rule.name,
           Status.INACTIVE,
           trigger.fullyQualifiedName(false),
           action.fullyQualifiedName(false),
-          version = SemVer().upPatch))
+          version = SemVer().upPatch,
+          updated = dummyInstant))
     }
   }
 
@@ -795,14 +837,16 @@
       status should be(OK)
       t.rules.get(rule.fullyQualifiedName(false)).action should be(action.fullyQualifiedName(false))
       val response = responseAs[WhiskRuleResponse]
-      response should be(
+      checkResponse(
+        response,
         WhiskRuleResponse(
           namespace,
           rule.name,
           Status.ACTIVE,
           trigger.fullyQualifiedName(false),
           action.fullyQualifiedName(false),
-          version = SemVer().upPatch))
+          version = SemVer().upPatch,
+          updated = dummyInstant))
     }
   }
 
@@ -948,7 +992,7 @@
     Get(s"$collectionPath/${rule.name}") ~> Route.seal(routes(creds)) ~> check {
       status should be(OK)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.toWhiskRule.withStatus(Status.INACTIVE))
+      checkResponse(response, rule.toWhiskRule.withStatus(Status.INACTIVE))
     }
   }
 
@@ -972,7 +1016,7 @@
       status should be(OK)
       t.rules.get(rule.fullyQualifiedName(false)) shouldBe ReducedRule(action.fullyQualifiedName(false), Status.ACTIVE)
       val response = responseAs[WhiskRuleResponse]
-      response should be(rule.withStatus(Status.ACTIVE))
+      checkResponse(response, rule.withStatus(Status.ACTIVE))
     }
   }
 
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala
index 91b22a2..d87751b 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/controller/test/TriggersApiTests.scala
@@ -67,6 +67,7 @@
   def aname() = MakeName.next("triggers_tests")
   def afullname(namespace: EntityPath, name: String) = FullyQualifiedEntityName(namespace, EntityName(name))
   val parametersLimit = Parameters.sizeLimit
+  val dummyInstant = Instant.now()
 
   //// GET /triggers
   it should "list triggers by default/explicit namespace" in {
@@ -183,6 +184,21 @@
     }
   }
 
+  it should "get trigger with updated field" in {
+    implicit val tid = transid()
+    val trigger = WhiskTrigger(namespace, aname(), Parameters("x", "b"))
+    put(entityStore, trigger)
+
+    // `updated` field should be compared with a document in DB
+    val t = get(entityStore, trigger.docid, WhiskTrigger)
+
+    Get(s"$collectionPath/${trigger.name}") ~> Route.seal(routes(creds)) ~> check {
+      status should be(OK)
+      val response = responseAs[WhiskTrigger]
+      response should be(trigger copy (updated = t.updated))
+    }
+  }
+
   it should "report Conflict if the name was of a different type" in {
     implicit val tid = transid()
     val rule = WhiskRule(
@@ -217,7 +233,7 @@
       deleteTrigger(trigger.docid)
       status should be(OK)
       val response = responseAs[WhiskTrigger]
-      response should be(trigger.withoutRules)
+      checkWhiskEntityResponse(response, trigger.withoutRules)
     }
   }
 
@@ -229,7 +245,7 @@
       deleteTrigger(trigger.docid)
       status should be(OK)
       val response = responseAs[WhiskTrigger]
-      response should be(trigger.withoutRules)
+      checkWhiskEntityResponse(response, trigger.withoutRules)
     }
   }
 
@@ -323,11 +339,14 @@
       deleteTrigger(trigger.docid)
       status should be(OK)
       val response = responseAs[WhiskTrigger]
-      response should be(WhiskTrigger(
-        trigger.namespace,
-        trigger.name,
-        trigger.parameters,
-        version = trigger.version.upPatch).withoutRules)
+      checkWhiskEntityResponse(
+        response,
+        WhiskTrigger(
+          trigger.namespace,
+          trigger.name,
+          trigger.parameters,
+          version = trigger.version.upPatch,
+          updated = dummyInstant).withoutRules)
     }
   }
 
@@ -432,8 +451,7 @@
     Get(s"$collectionPath/${trigger.name}") ~> Route.seal(routes(creds)) ~> check {
       val response = responseAs[WhiskTrigger]
       status should be(OK)
-
-      response should be(trigger.toWhiskTrigger)
+      checkWhiskEntityResponse(response, trigger.toWhiskTrigger)
     }
   }
 
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala
index 6f9a322..42fb0d0 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/SchemaTests.scala
@@ -431,7 +431,8 @@
       "parameters" -> Parameters().toJson,
       "version" -> SemVer().toJson,
       "publish" -> JsFalse,
-      "annotations" -> Parameters().toJson)
+      "annotations" -> Parameters().toJson,
+      "updated" -> pkg.updated.toEpochMilli.toJson)
   }
 
   it should "serialize and deserialize package binding" in {
@@ -443,7 +444,8 @@
       "parameters" -> Parameters().toJson,
       "version" -> SemVer().toJson,
       "publish" -> JsFalse,
-      "annotations" -> Parameters().toJson)
+      "annotations" -> Parameters().toJson,
+      "updated" -> pkg.updated.toEpochMilli.toJson)
     //val legacyPkgAsJson = JsObject(pkgAsJson.fields + ("binding" -> JsObject("namespace" -> "x".toJson, "name" -> "y".toJson)))
     WhiskPackage.serdes.write(pkg) shouldBe pkgAsJson
     WhiskPackage.serdes.read(pkgAsJson) shouldBe pkg
diff --git a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala
index e473070..3b10d76 100644
--- a/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala
+++ b/tests/src/test/scala/org/apache/openwhisk/core/entity/test/WhiskEntityTests.scala
@@ -186,7 +186,8 @@
         |		"timeout": 60000,
         |		"memory": 256
         |	},
-        |	"namespace": "namespace"
+        |	"namespace": "namespace",
+        |	"updated": 1546268400000
         |}""".stripMargin.parseJson
 
     val action = WhiskDocumentReader.read(manifest[WhiskAction], json)
@@ -213,7 +214,8 @@
         |    "timeout": 60000,
         |    "memory": 256
         |  },
-        |  "namespace": "namespace"
+        |  "namespace": "namespace",
+        |  "updated": 1546268400000
         |}""".stripMargin.parseJson
     val action = WhiskDocumentReader.read(manifest[WhiskAction], json)
     assertType(action.asInstanceOf[WhiskEntity], "action")