Introduce experimental immutable parameters.

- Uses final param annotation to exclude overriden params for meta actions.
- Disallow package bindings.
- Update annotations for meta action.
- Add helpers to filter immutable parameters from action.
diff --git a/tests/src/whisk/core/controller/test/MetaApiTests.scala b/tests/src/whisk/core/controller/test/MetaApiTests.scala
index 96f9e2c..f2d6b8e 100644
--- a/tests/src/whisk/core/controller/test/MetaApiTests.scala
+++ b/tests/src/whisk/core/controller/test/MetaApiTests.scala
@@ -23,6 +23,7 @@
 import scala.concurrent.Future
 
 import org.junit.runner.RunWith
+import org.scalatest.BeforeAndAfterEach
 import org.scalatest.junit.JUnitRunner
 
 import akka.event.Logging.InfoLevel
@@ -34,9 +35,8 @@
 import spray.json.DefaultJsonProtocol._
 import whisk.common.TransactionId
 import whisk.core.controller.WhiskMetaApi
-import whisk.core.entity._
 import whisk.core.database.NoDocumentException
-import org.scalatest.BeforeAndAfterEach
+import whisk.core.entity._
 import whisk.http.ErrorResponse
 import whisk.http.Messages
 
@@ -101,11 +101,15 @@
             EntityName("publicmeta"),
             publish = true,
             annotations = Parameters("meta", JsBoolean(true)) ++
