Repository: puppetlabs/wash
Pull request #685 (2020-01-14 04:57:54)

(Code) Change meta attribute => partial metadata
Supports #684.

There's a few more changes that need to be made (see the issue). These changes only make sense after the code here's released.


Patch:
diff --git a/api/fs/core.go b/api/fs/core.go
index 5995ed3..6048535 100644
--- a/api/fs/core.go
+++ b/api/fs/core.go
@@ -26,11 +26,11 @@ func newFSNode(finfo os.FileInfo, path string) fsnode {
 	}
 	n.
 		DisableDefaultCaching().
+		SetPartialMetadata(newFileInfo(finfo)).
 		Attributes().
 		SetMtime(finfo.ModTime()).
 		SetMode(finfo.Mode()).
-		SetSize(uint64(finfo.Size())).
-		SetMeta(plugin.ToJSONObject(newFileInfo(finfo)))
+		SetSize(uint64(finfo.Size()))
 	return n
 }
 
diff --git a/api/helpers.go b/api/helpers.go
index abf2c22..dbe6925 100644
--- a/api/helpers.go
+++ b/api/helpers.go
@@ -22,6 +22,7 @@ func toAPIEntry(e plugin.Entry) apitypes.Entry {
 		CName:      plugin.CName(e),
 		Actions:    plugin.SupportedActionsOf(e),
 		Attributes: plugin.Attributes(e),
+		Metadata:   plugin.PartialMetadata(e),
 	}
 }
 
