x/ref/services/repository: changes to Application repository API and daemon

This CL makes three changes to the Application repository API:

- introduce PutX(), which will eventually replace Put. PutX takes only one
  profile at a time (instead of a slice). If the client wants to add the same
  envelope for several profiles, they'd have to call PutX repeatedly.  This
  makes the API and failure semantics clear. PutX takes a new Overwrite option
  to control whether replacing the envelope is allowed (default to false, to
  encourage creating new versions over replacing existing versions).

- introduce Profiles(), which allows inspection of profiles for a given
  application version or all versions.

- add a '*' profile option for Remove to clear all profiles for a given
  application version or all versions.

This CL also contains the implementations for these changes in
x/ref/services/application/applicationd, as well as corresponding test logic in
impl_test.

Change-Id: I7400002f827910e8026ead474d2ca6dcd9d8ee38
diff --git a/services/application/application/impl_test.go b/services/application/application/impl_test.go
index 9cb5353..42b3cec 100644
--- a/services/application/application/impl_test.go
+++ b/services/application/application/impl_test.go
@@ -93,6 +93,16 @@
 	return nil
 }
 
+func (s *server) PutX(ctx *context.T, _ rpc.ServerCall, profile string, env application.Envelope, overwrite bool) error {
+	ctx.VI(2).Infof("%v.PutX(%v, %v, %t) was called", s.suffix, profile, env, overwrite)
+	return nil
+}
+
+func (s *server) Profiles(ctx *context.T, _ rpc.ServerCall) ([]string, error) {
+	ctx.VI(2).Infof("%v.Profiles() was called", s.suffix)
+	return nil, nil
+}
+
 func (s *server) Remove(ctx *context.T, _ rpc.ServerCall, profile string) error {
 	ctx.VI(2).Infof("%v.Remove(%v) was called", s.suffix, profile)
 	return nil
diff --git a/services/application/applicationd/dispatcher.go b/services/application/applicationd/dispatcher.go
index 506d3ae..8f081ac 100644
--- a/services/application/applicationd/dispatcher.go
+++ b/services/application/applicationd/dispatcher.go
@@ -44,7 +44,7 @@
 		naming.Join("/acls", "data"),
 		naming.Join("/acls", name, "data"),
 		(*applicationPermsStore)(d.store),
-		[]string{"Put", "__Glob"})
+		[]string{"Put", "PutX", "__Glob"})
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/services/application/applicationd/impl_test.go b/services/application/applicationd/impl_test.go
index b045bbf..0330514 100644
--- a/services/application/applicationd/impl_test.go
+++ b/services/application/applicationd/impl_test.go
@@ -39,6 +39,34 @@
 	return b, sig
 }
 
+func checkEnvelope(t *testing.T, ctx *context.T, expected application.Envelope, stub repository.ApplicationClientStub, profiles ...string) {
+	if output, err := stub.Match(ctx, profiles); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Match() failed: %v", err))
+	} else if !reflect.DeepEqual(expected, output) {
+		t.Fatalf(testutil.FormatLogLine(2, "Incorrect Match output: expected %#v, got %#v", expected, output))
+	}
+}
+
+func checkNoEnvelope(t *testing.T, ctx *context.T, stub repository.ApplicationClientStub, profiles ...string) {
+	if _, err := stub.Match(ctx, profiles); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatalf(testutil.FormatLogLine(2, "Unexpected error: expected %v, got %v", verror.ErrNoExist, err))
+	}
+}
+
+func checkProfiles(t *testing.T, ctx *context.T, stub repository.ApplicationClientStub, expected ...string) {
+	if output, err := stub.Profiles(ctx); err != nil {
+		t.Fatalf(testutil.FormatLogLine(2, "Profiles() failed: %v", err))
+	} else if !reflect.DeepEqual(expected, output) {
+		t.Fatalf(testutil.FormatLogLine(2, "Incorrect Profiles output: expected %v, got %v", expected, output))
+	}
+}
+
+func checkNoProfile(t *testing.T, ctx *context.T, stub repository.ApplicationClientStub) {
+	if _, err := stub.Profiles(ctx); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
+		t.Fatalf(testutil.FormatLogLine(2, "Unexpected error: expected %v, got %v", verror.ErrNoExist, err))
+	}
+}
+
 // TestInterface tests that the implementation correctly implements
 // the Application interface.
 func TestInterface(t *testing.T) {
@@ -99,6 +127,7 @@
 		},
 		Publisher: blessings,
 	}
