Merge "mounttable: Remove the MountX call."
diff --git a/cmd/gclogs/doc.go b/cmd/gclogs/doc.go
index 2fd759b..eb15ca1 100644
--- a/cmd/gclogs/doc.go
+++ b/cmd/gclogs/doc.go
@@ -31,5 +31,50 @@
    ".*test".
  -verbose=false
    If true, each deleted file is shown on stdout.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -vanadium.i18n_catalogue=
+   18n catalogue files to load, comma separated
+ -veyron.acl.file=map[]
+   specify an acl file as <name>:<aclfile>
+ -veyron.acl.literal=
+   explicitly specify the runtime acl as a JSON-encoded access.Permissions.
+   Overrides all --veyron.acl.file flags.
+ -veyron.credentials=
+   directory to use for storing security credentials
+ -veyron.namespace.root=[/ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -veyron.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -veyron.tcp.address=
+   address to listen on
+ -veyron.tcp.protocol=wsh
+   protocol to listen with
+ -veyron.vtrace.cache_size=1024
+   The number of vtrace traces to store in memory.
+ -veyron.vtrace.collect_regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -veyron.vtrace.dump_on_shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -veyron.vtrace.sample_rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for file-filtered logging
 */
 package main
diff --git a/cmd/gclogs/gclogs.go b/cmd/gclogs/gclogs.go
index d19b8b0..1939b25 100644
--- a/cmd/gclogs/gclogs.go
+++ b/cmd/gclogs/gclogs.go
@@ -13,7 +13,9 @@
 	"regexp"
 	"time"
 
+	"v.io/v23"
 	"v.io/x/lib/cmdline"
+	_ "v.io/x/ref/profiles/static"
 )
 
 var (
@@ -49,6 +51,9 @@
 }
 
 func garbageCollectLogs(cmd *cmdline.Command, args []string) error {
+	_, shutdown := v23.Init()
+	defer shutdown()
+
 	if len(args) == 0 {
 		cmd.UsageErrorf("gclogs requires at least one argument")
 	}
diff --git a/cmd/gclogs/main.go b/cmd/gclogs/main.go
index 8468057..b8e066f 100644
--- a/cmd/gclogs/main.go
+++ b/cmd/gclogs/main.go
@@ -7,7 +7,9 @@
 
 package main
 
-import "os"
+import (
+	"os"
+)
 
 func main() {
 	os.Exit(cmdGCLogs.Main())
diff --git a/cmd/uniqueid/doc.go b/cmd/uniqueid/doc.go
index 79eeba3..67b7de5 100644
--- a/cmd/uniqueid/doc.go
+++ b/cmd/uniqueid/doc.go
@@ -18,6 +18,51 @@
    help        Display help for commands or topics
 Run "uniqueid help [command]" for command usage.
 
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -vanadium.i18n_catalogue=
+   18n catalogue files to load, comma separated
+ -veyron.acl.file=map[]
+   specify an acl file as <name>:<aclfile>
+ -veyron.acl.literal=
+   explicitly specify the runtime acl as a JSON-encoded access.Permissions.
+   Overrides all --veyron.acl.file flags.
+ -veyron.credentials=
+   directory to use for storing security credentials
+ -veyron.namespace.root=[/ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -veyron.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -veyron.tcp.address=
+   address to listen on
+ -veyron.tcp.protocol=wsh
+   protocol to listen with
+ -veyron.vtrace.cache_size=1024
+   The number of vtrace traces to store in memory.
+ -veyron.vtrace.collect_regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -veyron.vtrace.dump_on_shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -veyron.vtrace.sample_rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for file-filtered logging
+
 Uniqueid Generate
 
 Generates unique ids and outputs them to standard out.
diff --git a/cmd/uniqueid/main.go b/cmd/uniqueid/main.go
index 1d6b598..3f8a16a 100644
--- a/cmd/uniqueid/main.go
+++ b/cmd/uniqueid/main.go
@@ -14,14 +14,24 @@
 	"os"
 	"regexp"
 
+	"v.io/v23"
 	"v.io/v23/uniqueid"
 	"v.io/x/lib/cmdline"
+	_ "v.io/x/ref/profiles/static"
 )
 
 func main() {
 	os.Exit(cmdUniqueId.Main())
 }
 
+func runHelper(run cmdline.Runner) cmdline.Runner {
+	return func(cmd *cmdline.Command, args []string) error {
+		_, shutdown := v23.Init()
+		defer shutdown()
+		return run(cmd, args)
+	}
+}
+
 var cmdUniqueId = &cmdline.Command{
 	Name:  "uniqueid",
 	Short: "Generates UniqueIds.",
@@ -34,7 +44,7 @@
 }
 
 var cmdGenerate = &cmdline.Command{
-	Run:   runGenerate,
+	Run:   runHelper(runGenerate),
 	Name:  "generate",
 	Short: "Generates UniqueIds",
 	Long: `
@@ -45,7 +55,7 @@
 }
 
 var cmdInject = &cmdline.Command{
-	Run:   runInject,
+	Run:   runHelper(runInject),
 	Name:  "inject",
 	Short: "Injects UniqueIds into existing files",
 	Long: `
diff --git a/cmd/vdl/arith_test.go b/cmd/vdl/arith_test.go
index 5a4e54a..0c2600f 100644
--- a/cmd/vdl/arith_test.go
+++ b/cmd/vdl/arith_test.go
@@ -22,7 +22,6 @@
 	"v.io/x/ref/lib/vdl/testdata/arith"
 	"v.io/x/ref/lib/vdl/testdata/base"
 
-	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/test"
 )
 
diff --git a/cmd/vdl/doc.go b/cmd/vdl/doc.go
index 4cc7b5f..0e06c1f 100644
--- a/cmd/vdl/doc.go
+++ b/cmd/vdl/doc.go
@@ -39,6 +39,51 @@
  -vdl.config=vdl.config
    Basename of the optional per-package config file.
 
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -vanadium.i18n_catalogue=
+   18n catalogue files to load, comma separated
+ -veyron.acl.file=map[]
+   specify an acl file as <name>:<aclfile>
+ -veyron.acl.literal=
+   explicitly specify the runtime acl as a JSON-encoded access.Permissions.
+   Overrides all --veyron.acl.file flags.
+ -veyron.credentials=
+   directory to use for storing security credentials
+ -veyron.namespace.root=[/ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -veyron.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -veyron.tcp.address=
+   address to listen on
+ -veyron.tcp.protocol=wsh
+   protocol to listen with
+ -veyron.vtrace.cache_size=1024
+   The number of vtrace traces to store in memory.
+ -veyron.vtrace.collect_regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -veyron.vtrace.dump_on_shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -veyron.vtrace.sample_rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for file-filtered logging
+
 Vdl Generate
 
 Generate compiles packages and their transitive dependencies, and generates code
diff --git a/cmd/vdl/main.go b/cmd/vdl/main.go
index 2e7666f..f7e802d 100644
--- a/cmd/vdl/main.go
+++ b/cmd/vdl/main.go
@@ -16,6 +16,7 @@
 	"path/filepath"
 	"strings"
 
+	"v.io/v23"
 	"v.io/v23/vdlroot/vdltool"
 	"v.io/x/lib/cmdline"
 	"v.io/x/lib/textutil"
@@ -25,6 +26,7 @@
 	"v.io/x/ref/lib/vdl/codegen/javascript"
 	"v.io/x/ref/lib/vdl/compile"
 	"v.io/x/ref/lib/vdl/vdlutil"
+	_ "v.io/x/ref/profiles/static"
 )
 
 func init() {
@@ -47,6 +49,9 @@
 // targets, and calls the supplied run function.
 func runHelper(run func(targets []*build.Package, env *compile.Env)) func(cmd *cmdline.Command, args []string) error {
 	return func(cmd *cmdline.Command, args []string) error {
+		_, shutdown := v23.Init()
+		defer shutdown()
+
 		if flagVerbose {
 			vdlutil.SetVerbose()
 		}
diff --git a/cmd/vom/doc.go b/cmd/vom/doc.go
index 842764d..4fc7d25 100644
--- a/cmd/vom/doc.go
+++ b/cmd/vom/doc.go
@@ -17,6 +17,51 @@
    help        Display help for commands or topics
 Run "vom help [command]" for command usage.
 
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -vanadium.i18n_catalogue=
+   18n catalogue files to load, comma separated
+ -veyron.acl.file=map[]
+   specify an acl file as <name>:<aclfile>
+ -veyron.acl.literal=
+   explicitly specify the runtime acl as a JSON-encoded access.Permissions.
+   Overrides all --veyron.acl.file flags.
+ -veyron.credentials=
+   directory to use for storing security credentials
+ -veyron.namespace.root=[/ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -veyron.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -veyron.tcp.address=
+   address to listen on
+ -veyron.tcp.protocol=wsh
+   protocol to listen with
+ -veyron.vtrace.cache_size=1024
+   The number of vtrace traces to store in memory.
+ -veyron.vtrace.collect_regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -veyron.vtrace.dump_on_shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -veyron.vtrace.sample_rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for file-filtered logging
+
 Vom Decode
 
 Decode decodes data encoded in the vom format.  If no arguments are provided,
diff --git a/cmd/vom/vom.go b/cmd/vom/vom.go
index 3126f90..2e2d2ce 100644
--- a/cmd/vom/vom.go
+++ b/cmd/vom/vom.go
@@ -17,15 +17,25 @@
 	"strings"
 	"unicode"
 
+	"v.io/v23"
 	"v.io/v23/vdl"
 	"v.io/v23/vom"
 	"v.io/x/lib/cmdline"
+	_ "v.io/x/ref/profiles/static"
 )
 
 func main() {
 	os.Exit(cmdVom.Main())
 }
 
+func runHelper(run cmdline.Runner) cmdline.Runner {
+	return func(cmd *cmdline.Command, args []string) error {
+		_, shutdown := v23.Init()
+		defer shutdown()
+		return run(cmd, args)
+	}
+}
+
 var cmdVom = &cmdline.Command{
 	Name:  "vom",
 	Short: "Vanadium Object Marshaling debugging tool",
@@ -36,7 +46,7 @@
 }
 
 var cmdDecode = &cmdline.Command{
-	Run:   runDecode,
+	Run:   runHelper(runDecode),
 	Name:  "decode",
 	Short: "Decode data encoded in the vom format",
 	Long: `
@@ -53,7 +63,7 @@
 }
 
 var cmdDump = &cmdline.Command{
-	Run:   runDump,
+	Run:   runHelper(runDump),
 	Name:  "dump",
 	Short: "Dump data encoded in the vom format into formatted output",
 	Long: `
diff --git a/cmd/vomtestgen/doc.go b/cmd/vomtestgen/doc.go
index 78c9098..e21085b 100644
--- a/cmd/vomtestgen/doc.go
+++ b/cmd/vomtestgen/doc.go
@@ -30,5 +30,50 @@
    Comma-separated list of valid VDL file name extensions.
  -max_errors=-1
    Stop processing after this many errors, or -1 for unlimited.
+
+The global flags are:
+ -alsologtostderr=true
+   log to standard error as well as files
+ -log_backtrace_at=:0
+   when logging hits line file:N, emit a stack trace
+ -log_dir=
+   if non-empty, write log files to this directory
+ -logtostderr=false
+   log to standard error instead of files
+ -max_stack_buf_size=4292608
+   max size in bytes of the buffer to use for logging stack traces
+ -stderrthreshold=2
+   logs at or above this threshold go to stderr
+ -v=0
+   log level for V logs
+ -vanadium.i18n_catalogue=
+   18n catalogue files to load, comma separated
+ -veyron.acl.file=map[]
+   specify an acl file as <name>:<aclfile>
+ -veyron.acl.literal=
+   explicitly specify the runtime acl as a JSON-encoded access.Permissions.
+   Overrides all --veyron.acl.file flags.
+ -veyron.credentials=
+   directory to use for storing security credentials
+ -veyron.namespace.root=[/ns.dev.v.io:8101]
+   local namespace root; can be repeated to provided multiple roots
+ -veyron.proxy=
+   object name of proxy service to use to export services across network
+   boundaries
+ -veyron.tcp.address=
+   address to listen on
+ -veyron.tcp.protocol=wsh
+   protocol to listen with
+ -veyron.vtrace.cache_size=1024
+   The number of vtrace traces to store in memory.
+ -veyron.vtrace.collect_regexp=
+   Spans and annotations that match this regular expression will trigger trace
+   collection.
+ -veyron.vtrace.dump_on_shutdown=true
+   If true, dump all stored traces on runtime shutdown.
+ -veyron.vtrace.sample_rate=0
+   Rate (from 0.0 to 1.0) to sample vtrace traces.
+ -vmodule=
+   comma-separated list of pattern=N settings for file-filtered logging
 */
 package main
diff --git a/cmd/vomtestgen/generate.go b/cmd/vomtestgen/generate.go
index ca4a434..63f9f88 100644
--- a/cmd/vomtestgen/generate.go
+++ b/cmd/vomtestgen/generate.go
@@ -13,6 +13,7 @@
 	"path/filepath"
 	"strings"
 
+	"v.io/v23"
 	"v.io/v23/vdl"
 	"v.io/v23/vom"
 	"v.io/x/lib/cmdline"
@@ -20,6 +21,7 @@
 	"v.io/x/ref/lib/vdl/codegen"
 	"v.io/x/ref/lib/vdl/codegen/vdlgen"
 	"v.io/x/ref/lib/vdl/compile"
+	_ "v.io/x/ref/profiles/static"
 )
 
 const (
@@ -63,6 +65,9 @@
 }
 
 func runGenerate(cmd *cmdline.Command, args []string) error {
+	_, shutdown := v23.Init()
+	defer shutdown()
+
 	debug := new(bytes.Buffer)
 	defer dumpDebug(cmd.Stderr(), debug)
 	env := compile.NewEnv(optGenMaxErrors)
diff --git a/cmd/vomtestgen/main.go b/cmd/vomtestgen/main.go
index ece59fa..fa43955 100644
--- a/cmd/vomtestgen/main.go
+++ b/cmd/vomtestgen/main.go
@@ -7,7 +7,9 @@
 
 package main
 
-import "os"
+import (
+	"os"
+)
 
 func main() {
 	os.Exit(cmdGenerate.Main())
diff --git a/cmd/vrun/vrun.go b/cmd/vrun/vrun.go
index e115e98..820ace6 100644
--- a/cmd/vrun/vrun.go
+++ b/cmd/vrun/vrun.go
@@ -5,7 +5,6 @@
 package main
 
 import (
-	"flag"
 	"os"
 	"path/filepath"
 	"syscall"
@@ -15,6 +14,7 @@
 	"v.io/x/ref/lib/flags/consts"
 	"v.io/x/ref/security/agent"
 	"v.io/x/ref/security/agent/keymgr"
+	isecurity "v.io/x/ref/services/security"
 
 	"v.io/v23"
 	"v.io/v23/context"
@@ -24,8 +24,11 @@
 	_ "v.io/x/ref/profiles"
 )
 
-var durationFlag time.Duration
-var nameOverride string
+var (
+	durationFlag time.Duration
+	name         string
+	role         string
+)
 
 var cmdVrun = &cmdline.Command{
 	Run:      vrun,
@@ -39,10 +42,9 @@
 	syscall.CloseOnExec(3)
 	syscall.CloseOnExec(4)
 
-	flag.DurationVar(&durationFlag, "duration", 1*time.Hour, "Duration for the blessing.")
-	flag.StringVar(&nameOverride, "name", "", "Name to use for the blessing. Uses the command name if unset.")
 	cmdVrun.Flags.DurationVar(&durationFlag, "duration", 1*time.Hour, "Duration for the blessing.")
-	cmdVrun.Flags.StringVar(&nameOverride, "name", "", "Name to use for the blessing. Uses the command name if unset.")
+	cmdVrun.Flags.StringVar(&name, "name", "", "Name to use for the blessing. Uses the command name if unset.")
+	cmdVrun.Flags.StringVar(&role, "role", "", "Role object from which to request the blessing. If set, the blessings from this role server are used and --name is ignored. If not set, the default blessings of the calling principal are extended with --name.")
 
 	os.Exit(cmdVrun.Main())
 }
@@ -58,10 +60,30 @@
 	if err != nil {
 		return err
 	}
-	err = bless(ctx, principal, filepath.Base(args[0]))
-	if err != nil {
-		return err
+	if len(role) == 0 {
+		if len(name) == 0 {
+			name = filepath.Base(args[0])
+		}
+		if err := bless(ctx, principal, name); err != nil {
+			return err
+		}
+	} else {
+		// The role server expects the client's blessing name to end
+		// with RoleSuffix. This is to avoid accidentally granting role
+		// access to anything else that might have been blessed by the
+		// same principal.
+		if err := bless(ctx, principal, isecurity.RoleSuffix); err != nil {
+			return err
+		}
+		rCtx, err := v23.SetPrincipal(ctx, principal)
+		if err != nil {
+			return err
+		}
+		if err := setupRoleBlessings(rCtx, role); err != nil {
+			return err
+		}
 	}
+
 	return doExec(args, conn)
 }
 
@@ -71,9 +93,6 @@
 		vlog.Errorf("Couldn't create caveat")
 		return err
 	}
-	if 0 != len(nameOverride) {
-		name = nameOverride
-	}
 
 	rp := v23.GetPrincipal(ctx)
 	blessing, err := rp.Bless(p.PublicKey(), rp.BlessingStore().Default(), name, caveat)
@@ -139,3 +158,21 @@
 	}
 	return principal, conn, nil
 }
+
+func setupRoleBlessings(ctx *context.T, role string) error {
+	b, err := isecurity.RoleClient(role).SeekBlessings(ctx)
+	if err != nil {
+		return err
+	}
+	p := v23.GetPrincipal(ctx)
+	// TODO(rthellend,ashankar): Revisit this configuration.
+	// SetDefault: Should we expect users to want to act as a server on behalf of the role (by default?)
+	// AllPrincipals: Do we not want to be discriminating about which services we use the role blessing at.
+	if err := p.BlessingStore().SetDefault(b); err != nil {
+		return err
+	}
+	if _, err := p.BlessingStore().Set(b, security.AllPrincipals); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/examples/tunnel/tunneld/main.go b/examples/tunnel/tunneld/main.go
index 090c416..fb20a8b 100644
--- a/examples/tunnel/tunneld/main.go
+++ b/examples/tunnel/tunneld/main.go
@@ -54,14 +54,9 @@
 	defer server.Stop()
 
 	listenSpec := v23.GetListenSpec(ctx)
-	eps, err := server.Listen(listenSpec)
-	if err != nil {
+	if _, err := server.Listen(listenSpec); err != nil {
 		vlog.Fatalf("Listen(%v) failed: %v", listenSpec, err)
 	}
-	vlog.Infof("Listening on: %v", eps)
-	if len(eps) > 0 {
-		fmt.Printf("NAME=%s\n", eps[0].Name())
-	}
 	hwaddr, err := firstHardwareAddrInUse()
 	if err != nil {
 		vlog.Fatalf("Couldn't find a good hw address: %v", err)
@@ -85,6 +80,11 @@
 	if !published {
 		vlog.Fatalf("Failed to publish with any of %v", names)
 	}
+	status := server.Status()
+	vlog.Infof("Listening on: %v", status.Endpoints)
+	if len(status.Endpoints) > 0 {
+		fmt.Printf("NAME=%s\n", status.Endpoints[0].Name())
+	}
 	vlog.Infof("Published as %v", names)
 
 	<-signals.ShutdownOnSignals(ctx)
diff --git a/lib/exec/child.go b/lib/exec/child.go
index cf2a141..b80033c 100644
--- a/lib/exec/child.go
+++ b/lib/exec/child.go
@@ -6,19 +6,22 @@
 
 import (
 	"encoding/binary"
-	"errors"
 	"io"
 	"os"
 	"strconv"
 	"sync"
 	"unicode/utf8"
 
+	"v.io/v23/verror"
 	"v.io/x/ref/lib/exec/consts"
 )
 
 var (
-	ErrNoVersion          = errors.New(consts.ExecVersionVariable + " environment variable missing")
-	ErrUnsupportedVersion = errors.New("Unsupported version of v.io/x/ref/lib/exec request by " + consts.ExecVersionVariable + " environment variable")
+	ErrNoVersion          = verror.Register(pkgPath+".ErrNoVersion", verror.NoRetry, "{1:}{2:} "+consts.ExecVersionVariable+" environment variable missing{:_}")
+	ErrUnsupportedVersion = verror.Register(pkgPath+".ErrUnsupportedVersion", verror.NoRetry, "{1:}{2:} Unsupported version of v.io/x/ref/lib/exec request by "+consts.ExecVersionVariable+" environment variable{:_}")
+
+	errDifferentStatusSent = verror.Register(pkgPath+".errDifferentStatusSent", verror.NoRetry, "{1:}{2:} A different status: {3} has already been sent{:_}")
+	errPartialRead         = verror.Register(pkgPath+".PartialRead", verror.NoRetry, "{1:}{2:} partial read{:_}")
 )
 
 type ChildHandle struct {
@@ -92,7 +95,7 @@
 		_, c.statusErr = c.statusPipe.Write(toWrite)
 		c.statusPipe.Close()
 	} else if c.sentStatus != status {
-		return errors.New("A different status: " + c.sentStatus + " has already been sent.")
+		return verror.New(errDifferentStatusSent, nil, c.sentStatus)
 	}
 	return c.statusErr
 }
@@ -123,11 +126,11 @@
 	// version #s.
 	switch os.Getenv(consts.ExecVersionVariable) {
 	case "":
-		return nil, ErrNoVersion
+		return nil, verror.New(ErrNoVersion, nil)
 	case version1:
 		os.Setenv(consts.ExecVersionVariable, "")
 	default:
-		return nil, ErrUnsupportedVersion
+		return nil, verror.New(ErrUnsupportedVersion, nil)
 	}
 	dataPipe := os.NewFile(3, "data_rd")
 	serializedConfig, err := decodeString(dataPipe)
@@ -160,7 +163,7 @@
 		if err != nil {
 			return "", err
 		} else {
-			return "", errors.New("partial read")
+			return "", verror.New(errPartialRead, nil)
 		}
 	}
 	return string(data), nil
diff --git a/lib/exec/exec_test.go b/lib/exec/exec_test.go
index b930bd7..0eaf7af 100644
--- a/lib/exec/exec_test.go
+++ b/lib/exec/exec_test.go
@@ -17,6 +17,7 @@
 	"time"
 	"unicode/utf8"
 
+	"v.io/v23/verror"
 	vexec "v.io/x/ref/lib/exec"
 	"v.io/x/ref/lib/exec/consts"
 	// Use mock timekeeper to avoid actually sleeping during the test.
@@ -160,7 +161,7 @@
 func TestNoVersion(t *testing.T) {
 	// Make sure that Init correctly tests for the presence of VEXEC_VERSION
 	_, err := vexec.GetChildHandle()
-	if err != vexec.ErrNoVersion {
+	if verror.ErrorID(err) != vexec.ErrNoVersion.ID {
 		t.Errorf("Should be missing Version")
 	}
 }
@@ -266,7 +267,7 @@
 	cmd := helperCommand(name, "failed", "to", "start")
 	ph := vexec.NewParentHandle(cmd)
 	err := waitForReady(t, cmd, name, 4, ph)
-	if err == nil || err.Error() != "failed to start" {
+	if err == nil || !strings.Contains(err.Error(), "failed to start") {
 		t.Errorf("unexpected error: %v", err)
 	}
 }
@@ -276,7 +277,7 @@
 	cmd := helperCommand(name, "invalid", "utf8", string([]byte{0xFF}), "in", string([]byte{0xFC}), "error", "message")
 	ph := vexec.NewParentHandle(cmd)
 	err := waitForReady(t, cmd, name, 4, ph)
-	if err == nil || err.Error() != "invalid utf8 "+string(utf8.RuneError)+" in "+string(utf8.RuneError)+" error message" {
+	if err == nil || !strings.Contains(err.Error(), "invalid utf8 "+string(utf8.RuneError)+" in "+string(utf8.RuneError)+" error message") {
 		t.Errorf("unexpected error: %v", err)
 	}
 }
@@ -288,7 +289,7 @@
 	stderr, _ := cmd.StderrPipe()
 	ph := vexec.NewParentHandle(cmd)
 	err := waitForReady(t, cmd, name, 1, ph)
-	if err != vexec.ErrTimeout {
+	if verror.ErrorID(err) != vexec.ErrTimeout.ID {
 		t.Errorf("Failed to get timeout: got %v\n", err)
 	} else {
 		// block waiting for error from child
@@ -322,7 +323,7 @@
 		tk.AdvanceTime(toWait)
 	}()
 	err = waitForReady(t, cmd, name, 1, ph)
-	if err != vexec.ErrTimeout {
+	if verror.ErrorID(err) != vexec.ErrTimeout.ID {
 		t.Errorf("Failed to get timeout: got %v\n", err)
 	} else {
 		// After the parent timed out, wake up the child and let it
@@ -449,7 +450,7 @@
 		<-tk.Requests()
 		tk.AdvanceTime(2 * time.Second)
 	}()
-	if got, want := ph.Wait(time.Second), vexec.ErrTimeout; got == nil || got.Error() != want.Error() {
+	if got, want := ph.Wait(time.Second), vexec.ErrTimeout.ID; got == nil || verror.ErrorID(got) != want {
 		t.Errorf("Wait returned %v, wanted %v instead", got, want)
 	}
 	if got, want := ph.Clean(), "signal: killed"; got == nil || got.Error() != want {
diff --git a/lib/exec/noprotocol_test.go b/lib/exec/noprotocol_test.go
index 8e9aa21..78564c9 100644
--- a/lib/exec/noprotocol_test.go
+++ b/lib/exec/noprotocol_test.go
@@ -12,6 +12,7 @@
 	"testing"
 	"time"
 
+	"v.io/v23/verror"
 	vexec "v.io/x/ref/lib/exec"
 	"v.io/x/ref/lib/exec/consts"
 )
@@ -23,7 +24,7 @@
 	if err := ph.Start(); err != nil {
 		t.Fatal(err)
 	}
-	if got, want := ph.WaitForReady(time.Minute), vexec.ErrNotUsingProtocol; got != want {
+	if got, want := ph.WaitForReady(time.Minute), vexec.ErrNotUsingProtocol.ID; verror.ErrorID(got) != want {
 		t.Fatalf("got %v, want %v", got, want)
 	}
 	re := regexp.MustCompile(fmt.Sprintf(".*%s=.*", consts.ExecVersionVariable))
diff --git a/lib/exec/parent.go b/lib/exec/parent.go
index fc11a2b..d011cfb 100644
--- a/lib/exec/parent.go
+++ b/lib/exec/parent.go
@@ -7,7 +7,6 @@
 import (
 	"bytes"
 	"encoding/binary"
-	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -18,17 +17,27 @@
 	"syscall"
 	"time"
 
+	"v.io/v23/verror"
+
 	"v.io/x/lib/vlog"
 
 	"v.io/x/ref/lib/exec/consts"
 	"v.io/x/ref/lib/timekeeper"
 )
 
+const pkgPath = "v.io/x/ref/lib/exec"
+
 var (
-	ErrAuthTimeout      = errors.New("timeout in auth handshake")
-	ErrTimeout          = errors.New("timeout waiting for child")
-	ErrSecretTooLarge   = errors.New("secret is too large")
-	ErrNotUsingProtocol = errors.New("not using parent/child exec protocol")
+	ErrAuthTimeout      = verror.Register(pkgPath+".ErrAuthTimeout", verror.NoRetry, "{1:}{2:} timeout in auth handshake{:_}")
+	ErrTimeout          = verror.Register(pkgPath+".ErrTimeout", verror.NoRetry, "{1:}{2:} timeout waiting for child{:_}")
+	ErrSecretTooLarge   = verror.Register(pkgPath+".ErrSecretTooLarge", verror.NoRetry, "{1:}{2:} secret is too large{:_}")
+	ErrNotUsingProtocol = verror.Register(pkgPath+".ErrNotUsingProtocol", verror.NoRetry, "{1:}{2:} not using parent/child exec protocol{:_}")
+
+	errFailedStatus       = verror.Register(pkgPath+".errFailedStatus", verror.NoRetry, "{1:}{2:} {_}")
+	errUnrecognizedStatus = verror.Register(pkgPath+".errUnrecognizedStatus", verror.NoRetry, "{1:}{2:} unrecognised status from subprocess{:_}")
+	errUnexpectedType     = verror.Register(pkgPath+".errUnexpectedType", verror.NoRetry, "{1:}{2:} unexpected type {3}{:_}")
+	errNoSuchProcess      = verror.Register(pkgPath+".errNoSuchProcess", verror.NoRetry, "{1:}{2:} no such process{:_}")
+	errPartialWrite       = verror.Register(pkgPath+".errPartialWrite", verror.NoRetry, "{1:}{2:} partial write{:_}")
 )
 
 // A ParentHandle is the Parent process' means of managing a single child.
@@ -234,7 +243,7 @@
 // WaitForReady will wait for the child process to become ready.
 func (p *ParentHandle) WaitForReady(timeout time.Duration) error {
 	if !p.protocol {
-		return ErrNotUsingProtocol
+		return verror.New(ErrNotUsingProtocol, nil)
 	}
 	// An invariant of WaitForReady is that both statusWrite and statusRead
 	// get closed before WaitForStatus returns (statusRead gets closed by
@@ -260,11 +269,11 @@
 				return nil
 			}
 			if strings.HasPrefix(m, failedStatus) {
-				return fmt.Errorf("%s", strings.TrimPrefix(m, failedStatus))
+				return verror.New(errFailedStatus, nil, strings.TrimPrefix(m, failedStatus))
 			}
-			return fmt.Errorf("unrecognised status from subprocess: %q", m)
+			return verror.New(errUnrecognizedStatus, nil, m)
 		default:
-			return fmt.Errorf("unexpected type %T", m)
+			return verror.New(errUnexpectedType, nil, fmt.Sprintf("%T", m))
 		}
 	case <-p.tk.After(timeout):
 		vlog.Errorf("Timed out waiting for child status")
@@ -281,7 +290,7 @@
 		// c.  Waiting on c ensures that r.Close() in waitForStatus
 		// already executed.
 		<-c
-		return ErrTimeout
+		return verror.New(ErrTimeout, nil)
 	}
 	panic("unreachable")
 }
@@ -315,7 +324,7 @@
 	if timeout > 0 {
 		select {
 		case <-p.tk.After(timeout):
-			return ErrTimeout
+			return verror.New(ErrTimeout, nil)
 		case err := <-c:
 			return err
 		}
@@ -350,7 +359,7 @@
 // Kill kills the child process.
 func (p *ParentHandle) Kill() error {
 	if p.c.Process == nil {
-		return errors.New("no such process")
+		return verror.New(errNoSuchProcess, nil)
 	}
 	return p.c.Process.Kill()
 }
@@ -358,7 +367,7 @@
 // Signal sends the given signal to the child process.
 func (p *ParentHandle) Signal(sig syscall.Signal) error {
 	if p.c.Process == nil {
-		return errors.New("no such process")
+		return verror.New(errNoSuchProcess, nil)
 	}
 	return syscall.Kill(p.c.Process.Pid, sig)
 }
@@ -380,7 +389,7 @@
 		if err != nil {
 			return err
 		} else {
-			return errors.New("partial write")
+			return verror.New(errPartialWrite, nil)
 		}
 	}
 	return nil
diff --git a/lib/exec/util.go b/lib/exec/util.go
index 813730c..323097b 100644
--- a/lib/exec/util.go
+++ b/lib/exec/util.go
@@ -5,8 +5,13 @@
 package exec
 
 import (
-	"errors"
 	"strings"
+
+	"v.io/v23/verror"
+)
+
+var (
+	errNotFound = verror.Register(pkgPath+".errNotFound", verror.NoRetry, "{1:}{2:} not found{:_}")
 )
 
 // Getenv retrieves the value of the given variable from the given
@@ -17,7 +22,7 @@
 			return strings.TrimPrefix(v, name+"="), nil
 		}
 	}
-	return "", errors.New("not found")
+	return "", verror.New(errNotFound, nil)
 }
 
 // Setenv updates / adds the value assignment for the given variable
diff --git a/profiles/internal/naming/endpoint.go b/profiles/internal/naming/endpoint.go
index 17f3e3e..88b333c 100644
--- a/profiles/internal/naming/endpoint.go
+++ b/profiles/internal/naming/endpoint.go
@@ -40,6 +40,7 @@
 	MaxRPCVersion version.RPCVersion
 	Blessings     []string
 	IsMountTable  bool
+	IsLeaf        bool
 }
 
 // NewEndpoint creates a new endpoint from a string as per naming.NewEndpoint
@@ -136,24 +137,23 @@
 	return strconv.FormatUint(uint64(v), 10)
 }
 
-func parseMountTableFlag(input string) (bool, error) {
+func parseMountTableFlag(input string) (bool, bool, error) {
 	switch len(input) {
 	case 0:
-		return true, nil
+		return true, false, nil
 	case 1:
 		switch f := input[0]; f {
+		case 'l':
+			return false, true, nil
 		case 'm':
-			return true, nil
-		case 's', 'l':
-			// TODO(rthellend): 'l' will be used to indicate leaf
-			// servers in a future version. For now, treat it the
-			// the same as 's'.
-			return false, nil
+			return true, false, nil
+		case 's':
+			return false, false, nil
 		default:
-			return false, fmt.Errorf("%c is not one of 'm' or 's'", f)
+			return false, false, fmt.Errorf("%c is not one of 'l', 'm', or 's'", f)
 		}
 	}
-	return false, fmt.Errorf("flag is either missing or too long")
+	return false, false, fmt.Errorf("flag is either missing or too long")
 }
 
 func (ep *Endpoint) parseV2(parts []string) error {
@@ -181,7 +181,7 @@
 	if err = ep.parseV2(parts[:6]); err != nil {
 		return err
 	}
-	if ep.IsMountTable, err = parseMountTableFlag(parts[6]); err != nil {
+	if ep.IsMountTable, ep.IsLeaf, err = parseMountTableFlag(parts[6]); err != nil {
 		return fmt.Errorf("invalid mount table flag: %v", err)
 	}
 	return nil
@@ -233,10 +233,13 @@
 			mt)
 	case 4:
 		mt := "s"
-		blessings := strings.Join(ep.Blessings, blessingsSeparator)
-		if ep.IsMountTable {
+		switch {
+		case ep.IsLeaf:
+			mt = "l"
+		case ep.IsMountTable:
 			mt = "m"
 		}
+		blessings := strings.Join(ep.Blessings, blessingsSeparator)
 		return fmt.Sprintf("@4@%s@%s@%s@%s@%s@%s@%s@@",
 			ep.Protocol, ep.Address, ep.RID,
 			printRPCVersion(ep.MinRPCVersion), printRPCVersion(ep.MaxRPCVersion),
@@ -269,6 +272,11 @@
 	return ep.IsMountTable
 }
 
+func (ep *Endpoint) ServesLeaf() bool {
+	//nologcall
+	return ep.IsLeaf
+}
+
 func (ep *Endpoint) BlessingNames() []string {
 	//nologcall
 	return ep.Blessings
diff --git a/profiles/internal/naming/namespace/all_test.go b/profiles/internal/naming/namespace/all_test.go
index 7ff78dc..c8981c9 100644
--- a/profiles/internal/naming/namespace/all_test.go
+++ b/profiles/internal/naming/namespace/all_test.go
@@ -574,8 +574,12 @@
 		boom(t, "Failed to detect cycle")
 	}
 
-	// Perform the glob with a response length limit.
-	doGlob(t, c, ns, "c1/...", 1000)
+	// Perform the glob with a response length limit and dup suppression.  The dup supression
+	// should win.
+	r := doGlob(t, c, ns, "c1/...", 1000)
+	if len(r) != 6 {
+		t.Fatal("expected 6 replies, got %v", r)
+	}
 }
 
 // TestGoroutineLeaks tests for leaking goroutines - we have many:-(
diff --git a/profiles/internal/naming/namespace/glob.go b/profiles/internal/naming/namespace/glob.go
index 0af01c3..16cdc11 100644
--- a/profiles/internal/naming/namespace/glob.go
+++ b/profiles/internal/naming/namespace/glob.go
@@ -7,6 +7,7 @@
 import (
 	"io"
 	"strings"
+	"sync"
 
 	"v.io/x/ref/lib/glob"
 
@@ -18,6 +19,25 @@
 	"v.io/x/lib/vlog"
 )
 
+type tracks struct {
+	m      sync.Mutex
+	places map[string]struct{}
+}
+
+func (tr *tracks) beenThereDoneThat(servers []string, pstr string) bool {
+	tr.m.Lock()
+	defer tr.m.Unlock()
+	found := false
+	for _, s := range servers {
+		x := s + "!" + pstr
+		if _, ok := tr.places[x]; ok {
+			found = true
+		}
+		tr.places[x] = struct{}{}
+	}
+	return found
+}
+
 // task is a sub-glob that has to be performed against a mount table.  Tasks are
 // done in parrallel to speed up the glob.
 type task struct {
@@ -30,7 +50,7 @@
 
 // globAtServer performs a Glob on the servers at a mount point.  It cycles through the set of
 // servers until it finds one that replies.
-func (ns *namespace) globAtServer(ctx *context.T, t *task, replies chan *task) {
+func (ns *namespace) globAtServer(ctx *context.T, t *task, replies chan *task, tr *tracks) {
 	defer func() {
 		if t.error == nil {
 			replies <- nil
@@ -53,6 +73,13 @@
 		t.error = nil
 		return
 	}
+
+	// If we've been there before with the same request, give up.
+	if tr.beenThereDoneThat(servers, pstr) {
+		t.error = nil
+		return
+	}
+
 	call, err := ns.parallelStartCall(ctx, client, servers, rpc.GlobMethod, []interface{}{pstr})
 	if err != nil {
 		t.error = err
@@ -119,7 +146,7 @@
 }
 
 // globLoop fires off a go routine for each server and read backs replies.
-func (ns *namespace) globLoop(ctx *context.T, e *naming.MountEntry, prefix string, pattern *glob.Glob, reply chan interface{}) {
+func (ns *namespace) globLoop(ctx *context.T, e *naming.MountEntry, prefix string, pattern *glob.Glob, reply chan interface{}, tr *tracks) {
 	defer close(reply)
 
 	// Provide enough buffers to avoid too much switching between the readers and the writers.
@@ -201,7 +228,7 @@
 			// Perform a glob at the next server.
 			inFlight++
 			t.pattern = suffix
-			go ns.globAtServer(ctx, t, replies)
+			go ns.globAtServer(ctx, t, replies, tr)
 		}
 	}
 }
@@ -221,6 +248,8 @@
 		return nil, err
 	}
 
+	tr := &tracks{places: make(map[string]struct{})}
+
 	// If pattern was already rooted, make sure we tack that root
 	// onto all returned names.  Otherwise, just return the relative
 	// name.
@@ -230,6 +259,6 @@
 	}
 	e.Name = ""
 	reply := make(chan interface{}, 100)
-	go ns.globLoop(ctx, e, prefix, g, reply)
+	go ns.globLoop(ctx, e, prefix, g, reply, tr)
 	return reply, nil
 }
diff --git a/profiles/internal/rpc/server.go b/profiles/internal/rpc/server.go
index a4b286d..8efaf7e 100644
--- a/profiles/internal/rpc/server.go
+++ b/profiles/internal/rpc/server.go
@@ -464,7 +464,6 @@
 		}
 
 		for _, iep := range ls.ieps {
-			s.publisher.AddServer(iep.String())
 			eps = append(eps, iep)
 		}
 	}
@@ -490,6 +489,7 @@
 	s.proxies[proxy] = proxyState{iep, nil}
 	s.Unlock()
 	iep.IsMountTable = s.servesMountTable
+	iep.IsLeaf = s.isLeaf
 	s.publisher.AddServer(iep.String())
 	return iep, ln, nil
 }
@@ -730,6 +730,8 @@
 			if ls != nil && ls.roaming {
 				niep := ls.protoIEP
 				niep.Address = net.JoinHostPort(host, ls.port)
+				niep.IsMountTable = s.servesMountTable
+				niep.IsLeaf = s.isLeaf
 				ls.ieps = append(ls.ieps, &niep)
 				vlog.VI(2).Infof("rpc: dhcp adding: %s", niep)
 				s.publisher.AddServer(niep.String())
@@ -761,10 +763,21 @@
 	if err != nil {
 		return verror.New(verror.ErrBadArg, s.ctx, fmt.Sprintf("bad object: %v", err))
 	}
-	s.isLeaf = true
+	s.setLeaf(true)
 	return s.ServeDispatcher(name, &leafDispatcher{invoker, authorizer})
 }
 
+func (s *server) setLeaf(value bool) {
+	s.Lock()
+	defer s.Unlock()
+	s.isLeaf = value
+	for ls, _ := range s.listenState {
+		for i := range ls.ieps {
+			ls.ieps[i].IsLeaf = s.isLeaf
+		}
+	}
+}
+
 func (s *server) ServeDispatcher(name string, disp rpc.Dispatcher) error {
 	defer vlog.LogCall()()
 	if disp == nil {
@@ -778,6 +791,11 @@
 	vtrace.GetSpan(s.ctx).Annotate("Serving under name: " + name)
 	s.disp = disp
 	if len(name) > 0 {
+		for ls, _ := range s.listenState {
+			for _, iep := range ls.ieps {
+				s.publisher.AddServer(iep.String())
+			}
+		}
 		s.publisher.AddName(name, s.servesMountTable, s.isLeaf)
 	}
 	return nil
diff --git a/profiles/internal/rpc/server_test.go b/profiles/internal/rpc/server_test.go
index ce935c9..3074ed5 100644
--- a/profiles/internal/rpc/server_test.go
+++ b/profiles/internal/rpc/server_test.go
@@ -323,6 +323,7 @@
 	if err = server.Serve("foo", &testServer{}, nil); err != nil {
 		t.Fatal(err)
 	}
+	setLeafEndpoints(eps)
 	status := server.Status()
 	if got, want := len(status.Mounts), 2; got != want {
 		t.Fatalf("got %d, want %d", got, want)
@@ -470,6 +471,7 @@
 	if err = server.Serve("foo", &testServer{}, nil); err != nil {
 		t.Fatal(err)
 	}
+	setLeafEndpoints(eps)
 	if err = server.AddName("bar"); err != nil {
 		t.Fatal(err)
 	}
@@ -560,7 +562,7 @@
 
 	status = server.Status()
 	if got, want := len(status.Mounts), 0; got != want {
-		t.Fatalf("got %d, want %d", got, want)
+		t.Fatalf("got %d, want %d: %v", got, want, status.Mounts)
 	}
 
 	roaming <- rpc.NewAddAddrsSetting([]rpc.Address{n1})
@@ -607,6 +609,7 @@
 	if err = server.Serve("foo", &testServer{}, nil); err != nil {
 		t.Fatal(err)
 	}
+	setLeafEndpoints(eps)
 
 	// Set a watcher that we never read from - the intent is to make sure
 	// that the listener still listens to changes even though there is no
@@ -642,3 +645,9 @@
 	}
 
 }
+
+func setLeafEndpoints(eps []naming.Endpoint) {
+	for i := range eps {
+		eps[i].(*inaming.Endpoint).IsLeaf = true
+	}
+}
diff --git a/profiles/internal/rpc/stream/vc/vc_test.go b/profiles/internal/rpc/stream/vc/vc_test.go
index 958b6ad..6bc422d 100644
--- a/profiles/internal/rpc/stream/vc/vc_test.go
+++ b/profiles/internal/rpc/stream/vc/vc_test.go
@@ -617,5 +617,6 @@
 func (e endpoint) RoutingID() naming.RoutingID              { return naming.RoutingID(e) }
 func (e endpoint) Addr() net.Addr                           { return nil }
 func (e endpoint) ServesMountTable() bool                   { return false }
+func (e endpoint) ServesLeaf() bool                         { return false }
 func (e endpoint) BlessingNames() []string                  { return nil }
 func (e endpoint) RPCVersionRange() version.RPCVersionRange { return version.RPCVersionRange{} }
diff --git a/profiles/internal/rt/mgmt.go b/profiles/internal/rt/mgmt.go
index d27d689..6033e93 100644
--- a/profiles/internal/rt/mgmt.go
+++ b/profiles/internal/rt/mgmt.go
@@ -26,12 +26,14 @@
 
 func (rt *Runtime) initMgmt(ctx *context.T) error {
 	handle, err := exec.GetChildHandle()
-	if err == exec.ErrNoVersion {
+	if err == nil {
+		// No error; fall through.
+	} else if verror.ErrorID(err) == exec.ErrNoVersion.ID {
 		// Do not initialize the mgmt runtime if the process has not
 		// been started through the vanadium exec library by a device
 		// manager.
 		return nil
-	} else if err != nil {
+	} else {
 		return err
 	}
 	parentName, err := handle.Config.Get(mgmt.ParentNameConfigKey)
diff --git a/profiles/internal/rt/security.go b/profiles/internal/rt/security.go
index ccdc157..9bdbb59 100644
--- a/profiles/internal/rt/security.go
+++ b/profiles/internal/rt/security.go
@@ -15,6 +15,7 @@
 	"v.io/v23/mgmt"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
+	"v.io/v23/verror"
 
 	"v.io/x/ref/lib/exec"
 	vsecurity "v.io/x/ref/security"
@@ -71,7 +72,7 @@
 // agent.
 func agentFD() (int, error) {
 	handle, err := exec.GetChildHandle()
-	if err != nil && err != exec.ErrNoVersion {
+	if err != nil && verror.ErrorID(err) != exec.ErrNoVersion.ID {
 		return -1, err
 	}
 	var fd string
diff --git a/profiles/internal/testing/mocks/mocknet/mocknet.go b/profiles/internal/testing/mocks/mocknet/mocknet.go
new file mode 100644
index 0000000..0252003
--- /dev/null
+++ b/profiles/internal/testing/mocks/mocknet/mocknet.go
@@ -0,0 +1,262 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package mocknet implements a mock net.Conn that can simulate a variety of
+// network errors and/or be used for tracing.
+package mocknet
+
+import (
+	"net"
+	"sync"
+	"time"
+)
+
+// TODO(cnicolaou): consider extending Dialer/Listener API to include a cipher
+// to allow access to encrypted data.
+
+type Mode int
+
+const (
+	Trace Mode = iota // Log the sizes of each read/write call
+	Close             // Close the connection after a specified #bytes are read/written
+	Drop              // Drop byes as per a policy specified in opts
+)
+
+type Opts struct {
+	// The underlying network protocol to use, e.g. "tcp", defaults to tcp.
+	UnderlyingProtocol string
+
+	// The mode to operate under.
+	Mode Mode
+
+	// Buffers to store the transmit and receive message sizes when
+	// in Trace mode.
+	Tx, Rx chan int
+
+	// The number of rx and tx bytes respectively to be seen before the
+	// connection is closed when in Close mode.
+	RxCloseAt, TxCloseAt int
+
+	// TXDropAfter is called to obtain the number of tx bytes to be sent
+	// before dropping the rest of the data passed to that write call. The
+	// number of bytes returned by TxDroptAfter will always be written,
+	// but the number of bytes dropped is unspecified since it depends
+	// on the size of the buffer passed to that write call. TxDropAfter
+	// will be called again after each drop and the current count of
+	// byte sent reset to zero.
+	TxDropAfter func() (pos int)
+}
+
+// DialerWithOpts is intended for use with rpc.RegisterProtocol via
+// a closure:
+//
+//  dialer := func(network, address string, timeout time.Duration) (net.Conn, error) {
+//	    return mocknet.DialerWithOpts(mocknet.Opts{UnderlyingProtocol:"tcp"}, network, address, timeout)
+//  }
+// rpc.RegisterProtocol("brkDial", dialer, net.Listen)
+//
+func DialerWithOpts(opts Opts, network, address string, timeout time.Duration) (net.Conn, error) {
+	protocol := opts.UnderlyingProtocol
+	if len(protocol) == 0 {
+		protocol = "tcp"
+	}
+	c, err := net.DialTimeout(protocol, address, timeout)
+	if err != nil {
+		return nil, err
+	}
+	return newMockConn(opts, c), nil
+}
+
+// ListenerWithOpts is intended for use with rpc.RegisterProtocol via
+// a closure as per DialerWithOpts.
+func ListenerWithOpts(opts Opts, network, laddr string) (net.Listener, error) {
+	protocol := opts.UnderlyingProtocol
+	if len(protocol) == 0 {
+		protocol = "tcp"
+	}
+	ln, err := net.Listen(protocol, laddr)
+	if err != nil {
+		return nil, err
+	}
+	return &listener{opts, ln}, nil
+}
+
+func newMockConn(opts Opts, c net.Conn) net.Conn {
+	switch opts.Mode {
+	case Trace:
+		return &traceConn{
+			conn: c,
+			rx:   opts.Rx,
+			tx:   opts.Tx}
+	case Close:
+		return &closeConn{
+			conn:      c,
+			rxCloseAt: opts.RxCloseAt,
+			txCloseAt: opts.TxCloseAt,
+		}
+	case Drop:
+		return &dropConn{
+			opts:        opts,
+			conn:        c,
+			txDropAfter: opts.TxDropAfter(),
+		}
+	}
+	return nil
+}
+
+type dropConn struct {
+	sync.Mutex
+	opts        Opts
+	conn        net.Conn
+	tx          int
+	txDropAfter int
+}
+
+func (c *dropConn) Read(b []byte) (n int, err error) {
+	return c.conn.Read(b)
+}
+
+func (c *dropConn) Write(b []byte) (n int, err error) {
+	c.Lock()
+	defer c.Unlock()
+	dropped := false
+	if c.tx+len(b) >= c.txDropAfter {
+		b = b[0 : c.txDropAfter-c.tx]
+		c.txDropAfter = c.opts.TxDropAfter()
+		dropped = true
+	}
+	n, err = c.conn.Write(b)
+	if dropped {
+		c.tx = 0
+	} else {
+		c.tx += n
+	}
+	return
+}
+
+func (c *dropConn) Close() error        { return c.conn.Close() }
+func (c *dropConn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
+func (c *dropConn) RemoteAddr() net.Addr {
+	return c.conn.RemoteAddr()
+}
+func (c *dropConn) SetDeadline(t time.Time) error {
+	return c.conn.SetDeadline(t)
+}
+func (c *dropConn) SetReadDeadline(t time.Time) error {
+	return c.conn.SetReadDeadline(t)
+}
+func (c *dropConn) SetWriteDeadline(t time.Time) error {
+	return c.conn.SetWriteDeadline(t)
+}
+
+type closeConn struct {
+	sync.Mutex
+	conn                 net.Conn
+	rx, tx               int
+	rxCloseAt, txCloseAt int
+	closed               bool
+}
+
+func (c *closeConn) Read(b []byte) (n int, err error) {
+	c.Lock()
+	defer c.Unlock()
+	n = len(b)
+	if c.rx+n >= c.rxCloseAt {
+		n = c.rxCloseAt - c.rx
+	}
+	b = b[:n]
+	n, err = c.conn.Read(b[:n])
+	c.rx += n
+	if c.rx == c.rxCloseAt {
+		c.conn.Close()
+	}
+	return
+}
+
+func (c *closeConn) Write(b []byte) (n int, err error) {
+	c.Lock()
+	defer c.Unlock()
+	n = len(b)
+	if c.tx+n >= c.txCloseAt {
+		n = c.txCloseAt - c.tx
+	}
+	n, err = c.conn.Write(b[:n])
+	c.tx += n
+	if c.tx == c.txCloseAt {
+		c.conn.Close()
+	}
+	return
+}
+
+func (c *closeConn) Close() error        { return c.conn.Close() }
+func (c *closeConn) LocalAddr() net.Addr { return c.conn.LocalAddr() }
+func (c *closeConn) RemoteAddr() net.Addr {
+	return c.conn.RemoteAddr()
+}
+func (c *closeConn) SetDeadline(t time.Time) error {
+	return c.conn.SetDeadline(t)
+}
+func (c *closeConn) SetReadDeadline(t time.Time) error {
+	return c.conn.SetReadDeadline(t)
+}
+func (c *closeConn) SetWriteDeadline(t time.Time) error {
+	return c.conn.SetWriteDeadline(t)
+}
+
+type traceConn struct {
+	conn   net.Conn
+	tx, rx chan int
+}
+
+func (c *traceConn) Read(b []byte) (n int, err error) {
+	n, err = c.conn.Read(b)
+	c.rx <- n
+	return n, err
+}
+
+func (c *traceConn) Write(b []byte) (n int, err error) {
+	n, err = c.conn.Write(b)
+	c.tx <- n
+	return
+}
+
+func (c *traceConn) Close() error {
+	c.rx <- -1
+	c.tx <- -1
+	return c.conn.Close()
+}
+
+func (c *traceConn) LocalAddr() net.Addr  { return c.conn.LocalAddr() }
+func (c *traceConn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() }
+func (c *traceConn) SetDeadline(t time.Time) error {
+	return c.conn.SetDeadline(t)
+}
+func (c *traceConn) SetReadDeadline(t time.Time) error {
+	return c.conn.SetReadDeadline(t)
+}
+func (c *traceConn) SetWriteDeadline(t time.Time) error {
+	return c.conn.SetWriteDeadline(t)
+}
+
+// listener is a wrapper around net.Listener.
+type listener struct {
+	opts  Opts
+	netLn net.Listener
+}
+
+func (ln *listener) Accept() (net.Conn, error) {
+	c, err := ln.netLn.Accept()
+	if err != nil {
+		return nil, err
+	}
+	return newMockConn(ln.opts, c), nil
+}
+
+func (ln *listener) Close() error {
+	return ln.netLn.Close()
+}
+
+func (ln *listener) Addr() net.Addr {
+	return ln.netLn.Addr()
+}
diff --git a/profiles/internal/testing/mocks/mocknet/mocknet_test.go b/profiles/internal/testing/mocks/mocknet/mocknet_test.go
new file mode 100644
index 0000000..1e62ac7
--- /dev/null
+++ b/profiles/internal/testing/mocks/mocknet/mocknet_test.go
@@ -0,0 +1,214 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package mocknet_test
+
+import (
+	"errors"
+	"io"
+	"net"
+	"reflect"
+	"sync"
+	"testing"
+	"time"
+
+	"v.io/x/ref/profiles/internal/testing/mocks/mocknet"
+)
+
+//go:generate v23 test generate
+
+func newListener(t *testing.T, opts mocknet.Opts) net.Listener {
+	ln, err := mocknet.ListenerWithOpts(opts, "test", "127.0.0.1:0")
+	if err != nil {
+		t.Fatal(err)
+	}
+	return ln
+}
+
+func TestTrace(t *testing.T) {
+	opts := mocknet.Opts{
+		Mode: mocknet.Trace,
+		Tx:   make(chan int, 100),
+		Rx:   make(chan int, 100),
+	}
+	ln := newListener(t, opts)
+	defer ln.Close()
+
+	var rxconn net.Conn
+	var rxerr error
+	var wg sync.WaitGroup
+	wg.Add(1)
+	go func() {
+		rxconn, rxerr = ln.Accept()
+		wg.Done()
+	}()
+
+	txconn, err := mocknet.DialerWithOpts(opts, "test", ln.Addr().String(), time.Minute)
+	if err != nil {
+		t.Fatal(err)
+	}
+	wg.Wait()
+
+	rw := func(s string) {
+		b := make([]byte, len(s))
+		txconn.Write([]byte(s))
+		rxconn.Read(b[:])
+		if got, want := string(b), s; got != want {
+			t.Fatalf("got %v, want %v", got, want)
+		}
+	}
+
+	sizes := []int{}
+	for _, s := range []string{"hello", " ", "world"} {
+		rw(s)
+		sizes = append(sizes, len(s))
+	}
+	rxconn.Close()
+	close(opts.Tx)
+	close(opts.Rx)
+	sizes = append(sizes, -1)
+
+	drain := func(ch chan int) []int {
+		r := []int{}
+		for v := range ch {
+			r = append(r, v)
+		}
+		return r
+	}
+
+	if got, want := drain(opts.Rx), sizes; !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+	if got, want := drain(opts.Tx), sizes; !reflect.DeepEqual(got, want) {
+		t.Fatalf("got %v, want %v", got, want)
+	}
+}
+
+func TestClose(t *testing.T) {
+	cases := []struct {
+		txClose, rxClose int
+		tx               []string
+		rx               []string
+		err              error
+	}{
+		{6, 10, []string{"hello", "world"}, []string{"hello", "w"}, io.EOF},
+		{5, 10, []string{"hello", "world"}, []string{"hello", ""}, io.EOF},
+		{8, 6, []string{"hello", "world"}, []string{"hello", "w"}, io.EOF},
+		{8, 5, []string{"hello", "world"}, []string{"hello", ""}, errors.New("use of closed network connection")},
+	}
+
+	for ci, c := range cases {
+		opts := mocknet.Opts{
+			Mode:      mocknet.Close,
+			TxCloseAt: c.txClose,
+			RxCloseAt: c.rxClose,
+		}
+
+		ln := newListener(t, opts)
+		defer ln.Close()
+
+		var rxconn net.Conn
+		var rxerr error
+		var wg sync.WaitGroup
+		wg.Add(1)
+		go func() {
+			rxconn, rxerr = ln.Accept()
+			wg.Done()
+		}()
+
+		txconn, err := mocknet.DialerWithOpts(opts, "test", ln.Addr().String(), time.Minute)
+		if err != nil {
+			t.Fatal(err)
+		}
+		wg.Wait()
+
+		rw := func(s string) (int, int, string, error) {
+			b := make([]byte, len(s))
+			tx, _ := txconn.Write([]byte(s))
+			rx, err := rxconn.Read(b[:])
+			return tx, rx, string(b[0:rx]), err
+		}
+
+		txBytes := 0
+		rxBytes := 0
+		for i, m := range c.tx {
+			tx, rx, rxed, err := rw(m)
+			if got, want := rxed, c.rx[i]; got != want {
+				t.Fatalf("%d: got %q, want %q", ci, got, want)
+			}
+			txBytes += tx
+			rxBytes += rx
+			if err != nil {
+				if got, want := err.Error(), c.err.Error(); got != want {
+					t.Fatalf("%d: got %v, want %v", ci, got, want)
+				}
+			}
+		}
+		if got, want := txBytes, c.txClose; got != want {
+			t.Fatalf("%d: got %v, want %v", ci, got, want)
+		}
+		rxWant := c.rxClose
+		if rxWant > c.txClose {
+			rxWant = c.txClose
+		}
+		if got, want := rxBytes, rxWant; got != want {
+			t.Fatalf("%d: got %v, want %v", ci, got, want)
+
+		}
+	}
+}
+
+func TestDrop(t *testing.T) {
+	cases := []struct {
+		txDropAfter int
+		tx          []string
+		rx          []string
+	}{
+		{6, []string{"hello", "world"}, []string{"hello", "w"}},
+		{2, []string{"hello", "world"}, []string{"he", "wo"}},
+		{0, []string{"hello", "world"}, []string{"", ""}},
+	}
+
+	for ci, c := range cases {
+		opts := mocknet.Opts{
+			Mode:        mocknet.Drop,
+			TxDropAfter: func() int { return c.txDropAfter },
+		}
+
+		ln := newListener(t, opts)
+		defer ln.Close()
+
+		var rxconn net.Conn
+		var rxerr error
+		var wg sync.WaitGroup
+		wg.Add(1)
+		go func() {
+			rxconn, rxerr = ln.Accept()
+			wg.Done()
+		}()
+
+		txconn, err := mocknet.DialerWithOpts(opts, "test", ln.Addr().String(), time.Minute)
+		if err != nil {
+			t.Fatal(err)
+		}
+		wg.Wait()
+
+		rw := func(s string, l int) (int, int, string, error) {
+			b := make([]byte, l)
+			tx, _ := txconn.Write([]byte(s))
+			rx, err := rxconn.Read(b[:])
+			return tx, rx, string(b[0:rx]), err
+		}
+
+		for i, m := range c.tx {
+			tx, rx, rxed, _ := rw(m, len(c.rx[i]))
+			if got, want := rxed, c.rx[i]; got != want {
+				t.Fatalf("%d: got %q, want %q", ci, got, want)
+			}
+			if tx != rx {
+				t.Fatalf("%d: tx %d, rx %d", ci, tx, rx)
+			}
+		}
+	}
+}
diff --git a/profiles/internal/util.go b/profiles/internal/util.go
index a6c45c7..600ff0a 100644
--- a/profiles/internal/util.go
+++ b/profiles/internal/util.go
@@ -10,6 +10,7 @@
 	"strings"
 
 	"v.io/v23/rpc"
+	"v.io/v23/verror"
 	"v.io/x/lib/vlog"
 
 	"v.io/x/lib/netstate"
@@ -23,14 +24,13 @@
 // profile can use or modify the flags as it pleases.
 func ParseFlags(f *flags.Flags) error {
 	handle, err := exec.GetChildHandle()
-	switch err {
-	case exec.ErrNoVersion:
-		// The process has not been started through the vanadium exec
-		// library. No further action is needed.
-	case nil:
+	if err == nil {
 		// The process has been started through the vanadium exec
 		// library.
-	default:
+	} else if verror.ErrorID(err) == exec.ErrNoVersion.ID {
+		// The process has not been started through the vanadium exec
+		// library. No further action is needed.
+	} else {
 		return err
 	}
 
diff --git a/services/mgmt/device/impl/callback.go b/services/mgmt/device/impl/callback.go
index 6ffef42..4241e60 100644
--- a/services/mgmt/device/impl/callback.go
+++ b/services/mgmt/device/impl/callback.go
@@ -7,6 +7,7 @@
 import (
 	"v.io/v23/context"
 	"v.io/v23/mgmt"
+	"v.io/v23/verror"
 	"v.io/x/lib/vlog"
 
 	"v.io/x/ref/lib/exec"
@@ -17,8 +18,7 @@
 // is expected to be this device manager's object name).
 func InvokeCallback(ctx *context.T, name string) {
 	handle, err := exec.GetChildHandle()
-	switch err {
-	case nil:
+	if err == nil {
 		// Device manager was started by self-update, notify the parent.
 		callbackName, err := handle.Config.Get(mgmt.ParentNameConfigKey)
 		if err != nil {
@@ -31,8 +31,7 @@
 		if err := client.Set(ctx, mgmt.ChildNameConfigKey, name); err != nil {
 			vlog.Fatalf("Set(%v, %v) failed: %v", mgmt.ChildNameConfigKey, name, err)
 		}
-	case exec.ErrNoVersion:
-	default:
+	} else if verror.ErrorID(err) != exec.ErrNoVersion.ID {
 		vlog.Fatalf("GetChildHandle() failed: %v", err)
 	}
 }
diff --git a/services/mgmt/profile/impl/v23_internal_test.go b/services/mgmt/profile/impl/v23_internal_test.go
deleted file mode 100644
index f3c1179..0000000
--- a/services/mgmt/profile/impl/v23_internal_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// This file was auto-generated via go generate.
-// DO NOT UPDATE MANUALLY
-package impl
-
-import "testing"
-import "os"
-
-import "v.io/x/ref/test"
-
-func TestMain(m *testing.M) {
-	test.Init()
-	os.Exit(m.Run())
-}
diff --git a/services/mgmt/profile/impl/dispatcher.go b/services/mgmt/profile/profiled/dispatcher.go
similarity index 98%
rename from services/mgmt/profile/impl/dispatcher.go
rename to services/mgmt/profile/profiled/dispatcher.go
index 64dfa3e..4b43fc7 100644
--- a/services/mgmt/profile/impl/dispatcher.go
+++ b/services/mgmt/profile/profiled/dispatcher.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package impl
+package main
 
 import (
 	"path/filepath"
diff --git a/services/mgmt/profile/impl/impl_test.go b/services/mgmt/profile/profiled/impl_test.go
similarity index 98%
rename from services/mgmt/profile/impl/impl_test.go
rename to services/mgmt/profile/profiled/impl_test.go
index 1f9583d..ba1c88b 100644
--- a/services/mgmt/profile/impl/impl_test.go
+++ b/services/mgmt/profile/profiled/impl_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package impl
+package main
 
 import (
 	"io/ioutil"
@@ -14,7 +14,6 @@
 	"v.io/v23/naming"
 	"v.io/v23/services/mgmt/build"
 
-	_ "v.io/x/ref/profiles"
 	"v.io/x/ref/services/mgmt/profile"
 	"v.io/x/ref/services/mgmt/repository"
 	"v.io/x/ref/test"
diff --git a/services/mgmt/profile/profiled/main.go b/services/mgmt/profile/profiled/main.go
index e8ef4d3..1c6b01f 100644
--- a/services/mgmt/profile/profiled/main.go
+++ b/services/mgmt/profile/profiled/main.go
@@ -13,7 +13,6 @@
 	"v.io/x/ref/lib/signals"
 	_ "v.io/x/ref/profiles/roaming"
 	vflag "v.io/x/ref/security/flag"
-	"v.io/x/ref/services/mgmt/profile/impl"
 )
 
 var (
@@ -34,7 +33,7 @@
 		vlog.Fatalf("NewServer() failed: %v", err)
 	}
 
-	dispatcher, err := impl.NewDispatcher(*store, vflag.NewAuthorizerOrDie())
+	dispatcher, err := NewDispatcher(*store, vflag.NewAuthorizerOrDie())
 	if err != nil {
 		vlog.Fatalf("NewDispatcher() failed: %v", err)
 	}
diff --git a/services/mgmt/profile/impl/service.go b/services/mgmt/profile/profiled/service.go
similarity index 99%
rename from services/mgmt/profile/impl/service.go
rename to services/mgmt/profile/profiled/service.go
index 2f33d08..49cef5f 100644
--- a/services/mgmt/profile/impl/service.go
+++ b/services/mgmt/profile/profiled/service.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package impl
+package main
 
 import (
 	"errors"
diff --git a/services/security/discharger.vdl b/services/security/discharger.vdl
index d913b8d..040fe21 100644
--- a/services/security/discharger.vdl
+++ b/services/security/discharger.vdl
@@ -13,3 +13,8 @@
   // this caveat.
   Discharge(Caveat security.Caveat, Impetus security.DischargeImpetus) (Discharge security.WireDischarge | error)
 }
+
+error (
+	// Indicates that the Caveat does not require a discharge
+	NotAThirdPartyCaveat(c security.Caveat) { "en": "discharges are not required for non-third-party caveats (id: {c.id})" }
+)
diff --git a/services/security/discharger.vdl.go b/services/security/discharger.vdl.go
index 825acae..5645357 100644
--- a/services/security/discharger.vdl.go
+++ b/services/security/discharger.vdl.go
@@ -11,12 +11,28 @@
 	// VDL system imports
 	"v.io/v23"
 	"v.io/v23/context"
+	"v.io/v23/i18n"
 	"v.io/v23/rpc"
+	"v.io/v23/verror"
 
 	// VDL user imports
 	"v.io/v23/security"
 )
 
+var (
+	// Indicates that the Caveat does not require a discharge
+	ErrNotAThirdPartyCaveat = verror.Register("v.io/x/ref/services/security.NotAThirdPartyCaveat", verror.NoRetry, "{1:}{2:} discharges are not required for non-third-party caveats (id: {c.id})")
+)
+
+func init() {
+	i18n.Cat().SetWithBase(i18n.LangID("en"), i18n.MsgID(ErrNotAThirdPartyCaveat.ID), "{1:}{2:} discharges are not required for non-third-party caveats (id: {c.id})")
+}
+
+// NewErrNotAThirdPartyCaveat returns an error with the ErrNotAThirdPartyCaveat ID.
+func NewErrNotAThirdPartyCaveat(ctx *context.T, c security.Caveat) error {
+	return verror.New(ErrNotAThirdPartyCaveat, ctx, c)
+}
+
 // DischargerClientMethods is the client interface
 // containing Discharger methods.
 //
diff --git a/services/security/discharger/discharger.go b/services/security/discharger/discharger.go
index e839380..30ad199 100644
--- a/services/security/discharger/discharger.go
+++ b/services/security/discharger/discharger.go
@@ -22,7 +22,7 @@
 	secCall := security.GetCall(ctx)
 	tp := caveat.ThirdPartyDetails()
 	if tp == nil {
-		return security.Discharge{}, fmt.Errorf("Caveat %v does not represent a third party caveat", caveat)
+		return security.Discharge{}, services.NewErrNotAThirdPartyCaveat(call.Context(), caveat)
 	}
 	if err := tp.Dischargeable(ctx); err != nil {
 		return security.Discharge{}, fmt.Errorf("third-party caveat %v cannot be discharged for this context: %v", tp, err)
diff --git a/services/security/role.vdl b/services/security/role.vdl
new file mode 100644
index 0000000..26e970a
--- /dev/null
+++ b/services/security/role.vdl
@@ -0,0 +1,24 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package security
+
+import "v.io/v23/security"
+
+// Role is an interface to request blessings from a role account server. The
+// returned blessings are bound to the client's public key thereby authorizing
+// the client to acquire the role. The server may tie the returned blessings
+// with the client's presented blessing name in order to maintain audit
+// information in the blessing.
+//
+// In order to avoid granting role blessings to all delegates of a principal,
+// the role server requires that each authorized blessing presented by the
+// client have the string "_role" as suffix.
+type Role interface {
+	SeekBlessings() (security.WireBlessings | error)
+}
+
+// Role.SeekBlessings will return an error if the requestor does not present
+// blessings that end in this suffix.
+const RoleSuffix = "_role"
diff --git a/services/security/role.vdl.go b/services/security/role.vdl.go
new file mode 100644
index 0000000..b90bb48
--- /dev/null
+++ b/services/security/role.vdl.go
@@ -0,0 +1,157 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated by the vanadium vdl tool.
+// Source: role.vdl
+
+package security
+
+import (
+	// VDL system imports
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+// Role.SeekBlessings will return an error if the requestor does not present
+// blessings that end in this suffix.
+const RoleSuffix = "_role"
+
+// RoleClientMethods is the client interface
+// containing Role methods.
+//
+// Role is an interface to request blessings from a role account server. The
+// returned blessings are bound to the client's public key thereby authorizing
+// the client to acquire the role. The server may tie the returned blessings
+// with the client's presented blessing name in order to maintain audit
+// information in the blessing.
+//
+// In order to avoid granting role blessings to all delegates of a principal,
+// the role server requires that each authorized blessing presented by the
+// client have the string "_role" as suffix.
+type RoleClientMethods interface {
+	SeekBlessings(*context.T, ...rpc.CallOpt) (security.Blessings, error)
+}
+
+// RoleClientStub adds universal methods to RoleClientMethods.
+type RoleClientStub interface {
+	RoleClientMethods
+	rpc.UniversalServiceMethods
+}
+
+// RoleClient returns a client stub for Role.
+func RoleClient(name string, opts ...rpc.BindOpt) RoleClientStub {
+	var client rpc.Client
+	for _, opt := range opts {
+		if clientOpt, ok := opt.(rpc.Client); ok {
+			client = clientOpt
+		}
+	}
+	return implRoleClientStub{name, client}
+}
+
+type implRoleClientStub struct {
+	name   string
+	client rpc.Client
+}
+
+func (c implRoleClientStub) c(ctx *context.T) rpc.Client {
+	if c.client != nil {
+		return c.client
+	}
+	return v23.GetClient(ctx)
+}
+
+func (c implRoleClientStub) SeekBlessings(ctx *context.T, opts ...rpc.CallOpt) (o0 security.Blessings, err error) {
+	var call rpc.ClientCall
+	if call, err = c.c(ctx).StartCall(ctx, c.name, "SeekBlessings", nil, opts...); err != nil {
+		return
+	}
+	err = call.Finish(&o0)
+	return
+}
+
+// RoleServerMethods is the interface a server writer
+// implements for Role.
+//
+// Role is an interface to request blessings from a role account server. The
+// returned blessings are bound to the client's public key thereby authorizing
+// the client to acquire the role. The server may tie the returned blessings
+// with the client's presented blessing name in order to maintain audit
+// information in the blessing.
+//
+// In order to avoid granting role blessings to all delegates of a principal,
+// the role server requires that each authorized blessing presented by the
+// client have the string "_role" as suffix.
+type RoleServerMethods interface {
+	SeekBlessings(rpc.ServerCall) (security.Blessings, error)
+}
+
+// RoleServerStubMethods is the server interface containing
+// Role methods, as expected by rpc.Server.
+// There is no difference between this interface and RoleServerMethods
+// since there are no streaming methods.
+type RoleServerStubMethods RoleServerMethods
+
+// RoleServerStub adds universal methods to RoleServerStubMethods.
+type RoleServerStub interface {
+	RoleServerStubMethods
+	// Describe the Role interfaces.
+	Describe__() []rpc.InterfaceDesc
+}
+
+// RoleServer returns a server stub for Role.
+// It converts an implementation of RoleServerMethods into
+// an object that may be used by rpc.Server.
+func RoleServer(impl RoleServerMethods) RoleServerStub {
+	stub := implRoleServerStub{
+		impl: impl,
+	}
+	// Initialize GlobState; always check the stub itself first, to handle the
+	// case where the user has the Glob method defined in their VDL source.
+	if gs := rpc.NewGlobState(stub); gs != nil {
+		stub.gs = gs
+	} else if gs := rpc.NewGlobState(impl); gs != nil {
+		stub.gs = gs
+	}
+	return stub
+}
+
+type implRoleServerStub struct {
+	impl RoleServerMethods
+	gs   *rpc.GlobState
+}
+
+func (s implRoleServerStub) SeekBlessings(call rpc.ServerCall) (security.Blessings, error) {
+	return s.impl.SeekBlessings(call)
+}
+
+func (s implRoleServerStub) Globber() *rpc.GlobState {
+	return s.gs
+}
+
+func (s implRoleServerStub) Describe__() []rpc.InterfaceDesc {
+	return []rpc.InterfaceDesc{RoleDesc}
+}
+
+// RoleDesc describes the Role interface.
+var RoleDesc rpc.InterfaceDesc = descRole
+
+// descRole hides the desc to keep godoc clean.
+var descRole = rpc.InterfaceDesc{
+	Name:    "Role",
+	PkgPath: "v.io/x/ref/services/security",
+	Doc:     "// Role is an interface to request blessings from a role account server. The\n// returned blessings are bound to the client's public key thereby authorizing\n// the client to acquire the role. The server may tie the returned blessings\n// with the client's presented blessing name in order to maintain audit\n// information in the blessing.\n//\n// In order to avoid granting role blessings to all delegates of a principal,\n// the role server requires that each authorized blessing presented by the\n// client have the string \"_role\" as suffix.",
+	Methods: []rpc.MethodDesc{
+		{
+			Name: "SeekBlessings",
+			OutArgs: []rpc.ArgDesc{
+				{"", ``}, // security.Blessings
+			},
+		},
+	},
+}
diff --git a/services/security/roled/internal/caveats.vdl b/services/security/roled/internal/caveats.vdl
new file mode 100644
index 0000000..0ad318c
--- /dev/null
+++ b/services/security/roled/internal/caveats.vdl
@@ -0,0 +1,18 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+import (
+	"v.io/v23/security"
+	"v.io/v23/uniqueid"
+)
+
+const (
+	// LoggingCaveat is a caveat that will always validate but it logs the parameter on every attempt to validate it.
+	LoggingCaveat = security.CaveatDescriptor{
+		Id:        uniqueid.Id{0xb0, 0x34, 0x1c, 0xed, 0xe2, 0xdf, 0x81, 0xbd, 0xed, 0x70, 0x97, 0xbb, 0x55, 0xad, 0x80, 0x0},
+		ParamType: typeobject([]string),
+	}
+)
diff --git a/services/security/roled/internal/caveats.vdl.go b/services/security/roled/internal/caveats.vdl.go
new file mode 100644
index 0000000..9a22ce7
--- /dev/null
+++ b/services/security/roled/internal/caveats.vdl.go
@@ -0,0 +1,40 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated by the vanadium vdl tool.
+// Source: caveats.vdl
+
+package internal
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+	"v.io/v23/uniqueid"
+)
+
+// LoggingCaveat is a caveat that will always validate but it logs the parameter on every attempt to validate it.
+var LoggingCaveat = security.CaveatDescriptor{
+	Id: uniqueid.Id{
+		176,
+		52,
+		28,
+		237,
+		226,
+		223,
+		129,
+		189,
+		237,
+		112,
+		151,
+		187,
+		85,
+		173,
+		128,
+		0,
+	},
+	ParamType: vdl.TypeOf([]string(nil)),
+}
diff --git a/services/security/roled/internal/config.vdl b/services/security/roled/internal/config.vdl
new file mode 100644
index 0000000..cffe6b4
--- /dev/null
+++ b/services/security/roled/internal/config.vdl
@@ -0,0 +1,26 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+import "v.io/v23/security"
+
+// Config contains the attributes of the role, and the list of members who have
+// access to it.
+type Config struct {
+	// Blessings that match at least one of the patterns in this set are
+	// allowed to act on behalf of the role.
+	Members []security.BlessingPattern
+	// Indicates that the blessing name of the caller should be appended to
+	// the role blessing name.
+	Extend bool
+	// If Audit is true, each use of the role blessing will be reported to
+	// an auditing service and will be usable only if the report was
+	// successful.
+	Audit bool
+	// The amount of time for which the role blessing will be valid. It is a
+	// string representation of a time.Duration, e.g. "24h". An empty string
+	// indicates that the role blessing will not expire.
+	Expiry string
+}
diff --git a/services/security/roled/internal/config.vdl.go b/services/security/roled/internal/config.vdl.go
new file mode 100644
index 0000000..258fed3
--- /dev/null
+++ b/services/security/roled/internal/config.vdl.go
@@ -0,0 +1,44 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file was auto-generated by the vanadium vdl tool.
+// Source: config.vdl
+
+package internal
+
+import (
+	// VDL system imports
+	"v.io/v23/vdl"
+
+	// VDL user imports
+	"v.io/v23/security"
+)
+
+// Config contains the attributes of the role, and the list of members who have
+// access to it.
+type Config struct {
+	// Blessings that match at least one of the patterns in this set are
+	// allowed to act on behalf of the role.
+	Members []security.BlessingPattern
+	// Indicates that the blessing name of the caller should be appended to
+	// the role blessing name.
+	Extend bool
+	// If Audit is true, each use of the role blessing will be reported to
+	// an auditing service and will be usable only if the report was
+	// successful.
+	Audit bool
+	// The amount of time for which the role blessing will be valid. It is a
+	// string representation of a time.Duration, e.g. "24h". An empty string
+	// indicates that the role blessing will not expire.
+	Expiry string
+}
+
+func (Config) __VDLReflect(struct {
+	Name string "v.io/x/ref/services/security/roled/internal.Config"
+}) {
+}
+
+func init() {
+	vdl.Register((*Config)(nil))
+}
diff --git a/services/security/roled/internal/discharger.go b/services/security/roled/internal/discharger.go
new file mode 100644
index 0000000..8bcab33
--- /dev/null
+++ b/services/security/roled/internal/discharger.go
@@ -0,0 +1,60 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+import (
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	isecurity "v.io/x/ref/services/security"
+
+	"v.io/x/lib/vlog"
+)
+
+func init() {
+	security.RegisterCaveatValidator(LoggingCaveat, func(ctx *context.T, params []string) error {
+		vlog.Infof("Params: %#v", params)
+		return nil
+	})
+
+}
+
+type discharger struct{}
+
+func (discharger) Discharge(call rpc.ServerCall, caveat security.Caveat, impetus security.DischargeImpetus) (security.Discharge, error) {
+	details := caveat.ThirdPartyDetails()
+	if details == nil {
+		return security.Discharge{}, isecurity.NewErrNotAThirdPartyCaveat(call.Context(), caveat)
+	}
+	if err := details.Dischargeable(call.Context()); err != nil {
+		return security.Discharge{}, err
+	}
+	// TODO(rthellend,ashankar): Do proper logging when the API allows it.
+	vlog.Infof("Discharge() impetus: %#v", impetus)
+
+	expiry, err := security.ExpiryCaveat(time.Now().Add(5 * time.Minute))
+	if err != nil {
+		return security.Discharge{}, verror.Convert(verror.ErrInternal, call.Context(), err)
+	}
+	// Bind the discharge to precisely the purpose the requestor claims it will be used.
+	method, err := security.MethodCaveat(impetus.Method)
+	if err != nil {
+		return security.Discharge{}, verror.Convert(verror.ErrInternal, call.Context(), err)
+	}
+	peer, err := security.NewCaveat(security.PeerBlessingsCaveat, impetus.Server)
+	if err != nil {
+		return security.Discharge{}, verror.Convert(verror.ErrInternal, call.Context(), err)
+	}
+	discharge, err := v23.GetPrincipal(call.Context()).MintDischarge(caveat, expiry, method, peer)
+	if err != nil {
+		return security.Discharge{}, verror.Convert(verror.ErrInternal, call.Context(), err)
+	}
+	return discharge, nil
+}
diff --git a/services/security/roled/internal/dispatcher.go b/services/security/roled/internal/dispatcher.go
new file mode 100644
index 0000000..d01e8a2
--- /dev/null
+++ b/services/security/roled/internal/dispatcher.go
@@ -0,0 +1,102 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	isecurity "v.io/x/ref/services/security"
+
+	"v.io/x/lib/vlog"
+)
+
+const requiredSuffix = security.ChainSeparator + isecurity.RoleSuffix
+
+// NewDispatcher returns a dispatcher object for a role service and its
+// associated discharger service.
+// The configRoot is the top level directory where the role configuration files
+// are stored.
+// The dischargerLocation is the object name or address of the discharger
+// service for the third-party caveats attached to the role blessings returned
+// by the role service.
+func NewDispatcher(configRoot, dischargerLocation string) rpc.Dispatcher {
+	return &dispatcher{configRoot, dischargerLocation}
+}
+
+type dispatcher struct {
+	configRoot         string
+	dischargerLocation string
+}
+
+func (d *dispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
+	if len(suffix) == 0 {
+		return isecurity.DischargerServer(&discharger{}), &openAuthorizer{}, nil
+	}
+	fileName := filepath.Join(d.configRoot, filepath.FromSlash(suffix+".conf"))
+	if !strings.HasPrefix(fileName, d.configRoot) {
+		// Guard against ".." in the suffix that could be used to read
+		// files outside of the config root.
+		return nil, nil, verror.New(verror.ErrNoExistOrNoAccess, nil)
+	}
+	config, err := loadConfig(fileName)
+	if err != nil && !os.IsNotExist(err) {
+		// The config file exists, but we failed to read it for some
+		// reason. This is likely a server configuration error.
+		vlog.Errorf("loadConfig(%q): %v", fileName, err)
+		return nil, nil, verror.Convert(verror.ErrInternal, nil, err)
+	}
+	obj := &roleService{role: suffix, config: config, dischargerLocation: d.dischargerLocation}
+	return isecurity.RoleServer(obj), &authorizer{config}, nil
+}
+
+type openAuthorizer struct{}
+
+func (openAuthorizer) Authorize(*context.T) error {
+	return nil
+}
+
+type authorizer struct {
+	config *Config
+}
+
+func (a *authorizer) Authorize(ctx *context.T) error {
+	if a.config == nil {
+		return verror.New(verror.ErrNoExistOrNoAccess, ctx)
+	}
+	remoteBlessingNames, _ := security.RemoteBlessingNames(ctx)
+
+	for _, pattern := range a.config.Members {
+		if pattern.MatchedBy(remoteBlessingNames...) {
+			return nil
+		}
+	}
+	return verror.New(verror.ErrNoExistOrNoAccess, ctx)
+}
+
+func loadConfig(fileName string) (*Config, error) {
+	contents, err := ioutil.ReadFile(fileName)
+	if err != nil {
+		return nil, err
+	}
+	var c Config
+	if err := json.Unmarshal(contents, &c); err != nil {
+		return nil, err
+	}
+	for i, pattern := range c.Members {
+		if p := string(pattern); !strings.HasSuffix(p, requiredSuffix) {
+			c.Members[i] = security.BlessingPattern(p + requiredSuffix)
+		}
+	}
+	return &c, nil
+}
diff --git a/services/security/roled/internal/doc.go b/services/security/roled/internal/doc.go
new file mode 100644
index 0000000..118ee40
--- /dev/null
+++ b/services/security/roled/internal/doc.go
@@ -0,0 +1,6 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package internal implements the role service defined in v.io/x/ref/services/security
+package internal
diff --git a/services/security/roled/internal/role.go b/services/security/roled/internal/role.go
new file mode 100644
index 0000000..3941329
--- /dev/null
+++ b/services/security/roled/internal/role.go
@@ -0,0 +1,145 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal
+
+import (
+	"time"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	"v.io/x/lib/vlog"
+)
+
+var (
+	errNoLocalBlessings = verror.Register("v.io/x/ref/services/security/roled/internal/noLocalBlessings", verror.NoRetry, "{1:}{2:} no local blessings")
+)
+
+type roleService struct {
+	role               string
+	config             *Config
+	dischargerLocation string
+}
+
+func (i *roleService) SeekBlessings(call rpc.ServerCall) (security.Blessings, error) {
+	ctx := call.Context()
+	remoteBlessingNames, _ := security.RemoteBlessingNames(ctx)
+	vlog.Infof("%q.SeekBlessings() called by %q", i.role, remoteBlessingNames)
+
+	members := i.filterNonMembers(remoteBlessingNames)
+	if len(members) == 0 {
+		// The Authorizer should already have caught that.
+		return security.Blessings{}, verror.New(verror.ErrNoAccess, ctx)
+	}
+
+	extensions := extensions(i.config, i.role, members)
+	caveats, err := caveats(ctx, i.config)
+	if err != nil {
+		return security.Blessings{}, err
+	}
+
+	return createBlessings(ctx, i.config, v23.GetPrincipal(ctx), extensions, caveats, i.dischargerLocation)
+}
+
+// filterNonMembers returns only the blessing names that are authorized members
+// for the role.
+func (i *roleService) filterNonMembers(blessingNames []string) []string {
+	var results []string
+	for _, name := range blessingNames {
+		// It is not enough to know if the pattern is matched by the
+		// blessings. We need to know exactly which names matched.
+		// These names will be used later to construct the role
+		// blessings.
+		for _, pattern := range i.config.Members {
+			if pattern.MatchedBy(name) {
+				results = append(results, name)
+				break
+			}
+		}
+	}
+	return results
+}
+
+func extensions(config *Config, role string, blessingNames []string) []string {
+	if !config.Extend {
+		return []string{role}
+	}
+	var extensions []string
+	for _, b := range blessingNames {
+		extensions = append(extensions, role+security.ChainSeparator+b)
+	}
+	return extensions
+}
+
+func caveats(ctx *context.T, config *Config) ([]security.Caveat, error) {
+	if config.Expiry == "" {
+		return nil, nil
+	}
+	d, err := time.ParseDuration(config.Expiry)
+	if err != nil {
+		return nil, verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	expiry, err := security.ExpiryCaveat(time.Now().Add(d))
+	if err != nil {
+		return nil, verror.Convert(verror.ErrInternal, ctx, err)
+	}
+	return []security.Caveat{expiry}, nil
+}
+
+func createBlessings(ctx *context.T, config *Config, principal security.Principal, extensions []string, caveats []security.Caveat, dischargerLocation string) (security.Blessings, error) {
+	blessWith := security.GetCall(ctx).LocalBlessings()
+	blessWithNames := security.LocalBlessingNames(ctx)
+	publicKey := security.GetCall(ctx).RemoteBlessings().PublicKey()
+	if len(blessWithNames) == 0 {
+		return security.Blessings{}, verror.New(errNoLocalBlessings, ctx)
+	}
+
+	var ret security.Blessings
+	for _, ext := range extensions {
+		cav := caveats
+		if config.Audit {
+			// TODO(rthellend): This third-party caveat will only work with a single
+			// discharger service. We need a way to allow multiple instances of this
+			// service to be interchangeable.
+
+			fullNames := make([]string, len(blessWithNames))
+			for i, n := range blessWithNames {
+				fullNames[i] = n + security.ChainSeparator + ext
+			}
+			loggingCaveat, err := security.NewCaveat(LoggingCaveat, fullNames)
+			if err != nil {
+				return security.Blessings{}, verror.Convert(verror.ErrInternal, ctx, err)
+			}
+			thirdParty, err := security.NewPublicKeyCaveat(principal.PublicKey(), dischargerLocation, security.ThirdPartyRequirements{true, true, true}, loggingCaveat)
+			if err != nil {
+				return security.Blessings{}, verror.Convert(verror.ErrInternal, ctx, err)
+			}
+			cav = append(cav, thirdParty)
+		}
+		if len(cav) == 0 {
+			// TODO(rthellend,ashankar): the use of unconstrained
+			// use is concerning. We should figure out how to get
+			// rid of it.
+			// Some options:
+			//  - have the seeker specify a set of caveats in the
+			//    request (and forcefully insert a restrictive one
+			//    or fail if the role server thinks that they are
+			//    too loose or something).
+			//  - have a set of caveats in the config of the role.
+			cav = []security.Caveat{security.UnconstrainedUse()}
+		}
+		b, err := principal.Bless(publicKey, blessWith, ext, cav[0], cav[1:]...)
+		if err != nil {
+			return security.Blessings{}, verror.Convert(verror.ErrInternal, ctx, err)
+		}
+		if ret, err = security.UnionOfBlessings(ret, b); err != nil {
+			verror.Convert(verror.ErrInternal, ctx, err)
+		}
+	}
+	return ret, nil
+}
diff --git a/services/security/roled/internal/role_test.go b/services/security/roled/internal/role_test.go
new file mode 100644
index 0000000..3ced6b1
--- /dev/null
+++ b/services/security/roled/internal/role_test.go
@@ -0,0 +1,205 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package internal_test
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/v23/naming"
+	"v.io/v23/rpc"
+	"v.io/v23/security"
+	"v.io/v23/verror"
+
+	_ "v.io/x/ref/profiles"
+	vsecurity "v.io/x/ref/security"
+	isecurity "v.io/x/ref/services/security"
+	irole "v.io/x/ref/services/security/roled/internal"
+	"v.io/x/ref/test/testutil"
+)
+
+func TestSeekBlessings(t *testing.T) {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	workdir, err := ioutil.TempDir("", "test-role-server-")
+	if err != nil {
+		t.Fatal("ioutil.TempDir failed: %v", err)
+	}
+	defer os.RemoveAll(workdir)
+
+	// Role A is a restricted role, i.e. it can be used in sensitive ACLs.
+	roleAConf := irole.Config{
+		Members: []security.BlessingPattern{
+			"root/users/user1/_role",
+			"root/users/user2/_role",
+			"root/users/user3", // _role/A implied
+		},
+		Extend: true,
+	}
+	writeConfig(t, roleAConf, filepath.Join(workdir, "A.conf"))
+
+	// Role B is an unrestricted role.
+	roleBConf := irole.Config{
+		Members: []security.BlessingPattern{
+			"root/users/user1/_role",
+			"root/users/user3/_role",
+		},
+		Audit:  true,
+		Extend: false,
+	}
+	writeConfig(t, roleBConf, filepath.Join(workdir, "B.conf"))
+
+	root := testutil.NewIDProvider("root")
+
+	var (
+		user1  = newPrincipalContext(t, ctx, root, "users/user1")
+		user1R = newPrincipalContext(t, ctx, root, "users/user1/_role")
+		user2  = newPrincipalContext(t, ctx, root, "users/user2")
+		user2R = newPrincipalContext(t, ctx, root, "users/user2/_role")
+		user3  = newPrincipalContext(t, ctx, root, "users/user3")
+		user3R = newPrincipalContext(t, ctx, root, "users/user3", "users/user3/_role/foo", "users/user3/_role/bar")
+	)
+
+	testServerCtx := newPrincipalContext(t, ctx, root, "testserver")
+	server, testAddr := newServer(t, testServerCtx)
+	tDisp := &testDispatcher{}
+	if err := server.ServeDispatcher("", tDisp); err != nil {
+		t.Fatalf("server.ServeDispatcher failed: %v", err)
+	}
+
+	const noErr = ""
+	testcases := []struct {
+		ctx       *context.T
+		role      string
+		errID     verror.ID
+		blessings []string
+	}{
+		{user1, "", verror.ErrNoExist.ID, nil},
+		{user1, "unknown", verror.ErrNoAccess.ID, nil},
+		{user2, "unknown", verror.ErrNoAccess.ID, nil},
+		{user3, "unknown", verror.ErrNoAccess.ID, nil},
+
+		{user1, "A", verror.ErrNoAccess.ID, nil},
+		{user1R, "A", noErr, []string{"root/roles/A/root/users/user1/_role"}},
+		{user2, "A", verror.ErrNoAccess.ID, nil},
+		{user2R, "A", noErr, []string{"root/roles/A/root/users/user2/_role"}},
+		{user3, "A", verror.ErrNoAccess.ID, nil},
+		{user3R, "A", noErr, []string{"root/roles/A/root/users/user3/_role/bar", "root/roles/A/root/users/user3/_role/foo"}},
+
+		{user1, "B", verror.ErrNoAccess.ID, nil},
+		{user1R, "B", noErr, []string{"root/roles/B"}},
+		{user2, "B", verror.ErrNoAccess.ID, nil},
+		{user2R, "B", verror.ErrNoAccess.ID, nil},
+		{user3, "B", verror.ErrNoAccess.ID, nil},
+		{user3R, "B", noErr, []string{"root/roles/B"}},
+	}
+	addr := newRoleServer(t, newPrincipalContext(t, ctx, root, "roles"), workdir)
+	for _, tc := range testcases {
+		user := v23.GetPrincipal(tc.ctx).BlessingStore().Default()
+		c := isecurity.RoleClient(naming.Join(addr, tc.role))
+		blessings, err := c.SeekBlessings(tc.ctx)
+		if verror.ErrorID(err) != tc.errID {
+			t.Errorf("unexpected error ID for (%q, %q). Got %#v, expected %#v", user, tc.role, verror.ErrorID(err), tc.errID)
+		}
+		if err == nil {
+			previousBlessings, _ := v23.GetPrincipal(tc.ctx).BlessingStore().Set(blessings, security.AllPrincipals)
+			blessingNames, rejected := callTest(t, tc.ctx, testAddr)
+			if !reflect.DeepEqual(blessingNames, tc.blessings) {
+				t.Errorf("unexpected blessings for (%q, %q). Got %q, expected %q", user, tc.role, blessingNames, tc.blessings)
+			}
+			if len(rejected) != 0 {
+				t.Errorf("unexpected rejected blessings for (%q, %q): %q", user, tc.role, rejected)
+			}
+			v23.GetPrincipal(tc.ctx).BlessingStore().Set(previousBlessings, security.AllPrincipals)
+		}
+	}
+}
+
+func newPrincipalContext(t *testing.T, ctx *context.T, root *testutil.IDProvider, names ...string) *context.T {
+	principal := testutil.NewPrincipal()
+	var blessings []security.Blessings
+	for _, n := range names {
+		blessing, err := root.NewBlessings(principal, n)
+		if err != nil {
+			t.Fatal("root.Bless failed for %q: %v", n, err)
+		}
+		blessings = append(blessings, blessing)
+	}
+	bUnion, err := security.UnionOfBlessings(blessings...)
+	if err != nil {
+		t.Fatal("security.UnionOfBlessings failed: %v", err)
+	}
+	vsecurity.SetDefaultBlessings(principal, bUnion)
+	ctx, err = v23.SetPrincipal(ctx, principal)
+	if err != nil {
+		t.Fatal("v23.SetPrincipal failed: %v", err)
+	}
+	return ctx
+}
+
+func newRoleServer(t *testing.T, ctx *context.T, dir string) string {
+	server, addr := newServer(t, ctx)
+	if err := server.ServeDispatcher("", irole.NewDispatcher(dir, addr)); err != nil {
+		t.Fatalf("ServeDispatcher failed: %v", err)
+	}
+	return addr
+}
+
+func newServer(t *testing.T, ctx *context.T) (rpc.Server, string) {
+	server, err := v23.NewServer(ctx)
+	if err != nil {
+		t.Fatalf("NewServer() failed: %v", err)
+	}
+	spec := rpc.ListenSpec{Addrs: rpc.ListenAddrs{{"tcp", "127.0.0.1:0"}}}
+	endpoints, err := server.Listen(spec)
+	if err != nil {
+		t.Fatalf("Listen(%v) failed: %v", spec, err)
+	}
+	return server, endpoints[0].Name()
+}
+
+func writeConfig(t *testing.T, config irole.Config, fileName string) {
+	mConf, err := json.Marshal(config)
+	if err != nil {
+		t.Fatal("json.MarshalIndent failed: %v", err)
+	}
+	if err := ioutil.WriteFile(fileName, mConf, 0644); err != nil {
+		t.Fatal("ioutil.WriteFile(%q, %q) failed: %v", fileName, string(mConf), err)
+	}
+}
+
+func callTest(t *testing.T, ctx *context.T, addr string) (blessingNames []string, rejected []security.RejectedBlessing) {
+	call, err := v23.GetClient(ctx).StartCall(ctx, addr, "Test", nil)
+	if err != nil {
+		t.Fatalf("StartCall failed: %v", err)
+	}
+	if err := call.Finish(&blessingNames, &rejected); err != nil {
+		t.Fatalf("Finish failed: %v", err)
+	}
+	return
+}
+
+type testDispatcher struct {
+}
+
+func (d *testDispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) {
+	return d, d, nil
+}
+
+func (d *testDispatcher) Authorize(*context.T) error {
+	return nil
+}
+
+func (d *testDispatcher) Test(call rpc.ServerCall) ([]string, []security.RejectedBlessing, error) {
+	blessings, rejected := security.RemoteBlessingNames(call.Context())
+	return blessings, rejected, nil
+}
diff --git a/services/security/roled/main.go b/services/security/roled/main.go
new file mode 100644
index 0000000..997358b
--- /dev/null
+++ b/services/security/roled/main.go
@@ -0,0 +1,57 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+
+	"v.io/v23"
+
+	"v.io/x/lib/vlog"
+	"v.io/x/ref/lib/signals"
+	_ "v.io/x/ref/profiles/static"
+	irole "v.io/x/ref/services/security/roled/internal"
+)
+
+var (
+	configDir = flag.String("config_dir", "", "The directory where the role configuration files are stored.")
+	name      = flag.String("name", "", "The name to publish for this service.")
+)
+
+func main() {
+	ctx, shutdown := v23.Init()
+	defer shutdown()
+
+	if len(*configDir) == 0 {
+		fmt.Fprintf(os.Stderr, "--config_dir must be specified\n")
+		os.Exit(1)
+	}
+	if len(*name) == 0 {
+		fmt.Fprintf(os.Stderr, "--name must be specified\n")
+		os.Exit(1)
+	}
+	server, err := v23.NewServer(ctx)
+	if err != nil {
+		vlog.Fatalf("NewServer failed: %v", err)
+	}
+
+	listenSpec := v23.GetListenSpec(ctx)
+	eps, err := server.Listen(listenSpec)
+	if err != nil {
+		vlog.Fatalf("Listen(%v) failed: %v", listenSpec, err)
+	}
+	vlog.Infof("Listening on: %q", eps)
+	if err := server.ServeDispatcher(*name, irole.NewDispatcher(*configDir, *name)); err != nil {
+		vlog.Fatalf("ServeDispatcher(%q) failed: %v", *name, err)
+	}
+	if len(*name) > 0 {
+		fmt.Printf("NAME=%s\n", *name)
+	} else if len(eps) > 0 {
+		fmt.Printf("NAME=%s\n", eps[0].Name())
+	}
+	<-signals.ShutdownOnSignals(ctx)
+}
diff --git a/test/modules/exec.go b/test/modules/exec.go
index d9837c7..35c34e6 100644
--- a/test/modules/exec.go
+++ b/test/modules/exec.go
@@ -15,6 +15,7 @@
 	"time"
 
 	"v.io/v23/mgmt"
+	"v.io/v23/verror"
 	"v.io/x/lib/vlog"
 	vexec "v.io/x/ref/lib/exec"
 	"v.io/x/ref/test/expect"
@@ -248,7 +249,7 @@
 		// The child has exited already.
 	case <-time.After(eh.opts.ShutdownTimeout):
 		// Time out waiting for child to exit.
-		procErr = vexec.ErrTimeout
+		procErr = verror.New(vexec.ErrTimeout, nil)
 		// Force close stdout to unblock any readers of stdout
 		// (including the drain loop started above).
 		eh.stdout.Close()
diff --git a/test/modules/modules_test.go b/test/modules/modules_test.go
index ced8fc8..b0745f0 100644
--- a/test/modules/modules_test.go
+++ b/test/modules/modules_test.go
@@ -21,6 +21,7 @@
 	"time"
 
 	"v.io/v23"
+	"v.io/v23/verror"
 
 	"v.io/x/ref/lib/exec"
 	execconsts "v.io/x/ref/lib/exec/consts"
@@ -469,8 +470,8 @@
 		t.Fatalf("unexpected error: %s", err)
 	}
 	var stdoutBuf, stderrBuf bytes.Buffer
-	if err := sh.Cleanup(&stdoutBuf, &stderrBuf); err == nil || err.Error() != exec.ErrTimeout.Error() {
-		t.Errorf("unexpected error in Cleanup: got %v, want %v", err, exec.ErrTimeout)
+	if err := sh.Cleanup(&stdoutBuf, &stderrBuf); err == nil || verror.ErrorID(err) != exec.ErrTimeout.ID {
+		t.Errorf("unexpected error in Cleanup: got %v, want %v", err, exec.ErrTimeout.ID)
 	}
 	if err := syscall.Kill(h.Pid(), syscall.SIGINT); err != nil {
 		t.Errorf("Kill failed: %v", err)