-                Parameters("get", JsString("getApi"))))
+                Parameters("get", JsString("getApi"))),
+        WhiskPackage(
+            EntityPath(systemId),
+            EntityName("bindingmeta"),
+            Some(Binding(EntityName(systemId), EntityName("heavymeta"))),
+            annotations = Parameters("meta", JsBoolean(true))))
 
     override protected[controller] def invokeAction(user: Identity, action: WhiskAction, payload: Option[JsObject], blocking: Boolean, waitOverride: Boolean = false)(
         implicit transid: TransactionId): Future[(ActivationId, Option[WhiskActivation])] = {
-
         if (failActivation == 0) {
             // construct a result stub that includes:
             // 1. the package name for the action (to confirm that this resolved to systemId)
@@ -127,9 +131,9 @@
 
             // check that action parameters were merged with package
             // all actions have default parameters (see actionLookup stub)
-            pkgLookup(resolvePackageName(action.namespace.last)) map { pkg =>
+            pkgLookup(resolvePackageName(action.namespace.last)) foreach { pkg =>
                 action.parameters shouldBe (pkg.parameters ++ defaultActionParameters)
-                action.parameters("z") shouldBe defaultActionParameters("z")
+                action.parameters.get("z") shouldBe defaultActionParameters.get("z")
             }
 
             Future.successful(activation.activationId, Some(activation))
@@ -151,13 +155,16 @@
         }
     }
 
-    val defaultActionParameters = Parameters("y", JsString("Y")) ++ Parameters("z", JsString("Z"))
+    val defaultActionParameters = {
+        Parameters("y", JsString("Y")) ++ Parameters("z", JsString("Z"))
+    }
 
     override protected def actionLookup(pkgName: FullyQualifiedEntityName, actionName: EntityName)(
         implicit transid: TransactionId) = {
         if (!failActionLookup) {
             Future.successful {
-                WhiskAction(pkgName.fullPath, actionName, Exec.js("??"), defaultActionParameters)
+                val annotations = Parameters(WhiskAction.finalParamsAnnotationName, JsBoolean(true))
+                WhiskAction(pkgName.fullPath, actionName, Exec.js("??"), defaultActionParameters, annotations = annotations)
             }
         } else {
             Future.failed(NoDocumentException("doesn't exist"))
@@ -193,7 +200,7 @@
         implicit val tid = transid()
 
         val methods = Seq((Put, MethodNotAllowed))
-        methods.map {
+        methods.foreach {
             case (m, code) =>
                 m("/experimental/partialmeta") ~> sealRoute(routes(creds)) ~> check {
                     status should be(code)
@@ -205,15 +212,20 @@
         implicit val tid = transid()
         val methods = Seq(Get, Post, Delete)
 
-        methods.map { m =>
+        methods.foreach { m =>
             m("/experimental") ~> sealRoute(routes(creds)) ~> check {
                 status shouldBe NotFound
             }
         }
 
-        val paths = Seq("/experimental/doesntexist", "/experimental/notmeta", "/experimental/badmeta")
-        paths.map { p =>
-            methods.map { m =>
+        val paths = Seq(
+            "/experimental/doesntexist",
+            "/experimental/notmeta",
+            "/experimental/badmeta",
+            "/experimental/bindingmeta")
+
+        paths.foreach { p =>
+            methods.foreach { m =>
                 m(p) ~> sealRoute(routes(creds)) ~> check {
                     withClue(p) {
                         status shouldBe MethodNotAllowed
@@ -232,7 +244,7 @@
         implicit val tid = transid()
 
         val methods = Seq((Get, "getApi"), (Post, "createRoute"), (Delete, "deleteApi"))
-        methods.map {
+        methods.foreach {
             case (m, name) =>
                 m("/experimental/heavymeta?a=b&c=d&namespace=xyz") ~> sealRoute(routes(creds)) ~> check {
                     status should be(OK)
@@ -249,7 +261,7 @@
         implicit val tid = transid()
 
         val methods = Seq((Get, OK), (Post, MethodNotAllowed), (Delete, MethodNotAllowed))
-        methods.map {
+        methods.foreach {
             case (m, code) =>
                 m("/experimental/partialmeta?a=b&c=d") ~> sealRoute(routes(creds)) ~> check {
                     status should be(code)
@@ -268,7 +280,7 @@
         implicit val tid = transid()
 
         val paths = Seq("", "/", "/foo", "/foo/bar", "/foo/bar/", "/foo%20bar")
-        paths.map { p =>
+        paths.foreach { p =>
             withClue(s"failed on path: '$p'") {
                 Get(s"/experimental/partialmeta$p?a=b&c=d") ~> sealRoute(routes(creds)) ~> check {
                     status should be(OK)
@@ -334,8 +346,8 @@
 
         val methods = Seq(Get, Post, Delete)
 
-        methods.map { m =>
-            reservedProperties.map { p =>
+        methods.foreach { m =>
+            reservedProperties.foreach { p =>
                 m(s"/experimental/packagemeta/?$p=YYY") ~> sealRoute(routes(creds)) ~> check {
                     status should be(BadRequest)
                     responseAs[ErrorResponse].error shouldBe Messages.parametersNotAllowed
@@ -363,6 +375,32 @@
         }
     }
 
+    it should "invoke action and ignore invoke parameters that are immutable" in {
+        implicit val tid = transid()
+        val contentX = JsObject("x" -> "overriden".toJson)
+        val contentZ = JsObject("z" -> "overriden".toJson)
+
+        Get(s"/experimental/packagemeta?x=overriden") ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            responseAs[ErrorResponse].error shouldBe Messages.parametersNotAllowed
+        }
+
+        Get(s"/experimental/packagemeta?y=overriden") ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            responseAs[ErrorResponse].error shouldBe Messages.parametersNotAllowed
+        }
+
+        Get(s"/experimental/packagemeta", contentX) ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            responseAs[ErrorResponse].error shouldBe Messages.parametersNotAllowed
+        }
+
+        Get(s"/experimental/packagemeta?y=overriden", contentZ) ~> sealRoute(routes(creds)) ~> check {
+            status should be(BadRequest)
+            responseAs[ErrorResponse].error shouldBe Messages.parametersNotAllowed
+        }
+    }
+
     it should "rejection invoke action when receiving entity that is not a JsObject" in {
         implicit val tid = transid()
 
diff --git a/tests/src/whisk/core/entity/test/SchemaTests.scala b/tests/src/whisk/core/entity/test/SchemaTests.scala
index 4b7418f..0db33c6 100644
--- a/tests/src/whisk/core/entity/test/SchemaTests.scala
+++ b/tests/src/whisk/core/entity/test/SchemaTests.scala
@@ -402,6 +402,11 @@
         assert(params(3).toString == json(3).compactPrint) // drops unknown prop "foo"
     }
 
+    it should "filter immutable parameters" in {
+        val params = Parameters("k", "v") ++ Parameters("ns", null: String) ++ Parameters("njs", JsNull)
+        params.immutableParameters shouldBe Set("k")
+    }
+
     it should "reject malformed JSON" in {
         val params = Seq[JsValue](
             null,