+	checkNoProfile(t, ctx, stub)
 
 	// Test Put(), adding a number of application envelopes.
 	if err := stubV1.Put(ctx, []string{"base", "media"}, envelopeV1); err != nil {
@@ -111,47 +140,31 @@
 		t.Fatalf("Unexpected error: expected %v, got %v", appd.ErrInvalidSuffix, err)
 	}
 
-	// Test Match(), trying to retrieve both existing and non-existing
-	// application envelopes.
-	var output application.Envelope
-	if output, err = stubV2.Match(ctx, []string{"base", "media"}); err != nil {
-		t.Fatalf("Match() failed: %v", err)
-	}
-	if !reflect.DeepEqual(envelopeV2, output) {
-		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV2, output)
-	}
-	if output, err = stubV1.Match(ctx, []string{"media"}); err != nil {
-		t.Fatalf("Match() failed: %v", err)
-	}
-	if !reflect.DeepEqual(envelopeV1, output) {
-		t.Fatalf("Unexpected output: expected %v, got %v", envelopeV1, output)
-	}
-	if _, err := stubV2.Match(ctx, []string{"media"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
-		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
-	}
-	if _, err := stubV2.Match(ctx, []string{}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
-		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
-	}
+	// Test Match() against versioned names, trying profiles that do and
+	// don't have any envelopes uploaded.
+	checkNoEnvelope(t, ctx, stubV2)
+	checkEnvelope(t, ctx, envelopeV2, stubV2, "base")
+	checkNoEnvelope(t, ctx, stubV2, "media")
+	checkEnvelope(t, ctx, envelopeV2, stubV2, "base", "media")
+	checkEnvelope(t, ctx, envelopeV2, stubV2, "media", "base")
 
-	// Test that Match() against a name without a version suffix returns the latest.
-	if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
-		t.Fatalf("Match() failed: %v", err)
-	}
-	if !reflect.DeepEqual(envelopeV2, output) {
-		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV2, output)
-	}
+	// Test that Match() against a name without a version suffix returns the
+	// latest.
+	checkEnvelope(t, ctx, envelopeV2, stub, "base", "media")
+	checkEnvelope(t, ctx, envelopeV1, stub, "media")
 
-	// Test that we can add another envelope in sort order and we still get the
-	// correct (i.e. newest) version.
+	checkProfiles(t, ctx, stub, "base", "media")
+	checkProfiles(t, ctx, stubV1, "base", "media")
+	checkProfiles(t, ctx, stubV2, "base")
+	checkNoProfile(t, ctx, stubV3)
+
+	// Test that if we add another envelope for a version that's the highest
+	// in sort order, the new envelope becomes the latest.
 	if err := stubV3.Put(ctx, []string{"base"}, envelopeV3); err != nil {
 		t.Fatalf("Put() failed: %v", err)
 	}
-	if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
-		t.Fatalf("Match() failed: %v", err)
-	}
-	if !reflect.DeepEqual(envelopeV3, output) {
-		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output)
-	}
+	checkEnvelope(t, ctx, envelopeV3, stub, "base", "media")
+	checkProfiles(t, ctx, stubV3, "base")
 
 	// Test that this is not based on time but on sort order.
 	envelopeV0 := application.Envelope{
@@ -166,12 +179,7 @@
 	if err := stubV0.Put(ctx, []string{"base"}, envelopeV0); err != nil {
 		t.Fatalf("Put() failed: %v", err)
 	}
-	if output, err = stub.Match(ctx, []string{"base", "media"}); err != nil {
-		t.Fatalf("Match() failed: %v", err)
-	}
-	if !reflect.DeepEqual(envelopeV3, output) {
-		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output)
-	}
+	checkEnvelope(t, ctx, envelopeV3, stub, "base", "media")
 
 	// Test Glob
 	matches, _, err := testutil.GlobName(ctx, naming.JoinAddressName(endpoint, ""), "...")