diff --git a/api/list.go b/api/list.go
index a9bce38..16d4b46 100644
--- a/api/list.go
+++ b/api/list.go
@@ -23,6 +23,7 @@ type entryList struct {
 // Lists children of a path
 //
 // Returns a list of Entry objects describing children of the given path.
+// The "metadata" key is set to the partial metadata.
 //
 //     Produces:
 //     - application/json
diff --git a/api/types/entry.go b/api/types/entry.go
index 254b480..9c043a9 100644
--- a/api/types/entry.go
+++ b/api/types/entry.go
@@ -12,6 +12,7 @@ type Entry struct {
 	Name       string                 `json:"name"`
 	CName      string                 `json:"cname"`
 	Attributes plugin.EntryAttributes `json:"attributes"`
+	Metadata   plugin.JSONObject      `json:"metadata"`
 }
 
 // Supports returns true if e supports the given action, false
diff --git a/api/types/entrySchema.go b/api/types/entrySchema.go
index aab2b21..d27c7bf 100644
--- a/api/types/entrySchema.go
+++ b/api/types/entrySchema.go
@@ -234,16 +234,16 @@ func (s *EntrySchema) SetActions(actions []string) *EntrySchema {
 	return s
 }
 
-// MetaAttributeSchema returns the entry's meta attribute
+// PartialMetadataSchema returns the entry's partial metadata
 // schema
-func (s *EntrySchema) MetaAttributeSchema() *plugin.JSONSchema {
-	return s.EntrySchema.MetaAttributeSchema
+func (s *EntrySchema) PartialMetadataSchema() *plugin.JSONSchema {
+	return s.EntrySchema.PartialMetadataSchema
 }
 
-// SetMetaAttributeSchema sets the entry's meta attribute
+// SetPartialMetadataSchema sets the entry's meta attribute
 // schema to s. This should only be called by the tests
-func (s *EntrySchema) SetMetaAttributeSchema(schema *plugin.JSONSchema) {
-	s.EntrySchema.MetaAttributeSchema = schema
+func (s *EntrySchema) SetPartialMetadataSchema(schema *plugin.JSONSchema) {
+	s.EntrySchema.PartialMetadataSchema = schema
 }
 
 // MetadataSchema returns the entry's metadata schema
diff --git a/cmd/docs.go b/cmd/docs.go
index c0962d1..0689f4a 100644
--- a/cmd/docs.go
+++ b/cmd/docs.go
@@ -61,7 +61,7 @@ func docsMain(cmd *cobra.Command, args []string) exitCode {
 	//     * <attribute> (<full_name_of_attribute>)
 	//
 	//   <description that talks about attributes/metadata and shows off 'meta'>
-	if len(entry.Attributes.ToMap(false)) > 0 {
+	if len(entry.Attributes.ToMap()) > 0 {
 		addSection(docs, stringifySupportedAttributes(path, entry))
 	}
 
@@ -111,7 +111,7 @@ func docsMain(cmd *cobra.Command, args []string) exitCode {
 func stringifySupportedAttributes(path string, entry apitypes.Entry) string {
 	var supportedAttributes strings.Builder
 	supportedAttributes.WriteString("SUPPORTED ATTRIBUTES\n")
-	for attr, value := range entry.Attributes.ToMap(false) {
+	for attr, value := range entry.Attributes.ToMap() {
 		supportedAttributes.WriteString(fmt.Sprintf("* %v", attr))
 		var fullAttrName string
 		switch attr {
@@ -135,10 +135,10 @@ func stringifySupportedAttributes(path string, entry apitypes.Entry) string {
 		fmt.Sprintf("you ever need to know about the entry. You can use"),
 		fmt.Sprintf("    meta %s", path),
 		fmt.Sprintf("to view the metadata and"),
-		fmt.Sprintf("    meta --attribute %s", path),
-		fmt.Sprintf("to view the (partial) metadata. You can use 'find' to filter entries on their"),
-		fmt.Sprintf("attributes and metadata. Type 'find --help' to see all the properties that you"),
-		fmt.Sprintf("can filter on."),
+		fmt.Sprintf("    meta --partial %s", path),
+		fmt.Sprintf("to view the partial metadata. You can use 'find' to filter entries on their"),
+		fmt.Sprintf("attributes and metadata. Type 'find --help' to see all the properties that"),
+		fmt.Sprintf("you can filter on."),
 	}
 	supportedAttributes.WriteString(strings.Join(metadataLines, "\n"))
 	return supportedAttributes.String()
diff --git a/cmd/info.go b/cmd/info.go
index dccb582..ea02acb 100644
--- a/cmd/info.go
+++ b/cmd/info.go
@@ -63,7 +63,7 @@ func infoMain(cmd *cobra.Command, args []string) exitCode {
 			entryMap.Put("Name", entry.Name)
 			entryMap.Put("CName", entry.CName)
 			entryMap.Put("Actions", entry.Actions)
-			entryMap.Put("Attributes", entry.Attributes.ToMap(false))
+			entryMap.Put("Attributes", entry.Attributes.ToMap())
 
 			infoMapMux.Lock()
 			infoMap[path] = entryMap
diff --git a/cmd/internal/find/primary/meta.go b/cmd/internal/find/primary/meta.go
index 651badb..13a52a6 100644
--- a/cmd/internal/find/primary/meta.go
+++ b/cmd/internal/find/primary/meta.go
@@ -70,58 +70,19 @@ const metaDetailedDescription = `
 NOTE: https://puppetlabs.github.io/wash/tutorials/02_find/meta-primary
 contains a hands-on tutorial of the meta primary.
 
-The meta primary constructs a predicate on the entry's metadata. By
-default, this is the meta attribute. If you'd like to construct the
-predicate on the entry's full metadata, then set the "fullmeta" option.
-Be careful when you do this, because find will make O(N) API requests
-to retrieve this information (N = the number of visited entries). As a
-precaution, the "fullmeta" option is only supported on entries with
-schemas.
-
-Meta is a specialized filter, so you should only use it if you need to
-filter your entries on a property that isn't captured by the common Wash
-attributes. For example, the meta primary can be used to filter EC2
-instances on a specific tag. It can be used to filter Docker containers
-with a specified label. In general, the meta primary can be used to filter
-on any property that's specified in the entry's meta attribute. See the
-EXAMPLES section for some interesting real-world examples.
-
-NOTE: The meta primary should be used in conjunction with the kind primary.
-The latter's useful for explicitly specifying the kind of entries you're
-filtering on, which enables find to take full advantage of entry schema
-optimizations. This is especially useful when the entry's schema doesn't
-include metadata schemas (possible for external plugins). The kind primary's
-also useful for making meta queries more expressive. See the EXAMPLES section
-for more details on what this looks like.
-
-NOTE: If your plugin's API is not subscription based (like AWS) or if
-individual API requests are cheap, then feel free to always set the
-"fullmeta" option for more complete filtering. This is very useful when
-your plugin API's over a Unix socket.
-
-NOTE: You can use the meta command to construct meta primary queries.
-Here's how. First, find a representative entry that you'll be filtering.
-For example, if you are filtering EC2 instances, then your representative
-entry would be an EC2 instance. Next, invoke "meta <entry_path> -a" to
-see that entry's meta attribute. Check the output to see if it contains
-the properties you'd like to filter on. If yes, then construct the
-predicate. If no, then invoke "meta <entry_path>" to see the entry's
-full metadata, and check its output to see if it contains your properties.
-If the properties are there, and the O(N) API requests made by "fullmeta"
-aren't an issue, then construct the predicate and be sure to set the
-"fullmeta" option when invoking "wash find". Otherwise, contact the plugin
-author to see if they can add those properties to the entry's full metadata
-or, preferably, the meta attribute.
-
-NOTE: If the current meta primary predicates aren't enough to suit your
-needs, then please file an issue or feel free to add one yourself!
+NOTE: The EXAMPLES section contains some real-world examples of the meta
+primary in action.
+
+NOTE: By default, the meta primary constructs predicates on the entry's
+partial metadata. You can set the -fullmeta option to use the full
+metadata instead.
 
 USAGE:
 (-m|-meta) (-empty | KeySequence PredicateExpression)
 
-If -empty is specified, then returns true if the entry's meta attribute
-is empty. Otherwise, returns true if the meta value specified by the key
-sequence satisfies the given predicate expression.
+If -empty is specified, then returns true if the entry's metadata is empty.
+Otherwise, returns true if the meta value specified by the key sequence
+satisfies the given predicate expression.
 
 KEY SEQUENCES:
 A key sequence consists of a key token followed by zero or more "chunks".
@@ -520,7 +481,7 @@ This section contains various, real-world examples of the meta primary's usage.
 you have an interesting example that you'd like to include here, then please feel
 free to submit a PR!
 
-In these examples, let "m" be the value of the entry's 'meta' attribute.
+In these examples, let "m" be the value of the entry's metadata.
 
 -meta '.tags[?]' .key termination_date -a .value +0h
 -m '.tags[?]' .key termination_date -a .value +0h
diff --git a/cmd/internal/find/types/entry.go b/cmd/internal/find/types/entry.go
index 124f21e..dfa4857 100644
--- a/cmd/internal/find/types/entry.go
+++ b/cmd/internal/find/types/entry.go
@@ -2,14 +2,12 @@ package types
 
 import (
 	apitypes "github.com/puppetlabs/wash/api/types"
-	"github.com/puppetlabs/wash/plugin"
 )
 
 // Entry represents an Entry as interpreted by `wash find`
 type Entry struct {
 	apitypes.Entry
 	NormalizedPath string
-	Metadata       plugin.JSONObject
 	SchemaKnown    bool
 	Schema         *EntrySchema
 }
@@ -19,7 +17,6 @@ func NewEntry(e apitypes.Entry, normalizedPath string) Entry {
 	return Entry{
 		Entry:          e,
 		NormalizedPath: normalizedPath,
-		Metadata:       e.Attributes.Meta(),
 	}
 }
 
diff --git a/cmd/internal/find/types/entrySchema.go b/cmd/internal/find/types/entrySchema.go
index e7dbc69..3ccf523 100644
--- a/cmd/internal/find/types/entrySchema.go
+++ b/cmd/internal/find/types/entrySchema.go
@@ -83,7 +83,7 @@ func evaluateNodesToKeep(s *EntrySchema, p EntrySchemaPredicate, opts Options) m
 			return
 		}
 		// Set the metadata schema prior to evaluating the predicate
-		metadataSchema := s.MetaAttributeSchema()
+		metadataSchema := s.PartialMetadataSchema()
 		if opts.Fullmeta && s.MetadataSchema() != nil {
 			metadataSchema = s.MetadataSchema()
 		}
diff --git a/cmd/meta.go b/cmd/meta.go
index b13be0d..5c238b5 100644
--- a/cmd/meta.go
+++ b/cmd/meta.go
@@ -13,13 +13,13 @@ func metaCommand() *cobra.Command {
 		Short: "Prints the metadata of the given entries",
 		Long: `Prints the metadata of the given entries. By default, meta prints the
 full metadata as returned by the metadata endpoint. Specify the
---attribute flag to instead print the meta attribute, a (possibly)
+--partial flag to instead print the partial metadata, a (possibly)
 reduced set of metadata that's returned when entries are enumerated.`,
 		Args: cobra.MinimumNArgs(1),
 		RunE: toRunE(metaMain),
 	}
 	metaCmd.Flags().StringP("output", "o", "yaml", "Set the output format (json, yaml, or text)")
-	metaCmd.Flags().BoolP("attribute", "a", false, "Print the meta attribute instead of the full metadata")
+	metaCmd.Flags().BoolP("partial", "p", false, "Print the partial metadata instead")
 	return metaCmd
 }
 
@@ -29,7 +29,7 @@ func metaMain(cmd *cobra.Command, args []string) exitCode {
 	if err != nil {
 		panic(err.Error())
 	}
-	showMetaAttr, err := cmd.Flags().GetBool("attribute")
+	showPartialMetadata, err := cmd.Flags().GetBool("partial")
 	if err != nil {
 		panic(err.Error())
 	}
@@ -54,14 +54,14 @@ func metaMain(cmd *cobra.Command, args []string) exitCode {
 
 			var metadata map[string]interface{}
 
-			if showMetaAttr {
+			if showPartialMetadata {
 				e, err := conn.Info(path)
 				if err != nil {
 					ec = 1
 					cmdutil.SafeErrPrintf("%v: %v\n", path, err)
 					return
 				}
-				metadata = e.Attributes.Meta()
+				metadata = e.Metadata
 			} else {
 				var err error
 				metadata, err = conn.Metadata(path)
diff --git a/plugin/aws/ec2Instance.go b/plugin/aws/ec2Instance.go
index ddc064a..16b19e0 100644
--- a/plugin/aws/ec2Instance.go
+++ b/plugin/aws/ec2Instance.go
@@ -61,10 +61,13 @@ func newEC2Instance(ctx context.Context, inst *ec2Client.Instance, session *sess
 	ec2Instance.id = id
 	ec2Instance.session = session
 	ec2Instance.client = client
+
+	attributes, metadata := getAttributesAndMetadata(inst)
 	ec2Instance.
 		SetTTLOf(plugin.ListOp, 30*time.Second).
 		DisableCachingFor(plugin.MetadataOp).
-		SetAttributes(getAttributes(inst))
+		SetAttributes(attributes).
+		SetPartialMetadata(metadata)
 
 	return ec2Instance
 }
@@ -118,7 +121,7 @@ type ec2InstanceMetadata struct {
 	LastModifiedTime time.Time
 }
 
-func getAttributes(inst *ec2Client.Instance) plugin.EntryAttributes {
+func getAttributesAndMetadata(inst *ec2Client.Instance) (plugin.EntryAttributes, plugin.JSONObject) {
 	attr := plugin.EntryAttributes{}
 
 	// AWS does not include the EC2 instance's crtime in its
@@ -141,25 +144,24 @@ func getAttributes(inst *ec2Client.Instance) plugin.EntryAttributes {
 		}
 	}
 
+	attr.
+		SetCrtime(crtime).
+		SetMtime(mtime)
+
 	meta := plugin.ToJSONObject(ec2InstanceMetadata{
 		Instance:         inst,
 		CreationTime:     crtime,
 		LastModifiedTime: mtime,
 	})
 
-	attr.
-		SetCrtime(crtime).
-		SetMtime(mtime).
-		SetMeta(meta)
-
-	return attr
+	return attr, meta
 }
 
 func (inst *ec2Instance) Schema() *plugin.EntrySchema {
 	return plugin.
 		NewEntrySchema(inst, "instance").
 		SetDescription(ec2InstanceDescription).
-		SetMetaAttributeSchema(ec2InstanceMetadata{}).
+		SetPartialMetadataSchema(ec2InstanceMetadata{}).
 		AddSignal("start", "Starts the EC2 instance").
 		AddSignal("stop", "Stops the EC2 instance").
 		AddSignal("hibernate", "Hibernates the EC2 instance").
diff --git a/plugin/aws/s3Object.go b/plugin/aws/s3Object.go
index ca082dd..c85283f 100644
--- a/plugin/aws/s3Object.go
+++ b/plugin/aws/s3Object.go
@@ -39,13 +39,13 @@ func newS3Object(o *s3Client.Object, name string, bucket string, key string, cli
 	// logic of validating a negative size
 	mtime := awsSDK.TimeValue(o.LastModified)
 	s3Obj.
+		SetPartialMetadata(o).
 		Attributes().
 		SetCrtime(mtime).
 		SetMtime(mtime).
 		SetCtime(mtime).
 		SetAtime(mtime).
-		SetSize(uint64(awsSDK.Int64Value(o.Size))).
-		SetMeta(o)
+		SetSize(uint64(awsSDK.Int64Value(o.Size)))
 
 	return s3Obj
 }
@@ -54,7 +54,7 @@ func (o *s3Object) Schema() *plugin.EntrySchema {
 	return plugin.
 		NewEntrySchema(o, "object").
 		SetDescription(s3ObjectDescription).
-		SetMetaAttributeSchema(s3Client.Object{}).
+		SetPartialMetadataSchema(s3Client.Object{}).
 		SetMetadataSchema(s3Client.HeadObjectOutput{})
 }
 
diff --git a/plugin/cache.go b/plugin/cache.go
index 5cdda35..d7a3f12 100644
--- a/plugin/cache.go
+++ b/plugin/cache.go
@@ -181,7 +181,7 @@ func cachedList(ctx context.Context, p Parent) (*EntryMap, error) {
 		// Including the entry's ID allows plugin authors to use any Cached* methods defined on the
 		// children after their creation. This is necessary when the child's Cached* methods are used
 		// to calculate its attributes. Note that the child's ID is set in cachedOp.
-		entries, err := p.List(context.WithValue(ctx, parentID, p.id()))
+		entries, err := p.List(context.WithValue(ctx, parentID, p.eb().id))
 		if err != nil {
 			return nil, err
 		}
@@ -192,16 +192,16 @@ func cachedList(ctx context.Context, p Parent) (*EntryMap, error) {
 
 			if duplicateEntry, ok := searchedEntries.mp[cname]; ok {
 				return nil, DuplicateCNameErr{
-					ParentID:                 p.id(),
-					FirstChildName:           duplicateEntry.name(),
-					FirstChildSlashReplacer:  duplicateEntry.slashReplacer(),
-					SecondChildName:          entry.name(),
-					SecondChildSlashReplacer: entry.slashReplacer(),
+					ParentID:                 p.eb().id,
+					FirstChildName:           duplicateEntry.eb().name,
+					FirstChildSlashReplacer:  duplicateEntry.eb().slashReplacer,
+					SecondChildName:          entry.eb().name,
+					SecondChildSlashReplacer: entry.eb().slashReplacer,
 					CName:                    cname,
 				}
 			}
 
-			if entry.isInaccessible() {
+			if entry.eb().isInaccessible {
 				// Skip entries that are expected to be inaccessible.
 				continue
 			}
@@ -210,7 +210,7 @@ func cachedList(ctx context.Context, p Parent) (*EntryMap, error) {
 
 			// Ensure ID is set on all entries so that we can use it for caching later in places
 			// where the context doesn't include the parent's ID.
-			setChildID(p.id(), entry)
+			setChildID(p.eb().id, entry)
 
 			passAlongWrappedTypes(p, entry)
 		}
@@ -254,7 +254,7 @@ func cachedRead(ctx context.Context, e Entry) (entryContent, error) {
 				panic("attempting to retrieve the content of a non-readable entry")
 			}
 			content := newBlockReadableEntryContent(readFunc)
-			if attr := e.attributes(); attr.HasSize() {
+			if attr := e.eb().attributes; attr.HasSize() {
 				content.sz = attr.Size()
 			}
 			return content, nil
@@ -288,7 +288,7 @@ func cachedMetadata(ctx context.Context, e Entry) (JSONObject, error) {
 // Common helper for CachedList, CachedOpen and CachedMetadata
 func cachedDefaultOp(ctx context.Context, opCode defaultOpCode, entry Entry, op opFunc) (interface{}, error) {
 	opName := defaultOpCodeToNameMap[opCode]
-	ttl := entry.getTTLOf(opCode)
+	ttl := entry.eb().ttl[opCode]
 
 	return cachedOp(ctx, opName, entry, ttl, op)
 }
@@ -307,19 +307,19 @@ func cachedOp(ctx context.Context, opName string, entry Entry, ttl time.Duration
 		return op()
 	}
 
-	if entry.id() == "" {
+	if entry.eb().id == "" {
 		// Try to set the ID based on parent ID
 		if obj := ctx.Value(parentID); obj != nil {
 			setChildID(obj.(string), entry)
 		} else {
-			panic(fmt.Sprintf("Cached op %v on %v had no cache ID and context did not include parent ID", opName, entry.name()))
+			panic(fmt.Sprintf("Cached op %v on %v had no cache ID and context did not include parent ID", opName, entry.eb().name))
 		}
 	}
 
-	return cache.GetOrUpdate(opName, entry.id(), ttl, false, op)
+	return cache.GetOrUpdate(opName, entry.eb().id, ttl, false, op)
 }
 
 func setChildID(parentID string, child Entry) {
 	id := strings.TrimRight(parentID, "/") + "/" + CName(child)
-	child.setID(id)
+	child.eb().id = id
 }
diff --git a/plugin/docker/container.go b/plugin/docker/container.go
index 43663ea..5caa359 100644
--- a/plugin/docker/container.go
+++ b/plugin/docker/container.go
@@ -39,12 +39,12 @@ func newContainer(inst types.Container, client *client.Client) *container {
 
 	startTime := time.Unix(inst.Created, 0)
 	cont.
+		SetPartialMetadata(inst).
 		Attributes().
 		SetCrtime(startTime).
 		SetMtime(startTime).
 		SetCtime(startTime).
-		SetAtime(startTime).
-		SetMeta(inst)
+		SetAtime(startTime)
 
 	return cont
 }
@@ -62,7 +62,7 @@ func (c *container) Metadata(ctx context.Context) (plugin.JSONObject, error) {
 func (c *container) Schema() *plugin.EntrySchema {
 	return plugin.
 		NewEntrySchema(c, "container").
-		SetMetaAttributeSchema(types.Container{}).
+		SetPartialMetadataSchema(types.Container{}).
 		SetMetadataSchema(types.ContainerJSON{}).
 		AddSignal("start", "Starts the container. Equivalent to 'docker start <container>'").
 		AddSignal("stop", "Stops the container. Equivalent to 'docker stop <container>'").
diff --git a/plugin/docker/volume.go b/plugin/docker/volume.go
index 94fa701..4dc9c11 100644
--- a/plugin/docker/volume.go
+++ b/plugin/docker/volume.go
@@ -39,12 +39,12 @@ func newVolume(c *client.Client, v *types.Volume) (*volume, error) {
 	vol.client = c
 	vol.SetTTLOf(plugin.ListOp, volpkg.ListTTL)
 	vol.
+		SetPartialMetadata(v).
 		Attributes().
 		SetCrtime(startTime).
 		SetMtime(startTime).
 		SetCtime(startTime).
-		SetAtime(startTime).
-		SetMeta(v)
+		SetAtime(startTime)
 
 	return vol, nil
 }
@@ -53,7 +53,7 @@ func (v *volume) Schema() *plugin.EntrySchema {
 	return plugin.
 		NewEntrySchema(v, "volume").
 		SetDescription(volumeDescription).
-		SetMetaAttributeSchema(types.Volume{})
+		SetPartialMetadataSchema(types.Volume{})
 }
 
 func (v *volume) ChildSchemas() []*plugin.EntrySchema {
diff --git a/plugin/entryAttributes.go b/plugin/entryAttributes.go
index ebcb114..654a2ed 100644
--- a/plugin/entryAttributes.go
+++ b/plugin/entryAttributes.go
@@ -47,8 +47,7 @@ to do something like
 	attr := plugin.EntryAttributes{}
 	attr.
 		SetCrtime(crtime).
-		SetMtime(mtime).
-		SetMeta(meta)
+		SetMtime(mtime)
 	entry.SetAttributes(attr)
 */
 type EntryAttributes struct {
@@ -60,7 +59,6 @@ type EntryAttributes struct {
 	hasMode bool
 	size    uint64
 	hasSize bool
-	meta    JSONObject
 }
 
 // We can't just export EntryAttributes' fields because there's no way
@@ -179,36 +177,9 @@ func (a *EntryAttributes) SetSize(size uint64) *EntryAttributes {
 	return a
 }
 
-// Meta returns the entry's meta attribute. If a.SetMeta(obj) was called,
-// then this returns obj serialized to JSONObject. Otherwise, it returns
-// a.ToMap(false).
-//
-// NOTE: The meta attribute is a subset of the entry's full metadata, which
-// is what e.Metadata returns. It is typically provided by the plugin API's
-// List endpoint.
-func (a *EntryAttributes) Meta() JSONObject {
-	if a.meta == nil {
-		return a.ToMap(false)
-	}
-
-	return a.meta
-}
-
-// SetMeta sets the entry's meta attribute to obj. This is typically the
-// raw object that's returned by the plugin API's List endpoint, or a wrapper
-// that includes the raw object + some additional information. For example, if
-// the entry represents a Docker container, then obj would be a Container struct.
-// If the entry represents a Docker volume, then obj would be a Volume struct.
-//
-// SetMeta will panic if obj does not serialize to a JSON object.
-func (a *EntryAttributes) SetMeta(obj interface{}) *EntryAttributes {
-	a.meta = ToJSONObject(obj)
-	return a
-}
-
 // ToMap converts the entry's attributes to a map, which makes it easier to write
 // generic code on them.
-func (a *EntryAttributes) ToMap(includeMeta bool) map[string]interface{} {
+func (a *EntryAttributes) ToMap() map[string]interface{} {
 	mp := make(map[string]interface{})
 	if a.HasAtime() {
 		mp["atime"] = a.Atime()
@@ -230,9 +201,6 @@ func (a *EntryAttributes) ToMap(includeMeta bool) map[string]interface{} {
 	if a.HasSize() {
 		mp["size"] = a.Size()
 	}
-	if includeMeta {
-		mp["meta"] = a.Meta()
-	}
 	return mp
 }
 
@@ -241,7 +209,7 @@ func (a *EntryAttributes) ToMap(includeMeta bool) map[string]interface{} {
 // when they're referenced as interface{} objects. See
 // https://stackoverflow.com/a/21394657 for more details.
 func (a EntryAttributes) MarshalJSON() ([]byte, error) {
-	m := a.ToMap(true)
+	m := a.ToMap()
 	// Override Mode to use a re-marshalable representation.
 	if a.HasMode() {
 		m["mode"] = a.Mode()
@@ -298,13 +266,6 @@ func (a *EntryAttributes) UnmarshalJSON(data []byte) error {
 		}
 		a.SetSize(sz)
 	}
-	if rawMeta, ok := mp["meta"]; ok {
-		meta, isObj := rawMeta.(JSONObject)
-		if !isObj {
-			return fmt.Errorf("meta is not a JSON object")
-		}
-		a.SetMeta(meta)
-	}
 	return nil
 }
 
diff --git a/plugin/entryBase.go b/plugin/entryBase.go
index e6a901d..419611c 100644
--- a/plugin/entryBase.go
+++ b/plugin/entryBase.go
@@ -37,15 +37,15 @@ to do something like
 		SetMeta(meta)
 */
 type EntryBase struct {
-	entryName       string
-	attr            EntryAttributes
-	slashReplacerCh rune
-	// washID represents the entry's wash ID. It is set in CachedList.
-	washID          string
-	ttl             [3]time.Duration
-	wrappedTypesMap SchemaMap
-	prefetched      bool
-	inaccessible    bool
+	name                     string
+	attributes               EntryAttributes
+	specifiedPartialMetadata JSONObject
+	slashReplacer            rune
+	id                       string
+	ttl                      [3]time.Duration
+	wrappedTypes             SchemaMap
+	isPrefetched             bool
+	isInaccessible           bool
 }
 
 // NewEntry creates a new entry
@@ -55,8 +55,8 @@ func NewEntry(name string) EntryBase {
 	}
 
 	e := EntryBase{
-		entryName:       name,
-		slashReplacerCh: '#',
+		name:          name,
+		slashReplacer: '#',
 	}
 	for op := range e.ttl {
 		e.SetTTLOf(defaultOpCode(op), 15*time.Second)
@@ -64,52 +64,29 @@ func NewEntry(name string) EntryBase {
 	return e
 }
 
+func (e *EntryBase) partialMetadata() JSONObject {
+	if e.specifiedPartialMetadata != nil {
+		return e.specifiedPartialMetadata
+	}
+	// This case strictly enforces the "attributes are a subset of the
+	// (partial) metadata" invariant. Here, if no partial metadata was
+	// set, then we assume that attributes == partialMetadata.
+	return e.attributes.ToMap()
+}
+
 // ENTRY INTERFACE
 
-// Metadata returns the entry's meta attribute (see plugin.EntryAttributes).
-// Override this if e has additional metadata.
+// Metadata returns the entry's partial metadata. Override this if e has
+// additional metadata properties that couldn't be included in the partial
+// metadata because doing so would have slowed down parent#List.
 func (e *EntryBase) Metadata(ctx context.Context) (JSONObject, error) {
 	// Disable Metadata's caching in case the plugin author forgot to do this
 	e.DisableCachingFor(MetadataOp)
-
-	attr := e.attributes()
-	return attr.Meta(), nil
-}
-
-func (e *EntryBase) name() string {
-	return e.entryName
-}
-
-func (e *EntryBase) attributes() EntryAttributes {
-	return e.attr
-}
-
-func (e *EntryBase) slashReplacer() rune {
-	return e.slashReplacerCh
-}
-
-func (e *EntryBase) id() string {
-	return e.washID
-}
-
-func (e *EntryBase) setID(id string) {
-	e.washID = id
+	return e.partialMetadata(), nil
 }
 
-func (e *EntryBase) getTTLOf(op defaultOpCode) time.Duration {
-	return e.ttl[op]
-}
-
-func (e *EntryBase) wrappedTypes() SchemaMap {
-	return e.wrappedTypesMap
-}
-
-func (e *EntryBase) setWrappedTypes(wrappedTypes SchemaMap) {
-	e.wrappedTypesMap = wrappedTypes
-}
-
-func (e *EntryBase) isPrefetched() bool {
-	return e.prefetched
+func (e *EntryBase) eb() *EntryBase {
+	return e
 }
 
 // OTHER METHODS USED TO FACILITATE PLUGIN DEVELOPMENT
@@ -119,37 +96,48 @@ func (e *EntryBase) isPrefetched() bool {
 // plugin.NewEntry. You should use e.Name() when making
 // the appropriate API calls within your plugin.
 func (e *EntryBase) Name() string {
-	return e.name()
+	return e.name
 }
 
 // String returns a unique identifier for the entry suitable for logging and error messages.
 func (e *EntryBase) String() string {
-	return e.id()
+	return e.id
 }
 
 // Attributes returns a pointer to the entry's attributes. Use it
 // to individually set the entry's attributes
 func (e *EntryBase) Attributes() *EntryAttributes {
-	return &e.attr
+	return &e.attributes
 }
 
 // SetAttributes sets the entry's attributes. Use it to set
 // the entry's attributes in a single operation, which is useful
 // when you've already pre-computed them.
 func (e *EntryBase) SetAttributes(attr EntryAttributes) *EntryBase {
-	e.attr = attr
+	e.attributes = attr
 	return e
 }
 
-func (e *EntryBase) isInaccessible() bool {
-	return e.inaccessible
+// SetPartialMetadata sets the entry's partial metadata. This is typically the
+// raw object that's returned by the plugin API's List endpoint, or a wrapper
+// that includes the raw object + some additional information. For example, if
+// the entry represents a Docker container, then obj would be a Container struct.
+// If the entry represents a Docker volume, then obj would be a Volume struct.
+//
+// In general, the partial metadata is the subset of metadata that can be fetched
+// "quickly" s.t. parent#List isn't slowed down.
+//
+// SetPartialMetadata will panic if obj does not serialize to a JSON object.
+func (e *EntryBase) SetPartialMetadata(obj interface{}) *EntryBase {
+	e.specifiedPartialMetadata = ToJSONObject(obj)
+	return e
 }
 
 // MarkInaccessible sets the inaccessible attribute and logs a message about why the entry is
 // inaccessible.
 func (e *EntryBase) MarkInaccessible(ctx context.Context, err error) {
-	activity.Record(ctx, "Omitting %v: %v", e.id(), err)
-	e.inaccessible = true
+	activity.Record(ctx, "Omitting %v: %v", e.id, err)
+	e.isInaccessible = true
 }
 
 // Prefetched marks the entry as a prefetched entry. A prefetched entry
@@ -158,7 +146,7 @@ func (e *EntryBase) MarkInaccessible(ctx context.Context, err error) {
 // files are good examples of prefetched entries (see the volume
 // package for more details).
 func (e *EntryBase) Prefetched() *EntryBase {
-	e.prefetched = true
+	e.isPrefetched = true
 	return e
 }
 
@@ -172,7 +160,7 @@ func (e *EntryBase) SetSlashReplacer(char rune) *EntryBase {
 		panic("e.SetSlashReplacer called with '/'")
 	}
 
-	e.slashReplacerCh = char
+	e.slashReplacer = char
 	return e
 }
 
@@ -204,7 +192,7 @@ func (e *EntryBase) SetTestID(id string) {
 		panic("SetTestID can be only be called by the tests")
 	}
 
-	e.setID(id)
+	e.id = id
 }
 
 func notRunningTests() bool {
diff --git a/plugin/entrySchema.go b/plugin/entrySchema.go
index e8de058..78c6705 100644
--- a/plugin/entrySchema.go
+++ b/plugin/entrySchema.go
@@ -40,7 +40,7 @@ func schema(e Entry) (*EntrySchema, error) {
 		for _, root := range t.pluginRoots {
 			childSchema, err := Schema(root)
 			if err != nil {
-				return nil, fmt.Errorf("failed to retrieve the %v plugin's schema: %v", root.name(), err)
+				return nil, fmt.Errorf("failed to retrieve the %v plugin's schema: %v", root.eb().name, err)
 			}
 			if childSchema == nil {
 				// The plugin doesn't have a schema, which means it's an external plugin.
@@ -68,14 +68,14 @@ func schema(e Entry) (*EntrySchema, error) {
 }
 
 type entrySchema struct {
-	Label               string         `json:"label"`
-	Description         string         `json:"description,omitempty"`
-	Singleton           bool           `json:"singleton"`
-	Signals             []SignalSchema `json:"signals,omitempty"`
-	Actions             []string       `json:"actions"`
-	MetaAttributeSchema *JSONSchema    `json:"meta_attribute_schema"`
-	MetadataSchema      *JSONSchema    `json:"metadata_schema"`
-	Children            []string       `json:"children"`
+	Label                 string         `json:"label"`
+	Description           string         `json:"description,omitempty"`
+	Singleton             bool           `json:"singleton"`
+	Signals               []SignalSchema `json:"signals,omitempty"`
+	Actions               []string       `json:"actions"`
+	PartialMetadataSchema *JSONSchema    `json:"partial_metadata_schema"`
+	MetadataSchema        *JSONSchema    `json:"metadata_schema"`
+	Children              []string       `json:"children"`
 }
 
 // EntrySchema represents an entry's schema. Use plugin.NewEntrySchema
@@ -88,8 +88,8 @@ type EntrySchema struct {
 	//
 	// This pattern was obtained from https://stackoverflow.com/a/11129474
 	entrySchema
-	metaAttributeSchemaObj interface{}
-	metadataSchemaObj      interface{}
+	partialMetadataSchemaObj interface{}
+	metadataSchemaObj        interface{}
 	// Store the entry so that we can compute its type ID and, if the entry's
 	// a core plugin entry, enumerate its child schemas when marshaling its
 	// schema.
@@ -108,9 +108,9 @@ func NewEntrySchema(e Entry, label string) *EntrySchema {
 			Label:   label,
 			Actions: SupportedActionsOf(e),
 		},
-		// The meta attribute's empty by default
-		metaAttributeSchemaObj: struct{}{},
-		entry:                  e,
+		// The partial metadata's empty by default
+		partialMetadataSchemaObj: struct{}{},
+		entry:                    e,
 	}
 	return s
 }
@@ -195,15 +195,15 @@ func (s *EntrySchema) addSignalSchema(name string, regex string, description str
 	return s
 }
 
-// SetMetaAttributeSchema sets the meta attribute's schema. obj is an empty struct
-// that will be marshalled into a JSON schema. SetMetaSchema will panic
-// if obj is not a struct.
-func (s *EntrySchema) SetMetaAttributeSchema(obj interface{}) *EntrySchema {
+// SetPartialMetadataSchema sets the partial metadata's schema. obj is an empty
+// struct that will be marshalled into a JSON schema. SetPartialMetadataSchema
+// will panic if obj is not a struct.
+func (s *EntrySchema) SetPartialMetadataSchema(obj interface{}) *EntrySchema {
 	// We need to know if s.entry has any wrapped types in order to correctly
 	// compute the schema. However that information is known when s.fill() is
 	// called. Thus, we'll set the schema object to obj so s.fill() can properly
 	// calculate the schema.
-	s.metaAttributeSchemaObj = obj
+	s.partialMetadataSchemaObj = obj
 	return s
 }
 
@@ -211,20 +211,20 @@ func (s *EntrySchema) SetMetaAttributeSchema(obj interface{}) *EntrySchema {
 // marshalled into a JSON schema. SetMetadataSchema will panic if obj is not a struct.
 //
 // NOTE: Only use SetMetadataSchema if you're overriding Entry#Metadata. Otherwise, use
-// SetMetaAttributeSchema.
+// SetPartialMetadataSchema.
 func (s *EntrySchema) SetMetadataSchema(obj interface{}) *EntrySchema {
-	// See the comments in SetMetaAttributeSchema to understand why this line's necessary
+	// See the comments in SetPartialMetadataSchema to understand why this line's necessary
 	s.metadataSchemaObj = obj
 	return s
 }
 
 func (s *EntrySchema) fill(graph *linkedhashmap.Map) {
-	// Fill-in the meta attribute + metadata schemas
+	// Fill-in the partial metadata + metadata schemas
 	var err error
-	if s.metaAttributeSchemaObj != nil {
-		s.entrySchema.MetaAttributeSchema, err = s.schemaOf(s.metaAttributeSchemaObj)
+	if s.partialMetadataSchemaObj != nil {
+		s.entrySchema.PartialMetadataSchema, err = s.schemaOf(s.partialMetadataSchemaObj)
 		if err != nil {
-			s.fillPanicf("bad value passed into SetMetaAttributeSchema: %v", err)
+			s.fillPanicf("bad value passed into SetPartialMetadataSchema: %v", err)
 		}
 	}
 	if s.metadataSchemaObj != nil {
@@ -251,7 +251,7 @@ func (s *EntrySchema) fill(graph *linkedhashmap.Map) {
 		}
 		// The ID here is meaningless. We only set it so that TypeID can get the
 		// plugin name
-		child.entry.setID(s.entry.id())
+		child.entry.eb().id = s.entry.eb().id
 		childTypeID := TypeID(child.entry)
 		s.entrySchema.Children = append(s.Children, childTypeID)
 		if _, ok := graph.Get(childTypeID); ok {
@@ -274,15 +274,15 @@ func passAlongWrappedTypes(p Parent, child Entry) {
 	if root, ok := child.(HasWrappedTypes); ok {
 		wrappedTypes = root.WrappedTypes()
 	} else {
-		wrappedTypes = p.wrappedTypes()
+		wrappedTypes = p.eb().wrappedTypes
 	}
-	child.setWrappedTypes(wrappedTypes)
+	child.eb().wrappedTypes = wrappedTypes
 }
 
 // Helper that wraps the common code shared by the SetMeta*Schema methods
 func (s *EntrySchema) schemaOf(obj interface{}) (*JSONSchema, error) {
 	typeMappings := make(map[reflect.Type]*jsonschema.Type)
-	for t, s := range s.entry.wrappedTypes() {
+	for t, s := range s.entry.eb().wrappedTypes {
 		typeMappings[reflect.TypeOf(t)] = s.Type
 	}
 	r := jsonschema.Reflector{
@@ -314,7 +314,7 @@ func pluginName(e Entry) string {
 	// via something like "Schema(registry) => TypeID(Root)", where
 	// CachedList(registry) was not yet called. This can happen if, for
 	// example, the user starts the Wash shell and runs `stree`.
-	trimmedID := strings.Trim(e.id(), "/")
+	trimmedID := strings.Trim(e.eb().id, "/")
 	if trimmedID == "" {
 		switch e.(type) {
 		case Root:
diff --git a/plugin/externalPluginEntry.go b/plugin/externalPluginEntry.go
index 5ade6f4..0eef203 100644
--- a/plugin/externalPluginEntry.go
+++ b/plugin/externalPluginEntry.go
@@ -55,6 +55,7 @@ type decodedExternalPluginEntry struct {
 	CacheTTLs          decodedCacheTTLs `json:"cache_ttls"`
 	InaccessibleReason string           `json:"inaccessible_reason"`
 	Attributes         EntryAttributes  `json:"attributes"`
+	PartialMetadata    JSONObject       `json:"partial_metadata"`
 	State              string           `json:"state"`
 }
 
@@ -157,6 +158,7 @@ func (e decodedExternalPluginEntry) toExternalPluginEntry(ctx context.Context, s
 		rawTypeID:   e.TypeID,
 	}
 	entry.SetAttributes(e.Attributes)
+	entry.SetPartialMetadata(e.PartialMetadata)
 	entry.setCacheTTLs(e.CacheTTLs)
 	if e.InaccessibleReason != "" {
 		entry.MarkInaccessible(ctx, fmt.Errorf(e.InaccessibleReason))
@@ -688,8 +690,8 @@ func unmarshalSchemaGraph(e externalPlugin, stdout []byte) (*linkedhashmap.Map,
 		if isSignalable && len(node.Signals) <= 0 {
 			return fmt.Errorf("signalable entries must include their supported signals")
 		}
-		if node.MetaAttributeSchema != nil && node.MetaAttributeSchema.Type.Type != "object" {
-			return fmt.Errorf("invalid value for the meta attribute schema: expected a JSON object schema but got %v", node.MetaAttributeSchema.Type.Type)
+		if node.PartialMetadataSchema != nil && node.PartialMetadataSchema.Type.Type != "object" {
+			return fmt.Errorf("invalid value for the partial metadata schema: expected a JSON object schema but got %v", node.PartialMetadataSchema.Type.Type)
 		}
 		if node.MetadataSchema != nil && node.MetadataSchema.Type.Type != "object" {
 			return fmt.Errorf("invalid value for the metadata schema: expected a JSON object schema but got %v", node.MetadataSchema.Type.Type)
diff --git a/plugin/externalPluginScript.go b/plugin/externalPluginScript.go
index cf61add..a1dea37 100644
--- a/plugin/externalPluginScript.go
+++ b/plugin/externalPluginScript.go
@@ -113,6 +113,6 @@ func (s externalPluginScriptImpl) NewInvocation(
 	return &invocationImpl{Command: internal.NewCommand(
 		ctx,
 		s.Path(),
-		append([]string{method, entry.id(), entry.state}, args...)...,
+		append([]string{method, entry.eb().id, entry.state}, args...)...,
 	)}
 }
diff --git a/plugin/gcp/cloudFunction.go b/plugin/gcp/cloudFunction.go
index af10109..3bda5b0 100644
--- a/plugin/gcp/cloudFunction.go
+++ b/plugin/gcp/cloudFunction.go
@@ -31,9 +31,9 @@ func newCloudFunction(function *cloudfunctions.CloudFunction, service cloudFunct
 		panic(fmt.Sprintf("Timestamp for %v was not expected format RFC3339: %v", cf, function.UpdateTime))
 	}
 	cf.
+		SetPartialMetadata(function).
 		Attributes().
-		SetMtime(mtime).
-		SetMeta(function)
+		SetMtime(mtime)
 	return cf
 }
 
@@ -52,7 +52,7 @@ func (cf *cloudFunction) Delete(ctx context.Context) (bool, error) {
 
 func (cf *cloudFunction) Schema() *plugin.EntrySchema {
 	return plugin.NewEntrySchema(cf, "cloud_function").
-		SetMetaAttributeSchema(cloudfunctions.CloudFunction{})
+		SetPartialMetadataSchema(cloudfunctions.CloudFunction{})
 }
 
 func (cf *cloudFunction) ChildSchemas() []*plugin.EntrySchema {
diff --git a/plugin/gcp/cloudRunService.go b/plugin/gcp/cloudRunService.go
index 6c74d6a..38360ff 100644
--- a/plugin/gcp/cloudRunService.go
+++ b/plugin/gcp/cloudRunService.go
@@ -26,9 +26,9 @@ func newCloudRunService(service *run.Service, apiService cloudRunProjectAPIServi
 		panic(fmt.Sprintf("Timestamp for %v was not expected format RFC3339: %v", crs, service.Metadata.CreationTimestamp))
 	}
 	crs.
+		SetPartialMetadata(service).
 		Attributes().
-		SetCrtime(crtime).
-		SetMeta(service)
+		SetCrtime(crtime)
 	return crs
 }
 
@@ -52,7 +52,7 @@ func (crs *cloudRunService) Delete(ctx context.Context) (bool, error) {
 }
 func (crs *cloudRunService) Schema() *plugin.EntrySchema {
 	return plugin.NewEntrySchema(crs, "service").
-		SetMetaAttributeSchema(run.Service{})
+		SetPartialMetadataSchema(run.Service{})
 }
 
 func (crs *cloudRunService) ChildSchemas() []*plugin.EntrySchema {
diff --git a/plugin/gcp/computeInst.go b/plugin/gcp/computeInst.go
index 195eae7..8daa103 100644
--- a/plugin/gcp/computeInst.go
+++ b/plugin/gcp/computeInst.go
@@ -40,9 +40,9 @@ func newComputeInstance(inst *compute.Instance, c computeProjectService) *comput
 	}
 	comp.
 		DisableCachingFor(plugin.MetadataOp).
+		SetPartialMetadata(inst).
 		Attributes().
-		SetCrtime(crtime).
-		SetMeta(inst)
+		SetCrtime(crtime)
 	return comp
 }
 
@@ -70,7 +70,7 @@ func (c *computeInstance) Schema() *plugin.EntrySchema {
 	return plugin.
 		NewEntrySchema(c, "instance").
 		SetDescription(computeInstDescription).
-		SetMetaAttributeSchema(compute.Instance{}).
+		SetPartialMetadataSchema(compute.Instance{}).
 		AddSignal("start", "Starts the instance").
 		AddSignal("stop", "Stops the instance").
 		AddSignal("reset", "Resets the instance, similar to doing a hard-reset on your computer")
diff --git a/plugin/gcp/firestoreDocument.go b/plugin/gcp/firestoreDocument.go
index 1ebfdb0..9488d05 100644
--- a/plugin/gcp/firestoreDocument.go
+++ b/plugin/gcp/firestoreDocument.go
@@ -31,16 +31,17 @@ func newFirestoreDocument(client *firestore.Client, parent string, snapshot *fir
 		data:      snapshot.Data(),
 	}
 
-	doc.Attributes().
+	metadata := firestoreDocumentMetadata{
+		snapshot.CreateTime,
+		snapshot.UpdateTime,
+		snapshot.ReadTime,
+		snapshot.Data(),
+	}
+	doc.SetPartialMetadata(metadata).
+		Attributes().
 		SetCrtime(snapshot.CreateTime).
 		SetCtime(snapshot.UpdateTime).
-		SetMtime(snapshot.UpdateTime).
-		SetMeta(firestoreDocumentMetadata{
-			snapshot.CreateTime,
-			snapshot.UpdateTime,
-			snapshot.ReadTime,
-			snapshot.Data(),
-		})
+		SetMtime(snapshot.UpdateTime)
 
 	return doc
 }
@@ -65,7 +66,7 @@ func (doc *firestoreDocument) Delete(ctx context.Context) (bool, error) {
 
 func (doc *firestoreDocument) Schema() *plugin.EntrySchema {
 	return plugin.NewEntrySchema(doc, "document").
-		SetMetaAttributeSchema(firestoreDocumentMetadata{}).
+		SetPartialMetadataSchema(firestoreDocumentMetadata{}).
 		SetDescription(firestoreDocumentDescription)
 }
 
diff --git a/plugin/gcp/project.go b/plugin/gcp/project.go
index bf07ecb..adb7ed5 100644
--- a/plugin/gcp/project.go
+++ b/plugin/gcp/project.go
@@ -25,7 +25,7 @@ func newProject(p *crm.Project, client *http.Client) *project {
 		name = p.ProjectId
 	}
 	proj := &project{EntryBase: plugin.NewEntry(name), client: client, id: p.ProjectId}
-	proj.Attributes().SetMeta(p)
+	proj.SetPartialMetadata(p)
 	return proj
 }
 
@@ -73,7 +73,7 @@ func (p *project) Delete(ctx context.Context) (bool, error) {
 
 func (p *project) Schema() *plugin.EntrySchema {
 	schema := plugin.NewEntrySchema(p, "project")
-	schema.SetMetaAttributeSchema(crm.Project{})
+	schema.SetPartialMetadataSchema(crm.Project{})
 	schema.SetDescription(projectDescription)
 	return schema
 }
diff --git a/plugin/gcp/pubsubTopic.go b/plugin/gcp/pubsubTopic.go
index a4297cc..b24e445 100644
--- a/plugin/gcp/pubsubTopic.go
+++ b/plugin/gcp/pubsubTopic.go
@@ -20,12 +20,12 @@ type pubsubTopic struct {
 	topic  *pubsub.Topic
 }
 
-type pubsubTopicAttrMeta struct {
+type pubsubTopicPartialMeta struct {
 	PublishSettings pubsub.PublishSettings
 }
 
 type pubsubTopicMetadata struct {
-	pubsubTopicAttrMeta
+	pubsubTopicPartialMeta
 	TopicConfig   pubsub.TopicConfig
 	Subscriptions []string
 }
@@ -36,9 +36,7 @@ func newPubsubTopic(client *pubsub.Client, topic *pubsub.Topic) *pubsubTopic {
 		client:    client,
 		topic:     topic,
 	}
-	top.
-		Attributes().
-		SetMeta(pubsubTopicAttrMeta{PublishSettings: topic.PublishSettings})
+	top.SetPartialMetadata(pubsubTopicPartialMeta{PublishSettings: topic.PublishSettings})
 
 	// This may be somewhat hacky, but it ensures the goroutines for publishing get cleaned up eventually.
 	// Writeable can optionally also implement io.Closer to do cleanup when we're done writing,
@@ -67,9 +65,9 @@ func (t *pubsubTopic) Metadata(ctx context.Context) (plugin.JSONObject, error) {
 	}
 
 	return plugin.ToJSONObject(pubsubTopicMetadata{
-		pubsubTopicAttrMeta: pubsubTopicAttrMeta{PublishSettings: t.topic.PublishSettings},
-		TopicConfig:         cfg,
-		Subscriptions:       subs,
+		pubsubTopicPartialMeta: pubsubTopicPartialMeta{PublishSettings: t.topic.PublishSettings},
+		TopicConfig:            cfg,
+		Subscriptions:          subs,
 	}), nil
 }
 
@@ -150,7 +148,7 @@ func (t *pubsubTopic) Write(ctx context.Context, b []byte) error {
 
 func (t *pubsubTopic) Schema() *plugin.EntrySchema {
 	return plugin.NewEntrySchema(t, "topic").
-		SetMetaAttributeSchema(&pubsubTopicAttrMeta{}).
+		SetPartialMetadataSchema(&pubsubTopicPartialMeta{}).
 		SetMetadataSchema(&pubsubTopicMetadata{}).
 		SetDescription(pubsubTopicDescription)
 }
diff --git a/plugin/gcp/storageBucket.go b/plugin/gcp/storageBucket.go
index 93ec94b..4baee53 100644
--- a/plugin/gcp/storageBucket.go
+++ b/plugin/gcp/storageBucket.go
@@ -21,11 +21,11 @@ type storageBucket struct {
 
 func newStorageBucket(client storageProjectClient, bucket *storage.BucketAttrs) *storageBucket {
 	stor := &storageBucket{EntryBase: plugin.NewEntry(bucket.Name), storageProjectClient: client}
-	stor.Attributes().
+	stor.SetPartialMetadata(bucket).
+		Attributes().
 		SetCrtime(bucket.Created).
 		SetCtime(bucket.Created).
-		SetMtime(bucket.Created).
-		SetMeta(bucket)
+		SetMtime(bucket.Created)
 	return stor
 }
 
@@ -86,7 +86,7 @@ func (s *storageBucket) Delete(ctx context.Context) (bool, error) {
 
 func (s *storageBucket) Schema() *plugin.EntrySchema {
 	return plugin.NewEntrySchema(s, "bucket").
-		SetMetaAttributeSchema(storage.BucketAttrs{}).
+		SetPartialMetadataSchema(storage.BucketAttrs{}).
 		SetMetadataSchema(fullMeta{}).
 		SetDescription(storageBucketDescription)
 }
diff --git a/plugin/gcp/storageObject.go b/plugin/gcp/storageObject.go
index 85b4d05..5072245 100644
--- a/plugin/gcp/storageObject.go
+++ b/plugin/gcp/storageObject.go
@@ -16,12 +16,12 @@ type storageObject struct {
 
 func newStorageObject(name string, object *storage.ObjectHandle, attrs *storage.ObjectAttrs) *storageObject {
 	obj := &storageObject{EntryBase: plugin.NewEntry(name), ObjectHandle: object}
-	obj.Attributes().
+	obj.SetPartialMetadata(attrs).
+		Attributes().
 		SetCrtime(attrs.Created).
 		SetCtime(attrs.Updated).
 		SetMtime(attrs.Updated).
-		SetSize(uint64(attrs.Size)).
-		SetMeta(attrs)
+		SetSize(uint64(attrs.Size))
 	return obj
 }
 
@@ -29,7 +29,7 @@ func (s *storageObject) Schema() *plugin.EntrySchema {
 	return plugin.
 		NewEntrySchema(s, "object").
 		SetDescription(storageObjectDescription).
-		SetMetaAttributeSchema(storage.ObjectAttrs{})
+		SetPartialMetadataSchema(storage.ObjectAttrs{})
 }
 
 func (s *storageObject) Read(ctx context.Context, size int64, offset int64) ([]byte, error) {
diff --git a/plugin/gcp/storageObjectPrefix.go b/plugin/gcp/storageObjectPrefix.go
index bbb6377..039ff6f 100644
--- a/plugin/gcp/storageObjectPrefix.go
+++ b/plugin/gcp/storageObjectPrefix.go
@@ -23,12 +23,12 @@ func newStorageObjectPrefix(bucket *storage.BucketHandle,
 		prefix:    prefix,
 	}
 	if attrs != nil {
-		pre.Attributes().
+		pre.SetPartialMetadata(attrs).
+			Attributes().
 			SetCrtime(attrs.Created).
 			SetCtime(attrs.Updated).
 			SetMtime(attrs.Updated).
-			SetSize(uint64(attrs.Size)).
-			SetMeta(attrs)
+			SetSize(uint64(attrs.Size))
 	}
 	return pre
 }
@@ -46,7 +46,7 @@ func (s *storageObjectPrefix) Delete(ctx context.Context) (bool, error) {
 func (s *storageObjectPrefix) Schema() *plugin.EntrySchema {
 	return plugin.NewEntrySchema(s, "prefix").
 		SetDescription(storageObjectPrefixDescription).
-		SetMetaAttributeSchema(storage.ObjectAttrs{})
+		SetPartialMetadataSchema(storage.ObjectAttrs{})
 }
 
 func (s *storageObjectPrefix) ChildSchemas() []*plugin.EntrySchema {
diff --git a/plugin/kubernetes/container.go b/plugin/kubernetes/container.go
index c4d5446..2c555a7 100644
--- a/plugin/kubernetes/container.go
+++ b/plugin/kubernetes/container.go
@@ -47,8 +47,7 @@ func newContainer(ctx context.Context, client *k8s.Clientset, config *rest.Confi
 	}
 
 	cntnr.
-		Attributes().
-		SetMeta(plugin.ToJSONObject(c))
+		SetPartialMetadata(c)
 
 	return cntnr, nil
 }
@@ -56,7 +55,7 @@ func newContainer(ctx context.Context, client *k8s.Clientset, config *rest.Confi
 func (c *container) Schema() *plugin.EntrySchema {
 	return plugin.
 		NewEntrySchema(c, "container").
-		SetMetaAttributeSchema(corev1.Container{})
+		SetPartialMetadataSchema(corev1.Container{})
 }
 
 func (c *container) Read(ctx context.Context) ([]byte, error) {
diff --git a/plugin/kubernetes/namespace.go b/plugin/kubernetes/namespace.go
index c301a5b..e54a02d 100644
--- a/plugin/kubernetes/namespace.go
+++ b/plugin/kubernetes/namespace.go
@@ -28,14 +28,14 @@ func newNamespace(name string, meta *corev1.Namespace, c *k8s.Clientset, cfg *re
 		newPVCSDir(ns),
 	}
 	// TODO: Figure out other attributes that we could set here, if any.
-	ns.Attributes().SetMeta(meta)
+	ns.SetPartialMetadata(meta)
 	return ns
 }
 
 func (n *namespace) Schema() *plugin.EntrySchema {
 	return plugin.
 		NewEntrySchema(n, "namespace").
-		SetMetaAttributeSchema(corev1.Namespace{}).
+		SetPartialMetadataSchema(corev1.Namespace{}).
 		SetDescription(namespaceDescription)
 }
 
diff --git a/plugin/kubernetes/pod.go b/plugin/kubernetes/pod.go
index 5d65a8b..6e326d9 100644
--- a/plugin/kubernetes/pod.go
+++ b/plugin/kubernetes/pod.go
@@ -26,10 +26,10 @@ func newPod(ctx context.Context, client *k8s.Clientset, config *rest.Config, ns
 	pd.ns = ns
 
 	pd.
+		SetPartialMetadata(p).
 		Attributes().
 		SetCrtime(p.CreationTimestamp.Time).
-		SetAtime(p.CreationTimestamp.Time).
-		SetMeta(plugin.ToJSONObject(p))
+		SetAtime(p.CreationTimestamp.Time)
 
 	return pd, nil
 }
@@ -37,7 +37,7 @@ func newPod(ctx context.Context, client *k8s.Clientset, config *rest.Config, ns
 func (p *pod) Schema() *plugin.EntrySchema {
 	return plugin.
 		NewEntrySchema(p, "pod").
-		SetMetaAttributeSchema(corev1.Pod{})
+		SetPartialMetadataSchema(corev1.Pod{})
 }
 
 func (p *pod) ChildSchemas() []*plugin.EntrySchema {
diff --git a/plugin/kubernetes/pvc.go b/plugin/kubernetes/pvc.go
index 2b48419..8d38f1b 100644
--- a/plugin/kubernetes/pvc.go
+++ b/plugin/kubernetes/pvc.go
@@ -38,12 +38,12 @@ func newPVC(pi typedv1.PersistentVolumeClaimInterface, pd typedv1.PodInterface,
 
 	vol.SetTTLOf(plugin.ListOp, volume.ListTTL)
 	vol.
+		SetPartialMetadata(p).
 		Attributes().
 		SetCrtime(p.CreationTimestamp.Time).
 		SetMtime(p.CreationTimestamp.Time).
 		SetCtime(p.CreationTimestamp.Time).
-		SetAtime(p.CreationTimestamp.Time).
-		SetMeta(p)
+		SetAtime(p.CreationTimestamp.Time)
 
 	return vol
 }
@@ -52,7 +52,7 @@ func (v *pvc) Schema() *plugin.EntrySchema {
 	return plugin.
 		NewEntrySchema(v, "persistentvolumeclaim").
 		SetDescription(pvcDescription).
-		SetMetaAttributeSchema(corev1.PersistentVolumeClaim{})
+		SetPartialMetadataSchema(corev1.PersistentVolumeClaim{})
 }
 
 func (v *pvc) ChildSchemas() []*plugin.EntrySchema {
diff --git a/plugin/methodWrappers.go b/plugin/methodWrappers.go
index 672fffc..08c29b9 100644
--- a/plugin/methodWrappers.go
+++ b/plugin/methodWrappers.go
@@ -45,7 +45,7 @@ func Name(e Entry) string {
 	// interface is so plugin authors don't override it. It ensures
 	// that whatever name they pass into plugin.NewEntry is the
 	// name received by Wash.
-	return e.name()
+	return e.eb().name
 }
 
 /*
@@ -65,16 +65,16 @@ e.SetSlashReplacer(<char>) to change the default slash replacer from
 a '#' to <char>.
 */
 func CName(e Entry) string {
-	if len(e.name()) == 0 {
-		panic("plugin.CName: e.name() is empty")
+	if len(e.eb().name) == 0 {
+		panic("plugin.CName: e.eb().name is empty")
 	}
 	// We make the CName a separate function instead of embedding it
 	// in the Entry interface because doing so prevents plugin authors
 	// from overriding it.
 	return strings.Replace(
-		e.name(),
+		e.eb().name,
 		"/",
-		string(e.slashReplacer()),
+		string(e.eb().slashReplacer),
 		-1,
 	)
 }
@@ -86,22 +86,22 @@ func CName(e Entry) string {
 // NOTE: <plugin_name> is really <plugin_cname>. However since <plugin_name>
 // can never contain a '/', <plugin_cname> reduces to <plugin_name>.
 func ID(e Entry) string {
-	if e.id() == "" {
-		msg := fmt.Sprintf("plugin.ID: entry %v (cname %v) has no ID", e.name(), CName(e))
+	if e.eb().id == "" {
+		msg := fmt.Sprintf("plugin.ID: entry %v (cname %v) has no ID", e.eb().name, CName(e))
 		panic(msg)
 	}
-	return e.id()
+	return e.eb().id
 }
 
 // Attributes returns the entry's attributes.
 func Attributes(e Entry) EntryAttributes {
-	return e.attributes()
+	return e.eb().attributes
 }
 
 // IsPrefetched returns whether an entry has data that was added during creation that it would
 // like to have updated.
 func IsPrefetched(e Entry) bool {
-	return e.isPrefetched()
+	return e.eb().isPrefetched
 }
 
 // Schema returns the entry's schema.
@@ -137,7 +137,7 @@ func Read(ctx context.Context, e Entry, size int64, offset int64) (data []byte,
 	}
 	data = []byte{}
 	inputSize := size
-	if attr := e.attributes(); attr.HasSize() {
+	if attr := e.eb().attributes; attr.HasSize() {
 		contentSize := int64(attr.Size())
 		if offset >= contentSize {
 			err = io.EOF
@@ -149,7 +149,7 @@ func Read(ctx context.Context, e Entry, size int64, offset int64) (data []byte,
 			err = io.EOF
 		}
 	} else if ReadAction().signature(e) == blockReadableSignature {
-		activity.Warnf(ctx, "size attribute not set for block-readable entry %v", e.id())
+		activity.Warnf(ctx, "size attribute not set for block-readable entry %v", e.eb().id)
 	}
 	content, contentErr := cachedRead(ctx, e)
 	if contentErr != nil {
@@ -168,7 +168,7 @@ func Read(ctx context.Context, e Entry, size int64, offset int64) (data []byte,
 
 // Size returns the size of readable data for an entry. It may call Read to do so.
 func Size(ctx context.Context, e Entry) (uint64, error) {
-	if attr := e.attributes(); attr.HasSize() {
+	if attr := e.eb().attributes; attr.HasSize() {
 		return attr.Size(), nil
 	}
 
@@ -183,6 +183,12 @@ func Size(ctx context.Context, e Entry) (uint64, error) {
 	return data.size(), nil
 }
 
+// PartialMetadata returns the entry's partial metadata, a subset of the entry's
+// metadata that is typically provided by the plugin API's List endpoint.
+func PartialMetadata(e Entry) JSONObject {
+	return e.eb().partialMetadata()
+}
+
 // Metadata returns the entry's metadata. Note that Metadata's results could be cached.
 func Metadata(ctx context.Context, e Entry) (JSONObject, error) {
 	return cachedMetadata(ctx, e)
@@ -249,7 +255,7 @@ func Signal(ctx context.Context, s Signalable, signal string) error {
 
 	// The signal was successfully sent. Clear the entry's cache and its parent's
 	// cached list result to ensure that fresh data's loaded when needed
-	ClearCacheFor(s.id(), true)
+	ClearCacheFor(s.eb().id, true)
 	return nil
 }
 
@@ -268,11 +274,11 @@ func Delete(ctx context.Context, d Deletable) (deleted bool, err error) {
 	// If !deleted, the entry will eventually be deleted. However it's likely that
 	// Delete did update the entry (e.g. on VMs, Delete causes a state transition).
 	// Thus we also clear the parent's cached list result to ensure fresh data.
-	ClearCacheFor(d.id(), !deleted)
+	ClearCacheFor(d.eb().id, !deleted)
 	if deleted {
 		// The entry was deleted, so delete the entry from the parent's cached list
 		// result.
-		parentID, cname := splitID(d.id())
+		parentID, cname := splitID(d.eb().id)
 		listOpName := defaultOpCodeToNameMap[ListOp]
 		entries, _ := cache.Get(listOpName, parentID)
 		if entries != nil {
diff --git a/plugin/registry.go b/plugin/registry.go
index 8cfa76d..93e600d 100644
--- a/plugin/registry.go
+++ b/plugin/registry.go
@@ -21,7 +21,7 @@ func NewRegistry() *Registry {
 		EntryBase: NewEntry("/"),
 		plugins:   make(map[string]Root),
 	}
-	r.setID("/")
+	r.eb().id = "/"
 	r.DisableDefaultCaching()
 
 	return r
@@ -41,23 +41,23 @@ func (r *Registry) RegisterPlugin(root Root, config map[string]interface{}) erro
 	registerPlugin := func(initSucceeded bool) {
 		r.mux.Lock()
 		if initSucceeded {
-			if !pluginNameRegex.MatchString(root.name()) {
-				msg := fmt.Sprintf("r.RegisterPlugin: invalid plugin name %v. The plugin name must consist of alphanumeric characters, or a hyphen", root.name())
+			if !pluginNameRegex.MatchString(root.eb().name) {
+				msg := fmt.Sprintf("r.RegisterPlugin: invalid plugin name %v. The plugin name must consist of alphanumeric characters, or a hyphen", root.eb().name)
 				panic(msg)
 			}
 
-			if _, ok := r.plugins[root.name()]; ok {
-				msg := fmt.Sprintf("r.RegisterPlugin: the %v plugin's already been registered", root.name())
+			if _, ok := r.plugins[root.eb().name]; ok {
+				msg := fmt.Sprintf("r.RegisterPlugin: the %v plugin's already been registered", root.eb().name)
 				panic(msg)
 			}
 
 			if DeleteAction().IsSupportedOn(root) {
-				msg := fmt.Sprintf("r.RegisterPlugin: the %v plugin's root implements delete", root.name())
+				msg := fmt.Sprintf("r.RegisterPlugin: the %v plugin's root implements delete", root.eb().name)
 				panic(msg)
 			}
 		}
 
-		r.plugins[root.name()] = root
+		r.plugins[root.eb().name] = root
 		r.pluginRoots = append(r.pluginRoots, root)
 		r.mux.Unlock()
 	}
diff --git a/plugin/types.go b/plugin/types.go
index 9c0aa1e..a6b92cd 100644
--- a/plugin/types.go
+++ b/plugin/types.go
@@ -53,16 +53,8 @@ import (
 type Entry interface {
 	Metadata(ctx context.Context) (JSONObject, error)
 	Schema() *EntrySchema
-	name() string
-	attributes() EntryAttributes
-	slashReplacer() rune
-	id() string
-	setID(id string)
-	getTTLOf(op defaultOpCode) time.Duration
-	wrappedTypes() map[interface{}]*JSONSchema
-	setWrappedTypes(map[interface{}]*JSONSchema)
-	isPrefetched() bool
-	isInaccessible() bool
+	// eb => entryBase
+	eb() *EntryBase
 }
 
 /*


Test patch:
diff --git a/cmd/docs_test.go b/cmd/docs_test.go
index a3973d0..2536e5e 100644
--- a/cmd/docs_test.go
+++ b/cmd/docs_test.go
@@ -37,7 +37,7 @@ func (suite *DocsTestSuite) TestStringifySupportedAttributes() {
 	suite.Regexp(fmt.Sprintf("crtime.*creation time.*%v", toRegexStr(entry.Attributes.Crtime())), supportedAttributes)
 	suite.Regexp(fmt.Sprintf("size.*%v", entry.Attributes.Size()), supportedAttributes)
 	suite.Regexp("meta foo", supportedAttributes)
-	suite.Regexp("meta --attribute foo", supportedAttributes)
+	suite.Regexp("meta --partial foo", supportedAttributes)
 	suite.Regexp("find --help", supportedAttributes)
 }
 
diff --git a/cmd/internal/find/types/entrySchema_test.go b/cmd/internal/find/types/entrySchema_test.go
index e4c1db6..ff13d3e 100644
--- a/cmd/internal/find/types/entrySchema_test.go
+++ b/cmd/internal/find/types/entrySchema_test.go
@@ -155,7 +155,7 @@ func (suite *EntrySchemaTestSuite) TestEvaluateNodesToKeep_Complex() {
 	suite.assertKeepResult(result, false, "A/B/E/C", "A/B/E/C/F", "A/C", "A/C/F")
 }
 
-func (suite *EntrySchemaTestSuite) TestEvaluateNodesToKeep_MetadataSchema_DefaultsToMetaAttributeSchema() {
+func (suite *EntrySchemaTestSuite) TestEvaluateNodesToKeep_MetadataSchema_DefaultsToPartialMetadataSchema() {
 	s := suite.readFixture("metadata")
 	p := suite.makeMetadataSchemaP("A", "object", "foo")
 	result := evaluateNodesToKeep(s, p, NewOptions())
@@ -163,7 +163,7 @@ func (suite *EntrySchemaTestSuite) TestEvaluateNodesToKeep_MetadataSchema_Defaul
 	suite.assertKeepResult(result, false, "A/B", "A/C")
 }
 
-func (suite *EntrySchemaTestSuite) TestEvaluateNodesToKeep_MetadataSchema_FullmetaSet_NilMetadataSchema_FallsbackToMetaAttributeSchema() {
+func (suite *EntrySchemaTestSuite) TestEvaluateNodesToKeep_MetadataSchema_FullmetaSet_NilMetadataSchema_FallsbackToPartialMetadataSchema() {
 	opts := NewOptions()
 	opts.Fullmeta = true
 	s := suite.readFixture("metadata")
diff --git a/cmd/internal/find/types/testdata/metadata.json b/cmd/internal/find/types/testdata/metadata.json
index ce2b6f7..23e14da 100644
--- a/cmd/internal/find/types/testdata/metadata.json
+++ b/cmd/internal/find/types/testdata/metadata.json
@@ -1,7 +1,7 @@
 {
     "A": {
         "label": "A",
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "type": "object",
             "properties": {
                 "foo": {}
diff --git a/cmd/internal/find/types/testdata/real_world_aws.json b/cmd/internal/find/types/testdata/real_world_aws.json
index 79a1311..98f2f88 100644
--- a/cmd/internal/find/types/testdata/real_world_aws.json
+++ b/cmd/internal/find/types/testdata/real_world_aws.json
@@ -5,7 +5,7 @@
         "actions": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -27,7 +27,7 @@
         "actions": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -49,7 +49,7 @@
         "actions": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -72,7 +72,7 @@
         "actions": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -94,7 +94,7 @@
         "actions": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -160,7 +160,7 @@
         "actions": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -184,7 +184,7 @@
             "read",
             "stream"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "required": [
                 "ETag",
@@ -373,7 +373,7 @@
         "actions": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -395,7 +395,7 @@
         "actions": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -418,7 +418,7 @@
             "list",
             "exec"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "required": [
                 "AmiLaunchIndex",
@@ -1143,7 +1143,7 @@
         "actions": [
             "read"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -1163,7 +1163,7 @@
         "actions": [
             "read"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -1183,7 +1183,7 @@
         "actions": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -1206,7 +1206,7 @@
         "actions": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -1230,7 +1230,7 @@
             "read",
             "stream"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
diff --git a/plugin/cache_test.go b/plugin/cache_test.go
index 9435bb9..18f8bdb 100644
--- a/plugin/cache_test.go
+++ b/plugin/cache_test.go
@@ -225,12 +225,12 @@ func (suite *CacheTestSuite) TestCachedOp() {
 	opTTL := 5 * time.Second
 	op := func() (interface{}, error) { return "result", nil }
 	generateValueMatcher := suite.makeGenerateValueMatcher("result")
-	suite.cache.On("GetOrUpdate", opName, entry.id(), opTTL, false, mock.MatchedBy(generateValueMatcher)).Return("result", nil).Once()
+	suite.cache.On("GetOrUpdate", opName, entry.eb().id, opTTL, false, mock.MatchedBy(generateValueMatcher)).Return("result", nil).Once()
 	v, err := CachedOp(context.Background(), opName, entry, opTTL, op)
 	if suite.NoError(err) {
 		suite.Equal("result", v)
 	}
-	suite.cache.AssertCalled(suite.T(), "GetOrUpdate", opName, entry.id(), opTTL, false, mock.MatchedBy(generateValueMatcher))
+	suite.cache.AssertCalled(suite.T(), "GetOrUpdate", opName, entry.eb().id, opTTL, false, mock.MatchedBy(generateValueMatcher))
 }
 
 func (suite *CacheTestSuite) TestDuplicateCNameErr() {
@@ -290,12 +290,12 @@ func (suite *CacheTestSuite) testCachedDefaultOp(
 	entry.SetTTLOf(op, opTTL)
 	entry.On(opName, mock.Anything).Return(opValue, nil)
 	generateValueMatcher := suite.makeGenerateValueMatcher(mungedOpValue)
-	suite.cache.On("GetOrUpdate", opName, entry.id(), opTTL, false, mock.MatchedBy(generateValueMatcher)).Return(mungedOpValue, nil).Once()
+	suite.cache.On("GetOrUpdate", opName, entry.eb().id, opTTL, false, mock.MatchedBy(generateValueMatcher)).Return(mungedOpValue, nil).Once()
 	v, err = cachedDefaultOp(ctx, entry)
 	if suite.NoError(err) {
 		suite.Equal(mungedOpValue, v)
 	}
-	suite.cache.AssertCalled(suite.T(), "GetOrUpdate", opName, entry.id(), opTTL, false, mock.MatchedBy(generateValueMatcher))
+	suite.cache.AssertCalled(suite.T(), "GetOrUpdate", opName, entry.eb().id, opTTL, false, mock.MatchedBy(generateValueMatcher))
 }
 
 func toMap(children []Entry) map[string]Entry {
@@ -360,8 +360,8 @@ func (suite *CacheTestSuite) TestCachedListSetEntryID() {
 	children, err := cachedList(ctx, entry)
 	if suite.NoError(err) {
 		if suite.Equal(toMap(mockChildren), children.mp) {
-			suite.Equal("/foo#child1", children.mp["foo#child1"].id())
-			suite.Equal("/child2", children.mp["child2"].id())
+			suite.Equal("/foo#child1", children.mp["foo#child1"].eb().id)
+			suite.Equal("/child2", children.mp["child2"].eb().id)
 		}
 	}
 
@@ -373,8 +373,8 @@ func (suite *CacheTestSuite) TestCachedListSetEntryID() {
 	children, err = cachedList(ctx, entry)
 	if suite.NoError(err) {
 		if suite.Equal(toMap(mockChildren), children.mp) {
-			suite.Equal("/parent/foo#child1", children.mp["foo#child1"].id())
-			suite.Equal("/parent/child2", children.mp["child2"].id())
+			suite.Equal("/parent/foo#child1", children.mp["foo#child1"].eb().id)
+			suite.Equal("/parent/child2", children.mp["child2"].eb().id)
 		}
 	}
 }
diff --git a/plugin/entryAttributes_test.go b/plugin/entryAttributes_test.go
index c249ddb..65a193d 100644
--- a/plugin/entryAttributes_test.go
+++ b/plugin/entryAttributes_test.go
@@ -48,7 +48,6 @@ func (suite *EntryAttributesTestSuite) TestEntryAttributes() {
 		return unmarshaledTime
 	}
 	attr := EntryAttributes{}
-	attr.meta = JSONObject{}
 	doUnmarshalJSONTests := func() {
 		attrJSON, err := json.Marshal(attr)
 		suite.NoError(err)
@@ -61,81 +60,71 @@ func (suite *EntryAttributesTestSuite) TestEntryAttributes() {
 
 	// ToMap - used for listing attributes - and JSON marshaling may have different representations.
 	expectedMp := make(map[string]interface{})
-	expectedMp["meta"] = JSONObject{}
 
 	// Tests for Atime
 	suite.Equal(false, attr.HasAtime())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	t := timeNow()
 	attr.SetAtime(t)
 	expectedMp["atime"] = t
 	suite.Equal(t, attr.Atime())
 	suite.Equal(true, attr.HasAtime())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	doUnmarshalJSONTests()
 
 	// Tests for Mtime
 	suite.Equal(false, attr.HasMtime())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	t = timeNow()
 	attr.SetMtime(t)
 	expectedMp["mtime"] = t
 	suite.Equal(t, attr.Mtime())
 	suite.Equal(true, attr.HasMtime())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	doUnmarshalJSONTests()
 
 	// Tests for Ctime
 	suite.Equal(false, attr.HasCtime())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	t = timeNow()
 	attr.SetCtime(t)
 	expectedMp["ctime"] = t
 	suite.Equal(t, attr.Ctime())
 	suite.Equal(true, attr.HasCtime())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	doUnmarshalJSONTests()
 
 	// Tests for Crtime
 	suite.Equal(false, attr.HasCrtime())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	t = timeNow()
 	attr.SetCrtime(t)
 	expectedMp["crtime"] = t
 	suite.Equal(t, attr.Crtime())
 	suite.Equal(true, attr.HasCrtime())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	doUnmarshalJSONTests()
 
 	// Tests for Mode
 	suite.Equal(false, attr.HasMode())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	m := os.FileMode(0777 | os.ModeCharDevice | os.ModeDir)
 	attr.SetMode(m)
 	expectedMp["mode"] = m.String()
 	suite.Equal(m, attr.Mode())
 	suite.Equal(true, attr.HasMode())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	doUnmarshalJSONTests()
 
 	// Tests for Size
 	suite.Equal(false, attr.HasSize())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	sz := uint64(10)
 	attr.SetSize(sz)
 	expectedMp["size"] = sz
 	suite.Equal(sz, attr.Size())
 	suite.Equal(true, attr.HasSize())
-	suite.Equal(expectedMp, attr.ToMap(true))
-	doUnmarshalJSONTests()
-
-	// Tests for Meta
-	suite.Equal(JSONObject{}, attr.Meta())
-	meta := JSONObject{"foo": "bar"}
-	attr.SetMeta(meta)
-	expectedMp["meta"] = meta
-	suite.Equal(meta, attr.Meta())
-	suite.Equal(expectedMp, attr.ToMap(true))
+	suite.Equal(expectedMp, attr.ToMap())
 	doUnmarshalJSONTests()
 }
 
diff --git a/plugin/entryBase_test.go b/plugin/entryBase_test.go
index 7349c41..84ce188 100644
--- a/plugin/entryBase_test.go
+++ b/plugin/entryBase_test.go
@@ -13,7 +13,7 @@ type EntryBaseTestSuite struct {
 }
 
 func (suite *EntryBaseTestSuite) assertOpTTL(e EntryBase, op defaultOpCode, opName string, expectedTTL time.Duration) {
-	actualTTL := e.getTTLOf(op)
+	actualTTL := e.ttl[op]
 	suite.Equal(
 		expectedTTL,
 		actualTTL,
@@ -35,16 +35,13 @@ func (suite *EntryBaseTestSuite) TestNewEntry() {
 	e := NewEntry("foo")
 
 	e.SetAttributes(initialAttr)
-	suite.Equal(initialAttr, e.attr)
+	suite.Equal(initialAttr, e.attributes)
 
 	suite.Equal("foo", e.Name())
 	suite.assertOpTTL(e, ListOp, "List", 15*time.Second)
 	suite.assertOpTTL(e, ReadOp, "Read", 15*time.Second)
 	suite.assertOpTTL(e, MetadataOp, "Metadata", 15*time.Second)
 
-	e.setID("/foo")
-	suite.Equal("/foo", e.id())
-
 	e.SetTTLOf(ListOp, 40*time.Second)
 	suite.assertOpTTL(e, ListOp, "List", 40*time.Second)
 
@@ -75,7 +72,7 @@ func (suite *EntryBaseTestSuite) TestSetSlashReplacer() {
 	)
 
 	e.SetSlashReplacer(':')
-	suite.Equal(e.slashReplacer(), ':')
+	suite.Equal(e.slashReplacer, ':')
 }
 
 func TestEntryBase(t *testing.T) {
diff --git a/plugin/externalPluginEntry_test.go b/plugin/externalPluginEntry_test.go
index f09035a..e6aec33 100644
--- a/plugin/externalPluginEntry_test.go
+++ b/plugin/externalPluginEntry_test.go
@@ -76,7 +76,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryRequired
 
 	entry, err := decodedEntry.toExternalPluginEntry(context.Background(), false, false)
 	if suite.NoError(err) {
-		suite.Equal(decodedEntry.Name, entry.name())
+		suite.Equal(decodedEntry.Name, entry.name)
 		suite.Equal(1, len(entry.methods))
 		suite.Contains(entry.methods, "list")
 		suite.Nil(entry.methods["list"].result)
@@ -91,14 +91,14 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryExtraFie
 
 	entry, err := decodedEntry.toExternalPluginEntry(context.Background(), false, false)
 	if suite.NoError(err) {
-		suite.Equal(decodedEntry.Name, entry.name())
+		suite.Equal(decodedEntry.Name, entry.name)
 		suite.Contains(entry.methods, "list")
 		suite.Equal(defaultSignature, entry.methods["list"].signature)
 		suite.Nil(entry.methods["list"].result)
 		suite.Contains(entry.methods, "stream")
 		suite.Equal(defaultSignature, entry.methods["stream"].signature)
 		suite.Nil(entry.methods["stream"].result)
-		suite.False(entry.isPrefetched())
+		suite.False(entry.isPrefetched)
 
 		methods := entry.supportedMethods()
 		suite.Equal(2, len(methods))
@@ -115,7 +115,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntry_Support
 
 	entry, err := decodedEntry.toExternalPluginEntry(context.Background(), false, false)
 	if suite.NoError(err) {
-		suite.Equal(decodedEntry.Name, entry.name())
+		suite.Equal(decodedEntry.Name, entry.name)
 	}
 }
 
@@ -128,13 +128,13 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithMeth
 
 	entry, err := decodedEntry.toExternalPluginEntry(context.Background(), false, false)
 	if suite.NoError(err) {
-		suite.Equal(decodedEntry.Name, entry.name())
+		suite.Equal(decodedEntry.Name, entry.name)
 		suite.Contains(entry.methods, "list")
 		suite.Equal(defaultSignature, entry.methods["list"].signature)
 		suite.NotNil(entry.methods["list"].result)
 		suite.Contains(entry.methods, "read")
 		suite.Nil(entry.methods["read"].result)
-		suite.True(entry.isPrefetched())
+		suite.True(entry.isPrefetched)
 	}
 }
 
@@ -202,7 +202,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithSlas
 	decodedEntry.SlashReplacer = ":"
 	entry, err := decodedEntry.toExternalPluginEntry(context.Background(), false, false)
 	if suite.NoError(err) {
-		suite.Equal(':', entry.slashReplacer())
+		suite.Equal(':', entry.slashReplacer)
 	}
 }
 
@@ -214,7 +214,16 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithAttr
 	if suite.NoError(err) {
 		expectedAttr := EntryAttributes{}
 		expectedAttr.SetCtime(t)
-		suite.Equal(expectedAttr, entry.attr)
+		suite.Equal(expectedAttr, entry.attributes)
+	}
+}
+
+func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithPartialMetadata() {
+	decodedEntry := newMockDecodedEntry("name")
+	decodedEntry.PartialMetadata = JSONObject{}
+	entry, err := decodedEntry.toExternalPluginEntry(context.Background(), false, false)
+	if suite.NoError(err) {
+		suite.Equal(JSONObject{}, entry.partialMetadata())
 	}
 }
 
@@ -302,7 +311,7 @@ func (suite *ExternalPluginEntryTestSuite) TestDecodeExternalPluginEntryWithInac
 
 	entry, err := decodedEntry.toExternalPluginEntry(context.Background(), false, false)
 	if suite.NoError(err) {
-		suite.True(entry.isInaccessible())
+		suite.True(entry.isInaccessible)
 	}
 }
 
@@ -318,9 +327,9 @@ func (suite *ExternalPluginEntryTestSuite) TestSetCacheTTLs() {
 	}
 	entry.setCacheTTLs(decodedTTLs)
 
-	suite.Equal(decodedTTLs.List*time.Second, entry.getTTLOf(ListOp))
-	suite.Equal(decodedTTLs.Read*time.Second, entry.getTTLOf(ReadOp))
-	suite.Equal(decodedTTLs.Metadata*time.Second, entry.getTTLOf(MetadataOp))
+	suite.Equal(decodedTTLs.List*time.Second, entry.ttl[ListOp])
+	suite.Equal(decodedTTLs.Read*time.Second, entry.ttl[ReadOp])
+	suite.Equal(decodedTTLs.Metadata*time.Second, entry.ttl[MetadataOp])
 }
 
 func mockInvocation(stdout []byte) invocation {
@@ -466,7 +475,7 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_NotPrefetched_SuccessfulIn
 		"methods": ["list"],
 		"children": ["baz.barTypeID"],
 		"singleton": true,
-		"meta_attribute_schema": {
+		"partial_metadata_schema": {
 			"type": "object"
 		},
 		"metadata_schema": null
@@ -476,7 +485,7 @@ func (suite *ExternalPluginEntryTestSuite) TestSchema_NotPrefetched_SuccessfulIn
 		"methods": ["list"],
 		"children": ["baz.barTypeID"],
 		"singleton": false,
-		"meta_attribute_schema": {
+		"partial_metadata_schema": {
 			"type": "object",
 			"properties": {
 				"foo": {
@@ -750,10 +759,10 @@ func (suite *ExternalPluginEntryTestSuite) TestMetadata_NotImplemented() {
 		script:    mockScript,
 	}
 	expectedMeta := JSONObject{"foo": "bar"}
-	entry.Attributes().SetMeta(expectedMeta)
+	entry.SetPartialMetadata(expectedMeta)
 
 	// If metadata is not implemented, then e.Metadata should return
-	// EntryBase#Metadata, which returns the meta attribute.
+	// EntryBase#Metadata, which returns the partial metadata.
 	meta, err := entry.Metadata(context.Background())
 	if suite.NoError(err) {
 		suite.Equal(expectedMeta, meta)
@@ -1093,7 +1102,7 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsIfSche
 	suite.Regexp("associated", err)
 }
 
-func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsOnInvalidMetaAttributeSchema() {
+func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsOnInvalidPartialMetadataSchema() {
 	entry := &externalPluginEntry{
 		rawTypeID: "foo",
 	}
@@ -1104,14 +1113,14 @@ func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsOnInva
 	"foo":{
 		"label": "fooLabel",
 		"methods": ["read"],
-		"meta_attribute_schema": {
+		"partial_metadata_schema": {
 			"type": "array"
 		}
 	}
 }
 `)
 	_, err := unmarshalSchemaGraph(entry, stdout)
-	suite.Regexp("invalid.*meta.*attribute.*object.*schema.*array", err)
+	suite.Regexp("invalid.*partial.*metadata.*object.*schema.*array", err)
 }
 
 func (suite *ExternalPluginEntryTestSuite) TestUnmarshalSchemaGraph_ErrorsOnInvalidMetadataSchema() {
diff --git a/plugin/externalPluginRoot_test.go b/plugin/externalPluginRoot_test.go
index a6cbde7..8e24051 100644
--- a/plugin/externalPluginRoot_test.go
+++ b/plugin/externalPluginRoot_test.go
@@ -160,7 +160,7 @@ func (suite *ExternalPluginRootTestSuite) TestInitWithSchema_PrefetchedSchema_Pa
 	if err := root.Init(nil); suite.NoError(err) && suite.NotNil(root.schemaGraphs) {
 		// Ensure that the graph of root.schemaGraphs[type_id] has "type_id" as its
 		// first item. We pick an arbitrary type ID here
-		typeID := namespace(root.name(), "aws.profile")
+		typeID := namespace(root.eb().name, "aws.profile")
 		graph := root.schemaGraphs[typeID]
 		if suite.NotNil(graph) {
 			it := graph.Iterator()
diff --git a/plugin/externalPluginSpec_test.go b/plugin/externalPluginSpec_test.go
index 175ffb0..beffc6f 100644
--- a/plugin/externalPluginSpec_test.go
+++ b/plugin/externalPluginSpec_test.go
@@ -10,7 +10,7 @@ func TestLoadExternalPlugin(t *testing.T) {
 	spec := ExternalPluginSpec{Script: "testdata/external.sh"}
 	root, err := spec.Load()
 	assert.NoError(t, err)
-	assert.Equal(t, "external", root.name())
+	assert.Equal(t, "external", root.eb().name)
 }
 
 func TestLoadExternalPluginNoExec(t *testing.T) {
diff --git a/plugin/methodWrappers_test.go b/plugin/methodWrappers_test.go
index df9ca2c..32ad47a 100644
--- a/plugin/methodWrappers_test.go
+++ b/plugin/methodWrappers_test.go
@@ -102,9 +102,9 @@ func (suite *MethodWrappersTestSuite) TestAttributes() {
 	suite.cache.On("Get", mock.Anything, mock.Anything).Return(nil, nil)
 
 	e := newMethodWrappersTestsMockEntry("mockEntry")
-	e.attr = EntryAttributes{}
-	e.attr.SetCtime(time.Now())
-	suite.Equal(e.attr, Attributes(e))
+	e.eb().attributes = EntryAttributes{}
+	e.eb().attributes.SetCtime(time.Now())
+	suite.Equal(e.eb().attributes, Attributes(e))
 }
 
 func (suite *MethodWrappersTestSuite) TestPrefetched() {
@@ -308,7 +308,7 @@ func (suite *MethodWrappersTestSuite) TestSignal_SendsSignalAndUpdatesCache() {
 	e.On("Schema").Return((*EntrySchema)(nil))
 	e.On("Signal", ctx, "start").Return(nil)
 
-	suite.cache.On("Delete", allOpKeysIncludingChildrenRegex(e.id())).Return([]string{})
+	suite.cache.On("Delete", allOpKeysIncludingChildrenRegex(e.eb().id)).Return([]string{})
 	suite.cache.On("Delete", opKeyRegex("List", "/foo")).Return([]string{})
 
 	// Also test case-insensitivity here
@@ -372,7 +372,7 @@ func (suite *MethodWrappersTestSuite) TestDelete_EntryDeletionInProgress_Updates
 	e.SetTestID("/foo/bar")
 	e.On("Delete", mock.Anything).Return(false, nil)
 
-	suite.cache.On("Delete", allOpKeysIncludingChildrenRegex(e.id())).Return([]string{})
+	suite.cache.On("Delete", allOpKeysIncludingChildrenRegex(e.eb().id)).Return([]string{})
 	suite.cache.On("Delete", opKeyRegex("List", "/foo")).Return([]string{})
 
 	deleted, err := Delete(context.Background(), e)
@@ -405,7 +405,7 @@ func (suite *MethodWrappersTestSuite) TestDelete_DeletedEntry_UpdatesCache() {
 
 	entryMap := newEntryMap()
 	entryMap.mp["bar"] = e
-	suite.cache.On("Delete", allOpKeysIncludingChildrenRegex(e.id())).Return([]string{})
+	suite.cache.On("Delete", allOpKeysIncludingChildrenRegex(e.eb().id)).Return([]string{})
 	suite.cache.On("Get", "List", "/foo").Return(entryMap, nil)
 
 	deleted, err := Delete(context.Background(), e)
diff --git a/plugin/testdata/externalPluginSchema.json b/plugin/testdata/externalPluginSchema.json
index 0e60cfc..1a77158 100644
--- a/plugin/testdata/externalPluginSchema.json
+++ b/plugin/testdata/externalPluginSchema.json
@@ -5,7 +5,7 @@
         "methods": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -27,7 +27,7 @@
         "methods": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -49,7 +49,7 @@
         "methods": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -72,7 +72,7 @@
         "methods": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -94,7 +94,7 @@
         "methods": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -160,7 +160,7 @@
         "methods": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -184,7 +184,7 @@
             "read",
             "stream"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "required": [
                 "ETag",
@@ -373,7 +373,7 @@
         "methods": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -395,7 +395,7 @@
         "methods": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -418,7 +418,7 @@
             "list",
             "exec"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "required": [
                 "AmiLaunchIndex",
@@ -1143,7 +1143,7 @@
         "methods": [
             "read"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -1161,7 +1161,7 @@
         "label": "metadata.json",
         "singleton": true,
         "methods": [],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -1181,7 +1181,7 @@
         "methods": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -1204,7 +1204,7 @@
         "methods": [
             "list"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
@@ -1228,7 +1228,7 @@
             "read",
             "stream"
         ],
-        "meta_attribute_schema": {
+        "partial_metadata_schema": {
             "$schema": "http://json-schema.org/draft-04/schema#",
             "additionalProperties": false,
             "type": "object",
diff --git a/plugin/testdata/externalPluginSchema_PartitionedSchemaGraph.json b/plugin/testdata/externalPluginSchema_PartitionedSchemaGraph.json
index 2e14150..5417aae 100644
--- a/plugin/testdata/externalPluginSchema_PartitionedSchemaGraph.json
+++ b/plugin/testdata/externalPluginSchema_PartitionedSchemaGraph.json
@@ -6,7 +6,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.profile"
@@ -18,7 +18,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.resourcesDir"
@@ -30,7 +30,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3Dir",
@@ -43,7 +43,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3Bucket"
@@ -55,7 +55,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -68,7 +68,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -82,7 +82,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -92,7 +92,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2InstancesDir"
@@ -104,7 +104,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2Instance"
@@ -117,7 +117,7 @@
                 "list",
                 "exec"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2InstanceConsoleOutput",
@@ -131,7 +131,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -141,7 +141,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -151,7 +151,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -164,7 +164,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -178,7 +178,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -190,7 +190,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2InstancesDir"
@@ -202,7 +202,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2Instance"
@@ -215,7 +215,7 @@
                 "list",
                 "exec"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2InstanceConsoleOutput",
@@ -229,7 +229,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -239,7 +239,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -249,7 +249,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -262,7 +262,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -276,7 +276,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -289,7 +289,7 @@
                 "list",
                 "exec"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2InstanceConsoleOutput",
@@ -303,7 +303,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -313,7 +313,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -323,7 +323,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -336,7 +336,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -350,7 +350,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -362,7 +362,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -374,7 +374,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -386,7 +386,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2Instance"
@@ -399,7 +399,7 @@
                 "list",
                 "exec"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2InstanceConsoleOutput",
@@ -413,7 +413,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -423,7 +423,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -433,7 +433,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -446,7 +446,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -460,7 +460,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -472,7 +472,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.resourcesDir"
@@ -484,7 +484,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3Dir",
@@ -497,7 +497,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3Bucket"
@@ -509,7 +509,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -522,7 +522,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -536,7 +536,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -546,7 +546,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2InstancesDir"
@@ -558,7 +558,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2Instance"
@@ -571,7 +571,7 @@
                 "list",
                 "exec"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2InstanceConsoleOutput",
@@ -585,7 +585,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -595,7 +595,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -605,7 +605,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -618,7 +618,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -632,7 +632,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -644,7 +644,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3Dir",
@@ -657,7 +657,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3Bucket"
@@ -669,7 +669,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -682,7 +682,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -696,7 +696,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -706,7 +706,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2InstancesDir"
@@ -718,7 +718,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2Instance"
@@ -731,7 +731,7 @@
                 "list",
                 "exec"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.ec2InstanceConsoleOutput",
@@ -745,7 +745,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -755,7 +755,7 @@
             "actions": [
                 "read"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         },
@@ -765,7 +765,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -778,7 +778,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -792,7 +792,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -804,7 +804,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -817,7 +817,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -831,7 +831,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -843,7 +843,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3Bucket"
@@ -855,7 +855,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -868,7 +868,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -882,7 +882,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -895,7 +895,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -907,7 +907,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.s3ObjectPrefix",
@@ -921,7 +921,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -933,7 +933,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -946,7 +946,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -960,7 +960,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -972,7 +972,7 @@
             "actions": [
                 "list"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": [
                 "fooPlugin::aws.volume.dir",
@@ -986,7 +986,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }
@@ -999,7 +999,7 @@
                 "read",
                 "stream"
             ],
-            "meta_attribute_schema": null,
+            "partial_metadata_schema": null,
             "metadata_schema": null,
             "children": null
         }