@@ -190,38 +198,75 @@
 		t.Errorf("unexpected Glob results. Got %q, want %q", matches, expected)
 	}
 
+	// PutX cannot replace the envelope for v0-base when overwrite is false.
+	if err := stubV0.PutX(ctx, "base", envelopeV2, false); err == nil || verror.ErrorID(err) != verror.ErrExist.ID {
+		t.Fatalf("Unexpected error: expected %v, got %v", appd.ErrInvalidSuffix, err)
+	}
+	checkEnvelope(t, ctx, envelopeV0, stubV0, "base")
+	// PutX can replace the envelope for v0-base when overwrite is true.
+	if err := stubV0.PutX(ctx, "base", envelopeV2, true); err != nil {
+		t.Fatalf("PutX() failed: %v", err)
+	}
+	checkEnvelope(t, ctx, envelopeV2, stubV0, "base")
+
 	// Test Remove(), trying to remove both existing and non-existing
 	// application envelopes.
 	if err := stubV1.Remove(ctx, "base"); err != nil {
 		t.Fatalf("Remove() failed: %v", err)
 	}
-	if output, err = stubV1.Match(ctx, []string{"media"}); err != nil {
-		t.Fatalf("Match() failed: %v", err)
-	}
+	checkNoEnvelope(t, ctx, stubV1)
+	checkEnvelope(t, ctx, envelopeV1, stubV1, "media")
+	checkNoEnvelope(t, ctx, stubV1, "base")
+	checkEnvelope(t, ctx, envelopeV1, stubV1, "base", "media")
+	checkEnvelope(t, ctx, envelopeV1, stubV1, "media", "base")
+
 	if err := stubV1.Remove(ctx, "base"); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
 		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
 	}
 	if err := stub.Remove(ctx, "base"); err != nil {
 		t.Fatalf("Remove() failed: %v", err)
 	}
+	checkNoProfile(t, ctx, stubV0)
+	checkProfiles(t, ctx, stubV1, "media")
+	checkNoProfile(t, ctx, stubV2)
+	checkNoProfile(t, ctx, stubV3)
+	checkProfiles(t, ctx, stub, "media")
 	if err := stubV2.Remove(ctx, "media"); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
 		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
 	}
 	if err := stubV1.Remove(ctx, "media"); err != nil {
 		t.Fatalf("Remove() failed: %v", err)
 	}
+	checkNoProfile(t, ctx, stub)
 
 	// Finally, use Match() to test that Remove really removed the
 	// application envelopes.
-	if _, err := stubV1.Match(ctx, []string{"base"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
+	checkNoEnvelope(t, ctx, stubV1, "base")
+	checkNoEnvelope(t, ctx, stubV1, "media")
+	checkNoEnvelope(t, ctx, stubV2, "base")
+
+	if err := stubV0.PutX(ctx, "base", envelopeV0, false); err != nil {
+		t.Fatalf("PutX() failed: %v", err)
+	}
+	if err := stubV1.PutX(ctx, "base", envelopeV1, false); err != nil {
+		t.Fatalf("PutX() failed: %v", err)
+	}
+	if err := stubV1.PutX(ctx, "media", envelopeV1, false); err != nil {
+		t.Fatalf("PutX() failed: %v", err)
+	}
+	if err := stubV2.PutX(ctx, "base", envelopeV2, false); err != nil {
+		t.Fatalf("PutX() failed: %v", err)
+	}
+	if err := stubV3.PutX(ctx, "base", envelopeV3, false); err != nil {
+		t.Fatalf("PutX() failed: %v", err)
+	}
+	if err := stub.Remove(ctx, "*"); err != nil {
+		t.Fatalf("Remove() failed: %v", err)
+	}
+	if err := stub.Remove(ctx, "*"); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
 		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
 	}
-	if _, err := stubV1.Match(ctx, []string{"media"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
-		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
-	}
-	if _, err := stubV2.Match(ctx, []string{"base"}); err == nil || verror.ErrorID(err) != verror.ErrNoExist.ID {
-		t.Fatalf("Unexpected error: expected %v, got %v", verror.ErrNoExist, err)
-	}
+	checkNoProfile(t, ctx, stub)
 
 	// Shutdown the application repository server.
 	if err := server.Stop(); err != nil {
@@ -272,13 +317,7 @@
 	}
 
 	// There is content here now.
-	output, err := stubV1.Match(ctx, []string{"media"})
-	if err != nil {
-		t.Fatalf("Match(%v) failed: %v", "media", err)
-	}
-	if !reflect.DeepEqual(envelopeV1, output) {
-		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV1, output)
-	}
+	checkEnvelope(t, ctx, envelopeV1, stubV1, "media")
 
 	server.Stop()
 
@@ -296,13 +335,7 @@
 
 	stubV1 = repository.ApplicationClient(naming.JoinAddressName(endpoint, "search/v1"))
 
-	output, err = stubV1.Match(ctx, []string{"media"})
-	if err != nil {
-		t.Fatalf("Match(%v) failed: %v", "media", err)
-	}
-	if !reflect.DeepEqual(envelopeV1, output) {
-		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV1, output)
-	}
+	checkEnvelope(t, ctx, envelopeV1, stubV1, "media")
 }
 
 // TestTidyNow tests that TidyNow operates correctly.
@@ -428,21 +461,8 @@
 
 	// And the newest version for each profile differs because
 	// not every version supports all profiles.
-	output1, err := stub.Match(ctx, []string{"media"})
-	if err != nil {
-		t.Fatalf("Match(%v) failed: %v", "media", err)
-	}
-	if !reflect.DeepEqual(envelopeV2, output1) {
-		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV2, output1)
-	}
-
-	output2, err := stub.Match(ctx, []string{"base"})
-	if err != nil {
-		t.Fatalf("Match(%v) failed: %v", "base", err)
-	}
-	if !reflect.DeepEqual(envelopeV3, output2) {
-		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output2)
-	}
+	checkEnvelope(t, ctx, envelopeV2, stub, "media")
+	checkEnvelope(t, ctx, envelopeV3, stub, "base")
 
 	// Test that we can add an envelope for v3 with profile media and after calling
 	// TidyNow(), there will be all versions still in glob but v0 will only match profile
@@ -463,26 +483,8 @@
 		"search/v3",
 	})
 
-	output3, err := stubs[0].Match(ctx, []string{"base"})
-	if err != nil {
-		t.Fatalf("Match(%v) failed: %v", "base", err)
-	}
-	if !reflect.DeepEqual(envelopeV3, output2) {
-		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output3)
-	}
-
-	output4, err := stubs[0].Match(ctx, []string{"base"})
-	if err != nil {
-		t.Fatalf("Match(%v) failed: %v", "base", err)
-	}
-	if !reflect.DeepEqual(envelopeV3, output2) {
-		t.Fatalf("Incorrect output: expected %v, got %v", envelopeV3, output4)
-	}
-
-	_, err = stubs[0].Match(ctx, []string{"media"})
-	if verror.ErrorID(err) != verror.ErrNoExist.ID {
-		t.Fatalf("got error %v,  expected %v", err, verror.ErrNoExist)
-	}
+	checkEnvelope(t, ctx, envelopeV1, stubs[0], "base")
+	checkNoEnvelope(t, ctx, stubs[0], "media")
 
 	stuffEnvelopes(t, ctx, stubs, []profEnvTuple{
 		{
diff --git a/services/application/applicationd/service.go b/services/application/applicationd/service.go
index 2ea8bff..0869f13 100644
--- a/services/application/applicationd/service.go
+++ b/services/application/applicationd/service.go
@@ -60,6 +60,43 @@
 	}
 }
 
+func (i *appRepoService) Profiles(ctx *context.T, call rpc.ServerCall) ([]string, error) {
+	ctx.VI(0).Infof("%v.Profiles()", i.suffix)
+	name, version, err := parse(ctx, i.suffix)
+	if err != nil {
+		return []string{}, err
+	}
+	i.store.Lock()
+	defer i.store.Unlock()
+
+	profiles, err := i.store.BindObject(naming.Join("/applications", name)).Children()
+	if err != nil {
+		return []string{}, err
+	}
+	if version == "" {
+		return profiles, nil
+	}
+	profilesRet := make(map[string]struct{})
+	for _, profile := range profiles {
+		versions, err := i.store.BindObject(naming.Join("/applications", name, profile)).Children()
+		if err != nil {
+			return []string{}, err
+		}
+		for _, v := range versions {
+			if version == v {
+				profilesRet[profile] = struct{}{}
+				break
+			}
+		}
+	}
+	ret := set.String.ToSlice(profilesRet)
+	if len(ret) == 0 {
+		return []string{}, verror.New(verror.ErrNoExist, ctx)
+	}
+	sort.Strings(ret)
+	return ret, nil
+}
+
 func (i *appRepoService) Match(ctx *context.T, call rpc.ServerCall, profiles []string) (application.Envelope, error) {
 	ctx.VI(0).Infof("%v.Match(%v)", i.suffix, profiles)
 	empty := application.Envelope{}
@@ -145,6 +182,52 @@
 	return nil
 }
 
+func (i *appRepoService) PutX(ctx *context.T, call rpc.ServerCall, profile string, envelope application.Envelope, overwrite bool) error {
+	ctx.VI(0).Infof("%v.PutX(%v, %v, %t)", i.suffix, profile, envelope, overwrite)
+	name, version, err := parse(ctx, i.suffix)
+	if err != nil {
+		return err
+	}
+	if version == "" {
+		return verror.New(ErrInvalidSuffix, ctx)
+	}
+	i.store.Lock()
+	defer i.store.Unlock()
+	// Transaction is rooted at "", so tname == tid.
+	tname, err := i.store.BindTransactionRoot("").CreateTransaction(call)
+	if err != nil {
+		return err
+	}
+
+	// Only add a Permissions value if there is not already one present.
+	apath := naming.Join("/acls", name, "data")
+	aobj := i.store.BindObject(apath)
+	if _, err := aobj.Get(call); verror.ErrorID(err) == fs.ErrNotInMemStore.ID {
+		rb, _ := security.RemoteBlessingNames(ctx, call.Security())
+		if len(rb) == 0 {
+			// None of the client's blessings are valid.
+			return verror.New(ErrNotAuthorized, ctx)
+		}
+		newperms := pathperms.PermissionsForBlessings(rb)
+		if _, err := aobj.Put(nil, newperms); err != nil {
+			return err
+		}
+	}
+
+	path := naming.Join(tname, "/applications", name, profile, version)
+	object := i.store.BindObject(path)
+	if _, err := object.Get(call); verror.ErrorID(err) != fs.ErrNotInMemStore.ID && !overwrite {
+		return verror.New(verror.ErrExist, ctx, "envelope already exists for profile", profile)
+	}
+	if _, err := object.Put(call, envelope); err != nil {
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
+		return verror.New(ErrOperationFailed, ctx)
+	}
+	return nil
+}
+
 func (i *appRepoService) Remove(ctx *context.T, call rpc.ServerCall, profile string) error {
 	ctx.VI(0).Infof("%v.Remove(%v)", i.suffix, profile)
 	name, version, err := parse(ctx, i.suffix)
@@ -158,20 +241,29 @@
 	if err != nil {
 		return err
 	}
-	path := naming.Join(tname, "/applications", name, profile)
-	if version != "" {
-		path += "/" + version
+	profiles := []string{profile}
+	if profile == "*" {
+		var err error
+		if profiles, err = i.store.BindObject(naming.Join("/applications", name)).Children(); err != nil {
+			return err
+		}
 	}
-	object := i.store.BindObject(path)
-	found, err := object.Exists(call)
-	if err != nil {
-		return verror.New(ErrOperationFailed, ctx)
-	}
-	if !found {
-		return verror.New(verror.ErrNoExist, ctx)
-	}
-	if err := object.Remove(call); err != nil {
-		return verror.New(ErrOperationFailed, ctx)
+	for _, profile := range profiles {
+		path := naming.Join(tname, "/applications", name, profile)
+		if version != "" {
+			path += "/" + version
+		}
+		object := i.store.BindObject(path)
+		found, err := object.Exists(call)
+		if err != nil {
+			return verror.New(ErrOperationFailed, ctx)
+		}
+		if !found {
+			return verror.New(verror.ErrNoExist, ctx)
+		}
+		if err := object.Remove(call); err != nil {
+			return verror.New(ErrOperationFailed, ctx)
+		}
 	}
 	if err := i.store.BindTransaction(tname).Commit(call); err != nil {
 		return verror.New(ErrOperationFailed, ctx)
diff --git a/services/repository/repository.vdl b/services/repository/repository.vdl
index 0e4cf1b..3990d3a 100644
--- a/services/repository/repository.vdl
+++ b/services/repository/repository.vdl
@@ -13,23 +13,38 @@
 	public "v.io/v23/services/repository"
 )
 
-// Application describes an application repository internally. Besides
-// the public Application interface, it allows to add and remove
-// application envelopes.
+// Application describes an application repository internally. Besides the
+// public Application interface, it allows adding and removing application
+// envelopes, as well as querying for a list of supported profiles.
 type Application interface {
 	public.Application
+	// DEPRECATED. Please use PutX for new code.
 	// Put adds the given tuple of application version (specified
 	// through the object name suffix) and application envelope to all
 	// of the given application profiles.
 	Put(Profiles []string, Envelope application.Envelope) error {access.Write}
+	// PutX adds the given application envelope for the given profile and
+	// application version (required, and specified through the object name
+	// suffix).
+	//
+	// An error is returned if an envelope already exists, unless the
+	// overwrite option is set.
+	PutX(Profile string, Envelope application.Envelope, Overwrite bool) error {access.Write}
 	// Remove removes the application envelope for the given profile
 	// name and application version (specified through the object name
-	// suffix). If no version is specified as part of the suffix, the
-	// method removes all versions for the given profile.
+	// suffix).
 	//
-	// TODO(jsimsa): Add support for using "*" to specify all profiles
-	// when Matt implements Globing (or Ken implements querying).
+	// If no version is specified as part of the suffix, the method removes
+	// all versions for the given profile.
+	//
+	// If the profile is the string "*", all profiles are removed for the
+	// given version (or for all versions if the version is not specified).
 	Remove(Profile string) error {access.Write}
+	// Profiles returns the supported profiles for the application version
+	// specified through the object name suffix.  If the version is not
+	// specified, Profiles returns the union of profiles across all
+	// versions.
+	Profiles() ([]string | error) {access.Read}
 }
 
 // Profile describes a profile internally. Besides the public Profile
diff --git a/services/repository/repository.vdl.go b/services/repository/repository.vdl.go
index 86454e4..65a3890 100644
--- a/services/repository/repository.vdl.go
+++ b/services/repository/repository.vdl.go
@@ -28,9 +28,9 @@
 // ApplicationClientMethods is the client interface
 // containing Application methods.
 //
-// Application describes an application repository internally. Besides
-// the public Application interface, it allows to add and remove
-// application envelopes.
+// Application describes an application repository internally. Besides the
+// public Application interface, it allows adding and removing application
+// envelopes, as well as querying for a list of supported profiles.
 type ApplicationClientMethods interface {
 	// Application provides access to application envelopes. An
 	// application envelope is identified by an application name and an
@@ -43,18 +43,33 @@
 	//   and executing the "search" application, version "v1", runnable
 	//   on either the "base" or "media" profile.
 	repository.ApplicationClientMethods
+	// DEPRECATED. Please use PutX for new code.
 	// Put adds the given tuple of application version (specified
 	// through the object name suffix) and application envelope to all
 	// of the given application profiles.
 	Put(ctx *context.T, Profiles []string, Envelope application.Envelope, opts ...rpc.CallOpt) error
+	// PutX adds the given application envelope for the given profile and
+	// application version (required, and specified through the object name
+	// suffix).
+	//
+	// An error is returned if an envelope already exists, unless the
+	// overwrite option is set.
+	PutX(ctx *context.T, Profile string, Envelope application.Envelope, Overwrite bool, opts ...rpc.CallOpt) error
 	// Remove removes the application envelope for the given profile
 	// name and application version (specified through the object name
-	// suffix). If no version is specified as part of the suffix, the
-	// method removes all versions for the given profile.
+	// suffix).
 	//
-	// TODO(jsimsa): Add support for using "*" to specify all profiles
-	// when Matt implements Globing (or Ken implements querying).
+	// If no version is specified as part of the suffix, the method removes
+	// all versions for the given profile.
+	//
+	// If the profile is the string "*", all profiles are removed for the
+	// given version (or for all versions if the version is not specified).
 	Remove(ctx *context.T, Profile string, opts ...rpc.CallOpt) error
+	// Profiles returns the supported profiles for the application version
+	// specified through the object name suffix.  If the version is not
+	// specified, Profiles returns the union of profiles across all
+	// versions.
+	Profiles(*context.T, ...rpc.CallOpt) ([]string, error)
 }
 
 // ApplicationClientStub adds universal methods to ApplicationClientMethods.
@@ -79,17 +94,27 @@
 	return
 }
 
+func (c implApplicationClientStub) PutX(ctx *context.T, i0 string, i1 application.Envelope, i2 bool, opts ...rpc.CallOpt) (err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "PutX", []interface{}{i0, i1, i2}, nil, opts...)
+	return
+}
+
 func (c implApplicationClientStub) Remove(ctx *context.T, i0 string, opts ...rpc.CallOpt) (err error) {
 	err = v23.GetClient(ctx).Call(ctx, c.name, "Remove", []interface{}{i0}, nil, opts...)
 	return
 }
 
+func (c implApplicationClientStub) Profiles(ctx *context.T, opts ...rpc.CallOpt) (o0 []string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "Profiles", nil, []interface{}{&o0}, opts...)
+	return
+}
+
 // ApplicationServerMethods is the interface a server writer
 // implements for Application.
 //
-// Application describes an application repository internally. Besides
-// the public Application interface, it allows to add and remove
-// application envelopes.
+// Application describes an application repository internally. Besides the
+// public Application interface, it allows adding and removing application
+// envelopes, as well as querying for a list of supported profiles.
 type ApplicationServerMethods interface {
 	// Application provides access to application envelopes. An
 	// application envelope is identified by an application name and an
@@ -102,18 +127,33 @@
 	//   and executing the "search" application, version "v1", runnable
 	//   on either the "base" or "media" profile.
 	repository.ApplicationServerMethods
+	// DEPRECATED. Please use PutX for new code.
 	// Put adds the given tuple of application version (specified
 	// through the object name suffix) and application envelope to all
 	// of the given application profiles.
 	Put(ctx *context.T, call rpc.ServerCall, Profiles []string, Envelope application.Envelope) error
+	// PutX adds the given application envelope for the given profile and
+	// application version (required, and specified through the object name
+	// suffix).
+	//
+	// An error is returned if an envelope already exists, unless the
+	// overwrite option is set.
+	PutX(ctx *context.T, call rpc.ServerCall, Profile string, Envelope application.Envelope, Overwrite bool) error
 	// Remove removes the application envelope for the given profile
 	// name and application version (specified through the object name
-	// suffix). If no version is specified as part of the suffix, the
-	// method removes all versions for the given profile.
+	// suffix).
 	//
-	// TODO(jsimsa): Add support for using "*" to specify all profiles
-	// when Matt implements Globing (or Ken implements querying).
+	// If no version is specified as part of the suffix, the method removes
+	// all versions for the given profile.
+	//
+	// If the profile is the string "*", all profiles are removed for the
+	// given version (or for all versions if the version is not specified).
 	Remove(ctx *context.T, call rpc.ServerCall, Profile string) error
+	// Profiles returns the supported profiles for the application version
+	// specified through the object name suffix.  If the version is not
+	// specified, Profiles returns the union of profiles across all
+	// versions.
+	Profiles(*context.T, rpc.ServerCall) ([]string, error)
 }
 
 // ApplicationServerStubMethods is the server interface containing
@@ -157,10 +197,18 @@
 	return s.impl.Put(ctx, call, i0, i1)
 }
 
+func (s implApplicationServerStub) PutX(ctx *context.T, call rpc.ServerCall, i0 string, i1 application.Envelope, i2 bool) error {
+	return s.impl.PutX(ctx, call, i0, i1, i2)
+}
+
 func (s implApplicationServerStub) Remove(ctx *context.T, call rpc.ServerCall, i0 string) error {
 	return s.impl.Remove(ctx, call, i0)
 }
 
+func (s implApplicationServerStub) Profiles(ctx *context.T, call rpc.ServerCall) ([]string, error) {
+	return s.impl.Profiles(ctx, call)
+}
+
 func (s implApplicationServerStub) Globber() *rpc.GlobState {
 	return s.gs
 }
@@ -176,14 +224,14 @@
 var descApplication = rpc.InterfaceDesc{
 	Name:    "Application",
 	PkgPath: "v.io/x/ref/services/repository",
-	Doc:     "// Application describes an application repository internally. Besides\n// the public Application interface, it allows to add and remove\n// application envelopes.",
+	Doc:     "// Application describes an application repository internally. Besides the\n// public Application interface, it allows adding and removing application\n// envelopes, as well as querying for a list of supported profiles.",
 	Embeds: []rpc.EmbedDesc{
 		{"Application", "v.io/v23/services/repository", "// Application provides access to application envelopes. An\n// application envelope is identified by an application name and an\n// application version, which are specified through the object name,\n// and a profile name, which is specified using a method argument.\n//\n// Example:\n// /apps/search/v1.Match([]string{\"base\", \"media\"})\n//   returns an application envelope that can be used for downloading\n//   and executing the \"search\" application, version \"v1\", runnable\n//   on either the \"base\" or \"media\" profile."},
 	},
 	Methods: []rpc.MethodDesc{
 		{
 			Name: "Put",
-			Doc:  "// Put adds the given tuple of application version (specified\n// through the object name suffix) and application envelope to all\n// of the given application profiles.",
+			Doc:  "// DEPRECATED. Please use PutX for new code.\n// Put adds the given tuple of application version (specified\n// through the object name suffix) and application envelope to all\n// of the given application profiles.",
 			InArgs: []rpc.ArgDesc{
 				{"Profiles", ``}, // []string
 				{"Envelope", ``}, // application.Envelope
@@ -191,13 +239,31 @@
 			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
 		},
 		{
+			Name: "PutX",
+			Doc:  "// PutX adds the given application envelope for the given profile and\n// application version (required, and specified through the object name\n// suffix).\n//\n// An error is returned if an envelope already exists, unless the\n// overwrite option is set.",
+			InArgs: []rpc.ArgDesc{
+				{"Profile", ``},   // string
+				{"Envelope", ``},  // application.Envelope
+				{"Overwrite", ``}, // bool
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
+		},
+		{
 			Name: "Remove",
-			Doc:  "// Remove removes the application envelope for the given profile\n// name and application version (specified through the object name\n// suffix). If no version is specified as part of the suffix, the\n// method removes all versions for the given profile.\n//\n// TODO(jsimsa): Add support for using \"*\" to specify all profiles\n// when Matt implements Globing (or Ken implements querying).",
+			Doc:  "// Remove removes the application envelope for the given profile\n// name and application version (specified through the object name\n// suffix).\n//\n// If no version is specified as part of the suffix, the method removes\n// all versions for the given profile.\n//\n// If the profile is the string \"*\", all profiles are removed for the\n// given version (or for all versions if the version is not specified).",
 			InArgs: []rpc.ArgDesc{
 				{"Profile", ``}, // string
 			},
 			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Write"))},
 		},
+		{
+			Name: "Profiles",
+			Doc:  "// Profiles returns the supported profiles for the application version\n// specified through the object name suffix.  If the version is not\n// specified, Profiles returns the union of profiles across all\n// versions.",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // []string
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
+		},
 	},
 }