Repository: common-workflow-language/schema_salad (language: python)

Issue:
feature request: convert a CWL "inputs" section to a schema-salad definition
https://www.biostars.org/p/383396/#394573

As far as I can tell, this isn't straightforward to do


Discussion / hints:

Fix patch:
diff --git a/schema_salad/avro/schema.py b/schema_salad/avro/schema.py
index c5d33bdfd..7aa5348bd 100644
--- a/schema_salad/avro/schema.py
+++ b/schema_salad/avro/schema.py
@@ -21,6 +21,7 @@
   A record, mapping field names to field value data;
   An enum, containing one of a small set of symbols;
   An array of values, all of the same schema;
+  A map of values, all of the same schema;
   A union of other schemas;
   A unicode string;
   A 32-bit signed int;
@@ -44,7 +45,7 @@
 
 NAMED_TYPES = ("enum", "record")
 
-VALID_TYPES = PRIMITIVE_TYPES + NAMED_TYPES + ("array", "union")
+VALID_TYPES = PRIMITIVE_TYPES + NAMED_TYPES + ("array", "map", "union")
 
 SCHEMA_RESERVED_PROPS = (
     "type",
@@ -52,7 +53,9 @@
     "namespace",
     "fields",  # Record
     "items",  # Array
+    "names",  # Union
     "symbols",  # Enum
+    "values",  # Map
     "doc",
 )
 
@@ -72,6 +75,7 @@
 
 VALID_FIELD_SORT_ORDERS = ("ascending", "descending", "ignore")
 
+
 #
 # Exceptions
 #
@@ -141,6 +145,7 @@ def __init__(
         @arg space_attr: namespace value read in schema or None.
         @ard default_space: the current default space or None.
         """
+
         # Ensure valid ctor args
 
         def validate(val: Optional[str], name: str) -> None:
@@ -389,8 +394,13 @@ def symbols(self) -> List[str]:
 
 
 class ArraySchema(Schema):
+    """Avro array schema class."""
+
     def __init__(
-        self, items: JsonDataType, names: Names, other_props: Optional[PropsType] = None
+        self,
+        items: JsonDataType,
+        names: Names,
+        other_props: Optional[PropsType] = None,
     ) -> None:
         # Call parent ctor
         Schema.__init__(self, "array", other_props)
@@ -414,10 +424,116 @@ def __init__(
     # read-only properties
     @property
     def items(self) -> Schema:
+        """Avro schema describing the array items' type."""
         return cast(Schema, self.get_prop("items"))
 
 
+class MapSchema(Schema):
+    """Avro map schema class."""
+
+    def __init__(
+        self,
+        values: JsonDataType,
+        names: Names,
+        other_props: Optional[PropsType] = None,
+    ) -> None:
+        """Create a MapSchema object."""
+        # Call parent ctor
+        Schema.__init__(self, "map", other_props)
+
+        # Add class members
+        if isinstance(values, str) and names.has_name(values, None):
+            values_schema = cast(Schema, names.get_name(values, None))
+        else:
+            try:
+                values_schema = make_avsc_object(values, names)
+            except SchemaParseException:
+                raise
+            except Exception as err:
+                raise SchemaParseException(
+                    f"Values schema ({values}) not a valid Avro schema: {err}. "
+                    f"Known names: {list(names.names.keys())})."
+                ) from err
+
+        self.set_prop("values", values_schema)
+
+    # read-only properties
+    @property
+    def values(self) -> Schema:
+        """Avro schema describing the map values' type."""
+        return cast(Schema, self.get_prop("values"))
+
+
+class NamedMapSchema(NamedSchema):
+    """Avro named map schema class."""
+
+    def __init__(
+        self,
+        values: JsonDataType,
+        names: Names,
+        name: str,
+        namespace: Optional[str] = None,
+        doc: Optional[Union[str, List[str]]] = None,
+        other_props: Optional[PropsType] = None,
+    ) -> None:
+        """Create a NamedMapSchema object."""
+        # Call parent ctor
+        NamedSchema.__init__(self, "map", name, namespace, names, other_props)
+
+        # Add class members
+        if isinstance(values, str) and names.has_name(values, None):
+            values_schema = cast(Schema, names.get_name(values, None))
+        else:
+            try:
+                values_schema = make_avsc_object(values, names)
+            except SchemaParseException:
+                raise
+            except Exception as err:
+                raise SchemaParseException(
+                    f"Values schema ({values}) not a valid Avro schema: {err}. "
+                    f"Known names: {list(names.names.keys())})."
+                ) from err
+
+        self.set_prop("values", values_schema)
+        if doc is not None:
+            self.set_prop("doc", doc)
+
+    # read-only properties
+    @property
+    def values(self) -> Schema:
+        """Avro schema describing the map values' type."""
+        return cast(Schema, self.get_prop("values"))
+
+
+def _build_schema_objects(schemas: List[JsonDataType], names: Names) -> List[Schema]:
+    schema_objects: List[Schema] = []
+    for schema in schemas:
+        if isinstance(schema, str) and names.has_name(schema, None):
+            new_schema = cast(Schema, names.get_name(schema, None))
+        else:
+            try:
+                new_schema = make_avsc_object(schema, names)
+            except Exception as err:
+                raise SchemaParseException(
+                    f"Union item must be a valid Avro schema: {err}; {schema},"
+                ) from err
+        # check the new schema
+        if (
+            new_schema.type in VALID_TYPES
+            and new_schema.type not in NAMED_TYPES
+            and new_schema.type in [schema.type for schema in schema_objects]
+        ):
+            raise SchemaParseException(f"{new_schema.type} type already in Union")
+        elif new_schema.type == "union" and new_schema.get_prop("name") is None:
+            raise SchemaParseException("Unions cannot contain other unions.")
+        else:
+            schema_objects.append(new_schema)
+    return schema_objects
+
+
 class UnionSchema(Schema):
+    """Avro union schema class."""
+
     def __init__(
         self,
         schemas: List[JsonDataType],
@@ -438,28 +554,44 @@ def __init__(
         Schema.__init__(self, "union")
 
         # Add class members
-        schema_objects: List[Schema] = []
-        for schema in schemas:
-            if isinstance(schema, str) and names.has_name(schema, None):
-                new_schema = cast(Schema, names.get_name(schema, None))
-            else:
-                try:
-                    new_schema = make_avsc_object(schema, names)
-                except Exception as err:
-                    raise SchemaParseException(
-                        f"Union item must be a valid Avro schema: {err}; {schema},"
-                    ) from err
-            # check the new schema
-            if (
-                new_schema.type in VALID_TYPES
-                and new_schema.type not in NAMED_TYPES
-                and new_schema.type in [schema.type for schema in schema_objects]
-            ):
-                raise SchemaParseException(f"{new_schema.type} type already in Union")
-            if new_schema.type == "union":
-                raise SchemaParseException("Unions cannot contain other unions.")
-            schema_objects.append(new_schema)
-        self._schemas = schema_objects
+        self._schemas = _build_schema_objects(schemas, names)
+
+    # read-only properties
+    @property
+    def schemas(self) -> List[Schema]:
+        """Avro schemas composing the Union type."""
+        return self._schemas
+
+
+class NamedUnionSchema(NamedSchema):
+    """Avro named union schema class."""
+
+    def __init__(
+        self,
+        schemas: List[JsonDataType],
+        names: Names,
+        name: str,
+        namespace: Optional[str] = None,
+        doc: Optional[Union[str, List[str]]] = None,
+    ):
+        """
+        Initialize a new NamedUnionSchema.
+
+        :param names: a dictionary of schema objects
+        """
+        # Ensure valid ctor args
+        if names is None:
+            raise SchemaParseException("Must provide Names.")
+        if not isinstance(schemas, list):
+            raise SchemaParseException("Union schema requires a list of schemas.")
+
+        # Call parent ctor
+        NamedSchema.__init__(self, "union", name, namespace, names)
+
+        # Add class members
+        self._schemas = _build_schema_objects(schemas, names)
+        if doc is not None:
+            self.set_prop("doc", doc)
 
     # read-only properties
     @property
@@ -606,6 +738,28 @@ def make_avsc_object(json_data: JsonDataType, names: Optional[Names] = None) ->
             if atype == "array":
                 items = json_data.get("items")
                 return ArraySchema(items, names, other_props)
+            elif atype == "map":
+                values = json_data.get("values")
+                if "name" in json_data:
+                    name = json_data["name"]
+                    namespace = json_data.get("namespace", names.default_namespace)
+                    doc = json_data.get("doc")
+                    return NamedMapSchema(values, names, name, namespace, doc, other_props)
+                else:
+                    return MapSchema(values, names, other_props)
+            elif atype == "union":
+                schemas = json_data.get("names")
+                if not isinstance(schemas, list):
+                    raise SchemaParseException(
+                        f'"names" for type union must be a list of schemas: {json_data}'
+                    )
+                if "name" in json_data:
+                    name = json_data["name"]
+                    namespace = json_data.get("namespace", names.default_namespace)
+                    doc = json_data.get("doc")
+                    return NamedUnionSchema(schemas, names, name, namespace, doc)
+                else:
+                    return UnionSchema(schemas, names)
         if atype is None:
             raise SchemaParseException(f'No "type" property: {json_data}')
         raise SchemaParseException(f"Undefined type: {atype}")
@@ -636,11 +790,17 @@ def is_subtype(types: Dict[str, Any], existing: PropType, new: PropType) -> bool
         return is_subtype(types, types[existing], new)
     if isinstance(new, str) and new in types:
         return is_subtype(types, existing, types[new])
+    if isinstance(existing, dict) and existing.get("type") == "union":
+        return is_subtype(types, existing["names"], new)
+    if isinstance(new, dict) and new.get("type") == "union":
+        return is_subtype(types, existing, new["names"])
     if isinstance(existing, dict) and isinstance(new, dict):
         if "extends" in new and new["extends"] == existing.get("name"):
             return True
         if existing.get("type") == "array" and new.get("type") == "array":
             return is_subtype(types, existing["items"], new["items"])
+        if existing.get("type") == "map" and new.get("type") == "map":
+            return is_subtype(types, existing["values"], new["values"])
         if existing.get("type") == "enum" and new.get("type") == "enum":
             return is_subtype(types, existing["symbols"], new["symbols"])
         if existing.get("type") == "record" and new.get("type") == "record":
diff --git a/schema_salad/codegen.py b/schema_salad/codegen.py
index df0b4ad89..cd433c883 100644
--- a/schema_salad/codegen.py
+++ b/schema_salad/codegen.py
@@ -114,8 +114,12 @@ def codegen(
     document_roots = []
 
     for rec in j:
-        if rec["type"] in ("enum", "record"):
-            gen.type_loader(rec)
+        if rec["type"] in ("enum", "map", "record", "union"):
+            jld = rec.get("jsonldPredicate")
+            if isinstance(jld, MutableMapping):
+                gen.type_loader(rec, jld.get("_container"), jld.get("noLinkCheck"))
+            else:
+                gen.type_loader(rec)
             gen.add_vocab(shortname(rec["name"]), rec["name"])
 
     for rec in j:
@@ -164,9 +168,7 @@ def codegen(
                     subscope = field.get("subscope")
                     fieldpred = field["name"]
                     optional = bool("https://w3id.org/cwl/salad#null" in field["type"])
-                    uri_loader = gen.uri_loader(
-                        gen.type_loader(field["type"]), True, False, None, None
-                    )
+                    uri_loader = gen.uri_loader(gen.type_loader(field["type"]), True, False, None)
                     gen.declare_id_field(
                         fieldpred,
                         uri_loader,
@@ -177,12 +179,14 @@ def codegen(
 
             for field in sorted_fields:
                 optional = bool("https://w3id.org/cwl/salad#null" in field["type"])
-                type_loader = gen.type_loader(field["type"])
                 jld = field.get("jsonldPredicate")
                 fieldpred = field["name"]
                 subscope = None
 
                 if isinstance(jld, MutableMapping):
+                    type_loader = gen.type_loader(
+                        field["type"], jld.get("_container"), jld.get("noLinkCheck")
+                    )
                     ref_scope = jld.get("refScope")
                     subscope = jld.get("subscope")
                     if jld.get("typeDSL"):
@@ -213,6 +217,8 @@ def codegen(
 
                     if "_id" in jld and jld["_id"][0] != "@":
                         fieldpred = jld["_id"]
+                else:
+                    type_loader = gen.type_loader(field["type"])
 
                 if jld == "@id":
                     continue
diff --git a/schema_salad/codegen_base.py b/schema_salad/codegen_base.py
index dd9eaa30a..315033864 100644
--- a/schema_salad/codegen_base.py
+++ b/schema_salad/codegen_base.py
@@ -41,11 +41,30 @@ def __init__(
         self.instance_type = instance_type
 
 
+class LazyInitDef:
+    """Lazy initialization logic."""
+
+    __slots__ = (
+        "name",
+        "init",
+    )
+
+    def __init__(
+        self,
+        name: str,
+        init: str,
+    ) -> None:
+        """Create a LazyInitDef object."""
+        self.name = name
+        self.init = init
+
+
 class CodeGenBase:
     """Abstract base class for schema salad code generators."""
 
     def __init__(self) -> None:
         self.collected_types: OrderedDict[str, TypeDef] = OrderedDict()
+        self.lazy_inits: OrderedDict[str, LazyInitDef] = OrderedDict()
         self.vocab: Dict[str, str] = {}
 
     def declare_type(self, declared_type: TypeDef) -> TypeDef:
@@ -54,6 +73,10 @@ def declare_type(self, declared_type: TypeDef) -> TypeDef:
             self.collected_types[declared_type.name] = declared_type
         return declared_type
 
+    def add_lazy_init(self, lazy_init: LazyInitDef) -> None:
+        """Add lazy initialization logic for a given type."""
+        self.lazy_inits[lazy_init.name] = lazy_init
+
     def add_vocab(self, name: str, uri: str) -> None:
         """Add the given name as an abbreviation for the given URI."""
         self.vocab[name] = uri
@@ -84,7 +107,12 @@ def end_class(self, classname: str, field_names: List[str]) -> None:
         """Signal that we are done with this class."""
         raise NotImplementedError()
 
-    def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any]]) -> TypeDef:
+    def type_loader(
+        self,
+        type_declaration: Union[List[Any], Dict[str, Any]],
+        container: Optional[str] = None,
+        no_link_check: Optional[bool] = None,
+    ) -> TypeDef:
         """Parse the given type declaration and declare its components."""
         raise NotImplementedError()
 
@@ -115,7 +143,7 @@ def uri_loader(
         scoped_id: bool,
         vocab_term: bool,
         ref_scope: Optional[int],
-        no_link_check: Optional[bool],
+        no_link_check: Optional[bool] = None,
     ) -> TypeDef:
         """Construct the TypeDef for the given URI loader."""
         raise NotImplementedError()
diff --git a/schema_salad/cpp_codegen.py b/schema_salad/cpp_codegen.py
index 56468c1e2..1790192d0 100644
--- a/schema_salad/cpp_codegen.py
+++ b/schema_salad/cpp_codegen.py
@@ -46,6 +46,7 @@ def replaceKeywords(s: str) -> str:
         "stdin",
         "stdout",
         "stderr",
+        "union",
     ):
         s = s + "_"
     return s
@@ -248,6 +249,132 @@ def writeDefinition(self, target: IO[Any], fullInd: str, ind: str, namespace: st
         target.write(f"{fullInd}heap_object<{typeStr}> {name};\n")
 
 
+class MapDefinition:
+    """Prototype of a map."""
+
+    def __init__(self, name: str, values: List[str]):
+        """Initialize union definition with a name and possible values."""
+        self.values = values
+        (self.namespace, self.classname) = split_name(name)
+        self.namespace = safename(self.namespace)
+        self.classname = safename(self.classname)
+
+    def _remove_namespace(self, typeStr: str) -> str:
+        return typeStr.replace(f"{self.namespace}::", "")
+
+    def writeFwdDeclaration(self, target: IO[str], fullInd: str, ind: str) -> None:
+        """Write forward declaration."""
+        target.write(f"{fullInd}namespace {self.namespace} {{ struct {self.classname}; }}\n")
+
+    def writeDefinition(self, target: IO[str], ind: str) -> None:
+        """Write map definition to output."""
+        target.write(f"namespace {self.namespace} {{\n")
+        if len(self.values) == 1:
+            valueType = self._remove_namespace(self.values[0])
+        else:
+            valueType = f"std::variant<{', '.join(self._remove_namespace(v) for v in self.values)}>"
+        target.write(f"struct {self.classname} {{\n")
+        target.write(f"{ind}heap_object<std::map<std::string, {valueType}>> value;\n")
+        target.write(f"{ind}auto toYaml() const -> YAML::Node;\n")
+        target.write(f"{ind}void fromYaml(YAML::Node const& n);\n")
+        target.write("};\n")
+        target.write("}\n\n")
+
+    def writeImplDefinition(self, target: IO[str], fullInd: str, ind: str) -> None:
+        """Write definition with implementation."""
+        # Write toYaml function
+        functionname = f"{self.namespace}::{self.classname}::toYaml"
+        target.write(
+            f"""{fullInd}inline auto {functionname}() const -> YAML::Node {{
+{fullInd}{ind}using ::toYaml;
+{fullInd}{ind}return toYaml(*value);\n{fullInd}}}\n
+"""
+        )
+
+        # Write fromYaml function
+        functionname = f"{self.namespace}::{self.classname}::fromYaml"
+        target.write(
+            f"""{fullInd}inline void {functionname}([[maybe_unused]] YAML::Node const& n) {{
+{fullInd}{ind}using ::fromYaml;
+{fullInd}{ind}fromYaml(n, *value);\n{fullInd}}}\n
+"""
+        )
+
+
+class UnionDefinition:
+    """Prototype of a union."""
+
+    def __init__(self, name: str, types: List[str]):
+        """Initialize union definition with a name and possible types."""
+        (self.namespace, self.classname) = split_name(name)
+        self.namespace = safename(self.namespace)
+        self.classname = safename(self.classname)
+        self.types = (
+            self._remove_namespace(types[0])
+            if len(types) == 1
+            else f"std::variant<{', '.join(self._remove_namespace(t) for t in types)}>"
+        )
+
+    def _remove_namespace(self, typeStr: str) -> str:
+        return typeStr.replace(f"{self.namespace}::", "")
+
+    def writeFwdDeclaration(self, target: IO[str], fullInd: str, ind: str) -> None:
+        """Write forward declaration."""
+        target.write(f"{fullInd}namespace {self.namespace} {{ struct {self.classname}; }}\n")
+
+    def writeDefinition(self, target: IO[str], ind: str) -> None:
+        """Write union definition to output."""
+        target.write(f"namespace {self.namespace} {{\n")
+        target.write(f"struct {self.classname} {{\n")
+        target.write(f"{ind}{self.types} *value = nullptr;\n")
+        target.write(f"{ind}{self.classname}();\n")
+        target.write(f"{ind}~{self.classname}();\n")
+        target.write(f"{ind}auto toYaml() const -> YAML::Node;\n")
+        target.write(f"{ind}void fromYaml(YAML::Node const& n);\n")
+        target.write("};\n")
+        target.write("}\n\n")
+
+    def writeImplDefinition(self, target: IO[str], fullInd: str, ind: str) -> None:
+        """Write definition with implementation."""
+        # Write constructor
+        functionname = f"{self.namespace}::{self.classname}::{self.classname}"
+        target.write(
+            f"""{fullInd}{functionname}() {{
+{fullInd}{ind}value = new {self.types}();\n{fullInd}}}\n
+"""
+        )
+
+        # Write destructor
+        functionname = f"{self.namespace}::{self.classname}::~{self.classname}"
+        target.write(
+            f"""{fullInd}{functionname}() {{
+{fullInd}{ind}if (value != nullptr) {{
+{fullInd}{ind}{ind}delete value;
+{fullInd}{ind}{ind}value = nullptr;
+{fullInd}{ind}}}
+{fullInd}}}\n
+"""
+        )
+
+        # Write toYaml function
+        functionname = f"{self.namespace}::{self.classname}::toYaml"
+        target.write(
+            f"""{fullInd}inline auto {functionname}() const -> YAML::Node {{
+{fullInd}{ind}using ::toYaml;
+{fullInd}{ind}return toYaml(*value);\n{fullInd}}}\n
+"""
+        )
+
+        # Write fromYaml function
+        functionname = f"{self.namespace}::{self.classname}::fromYaml"
+        target.write(
+            f"""{fullInd}inline void {functionname}([[maybe_unused]] YAML::Node const& n) {{
+{fullInd}{ind}using ::fromYaml;
+{fullInd}{ind}fromYaml(n, *value);\n{fullInd}}}\n
+"""
+        )
+
+
 class EnumDefinition:
     """Prototype of a enum."""
 
@@ -301,7 +428,9 @@ def writeDefinition(self, target: IO[str], ind: str) -> None:
         target.write(f"{ind}to_enum(n.as<std::string>(), out);\n}}\n")
 
         if len(self.values):
-            target.write(f"{ind}template <> struct IsConstant<{name}> : std::true_type {{}};")
+            target.write(f"template <> struct IsConstant<{name}> : std::true_type {{}};\n")
+
+        target.write("\n")
 
 
 # !TODO way tot many functions, most of these shouldn't exists
@@ -318,7 +447,7 @@ def hasFieldValue(e: Any, f: str, v: Any) -> bool:
         return False
     if f not in e:
         return False
-    return bool(e[f] == v)
+    return bool(e[f] in [v, f"https://w3id.org/cwl/salad#{v}"])
 
 
 def isRecordSchema(v: Any) -> bool:
@@ -354,6 +483,8 @@ def pred(i: Any) -> bool:
         or isRecordSchema(i)
         or isEnumSchema(i)
         or isArraySchema(i)
+        or isMapSchema(i)
+        or isUnionSchema(i)
         or isinstance(i, str)
     )
 
@@ -373,6 +504,26 @@ def isArraySchema(v: Any) -> bool:
     return True
 
 
+def isMapSchema(v: Any) -> bool:
+    """Check if v is of type map schema."""
+    if not hasFieldValue(v, "type", "map"):
+        return False
+    if "values" not in v:
+        return False
+    if not isinstance(v["values"], list):
+        return False
+
+    for i in v["values"]:
+        if not (pred(i) or isArray(i)):
+            return False
+    return True
+
+
+def isUnionSchema(v: Any) -> bool:
+    """Check if v is of type union schema."""
+    return hasFieldValue(v, "type", "union")
+
+
 class CppCodeGen(CodeGenBase):
     """Generation of C++ code for a given Schema Salad definition."""
 
@@ -398,6 +549,8 @@ def __init__(
 
         self.classDefinitions: Dict[str, ClassDefinition] = {}
         self.enumDefinitions: Dict[str, EnumDefinition] = {}
+        self.mapDefinitions: Dict[str, MapDefinition] = {}
+        self.unionDefinitions: Dict[str, UnionDefinition] = {}
 
     def convertTypeToCpp(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -> str:
         """Convert a Schema Salad type to a C++ type."""
@@ -462,14 +615,24 @@ def convertTypeToCpp(self, type_declaration: Union[List[Any], Dict[str, Any], st
                 ):
                     items = type_declaration[0]["items"]
                     if isinstance(items, list):
-                        ts = []
-                        for i in items:
-                            ts.append(self.convertTypeToCpp(i))
+                        ts = [self.convertTypeToCpp(i) for i in items]
                         name = ", ".join(ts)
                         return f"std::vector<std::variant<{name}>>"
                     else:
                         i = self.convertTypeToCpp(items)
                         return f"std::vector<{i}>"
+                elif "type" in type_declaration[0] and type_declaration[0]["type"] in (
+                    "map",
+                    "https://w3id.org/cwl/salad#map",
+                ):
+                    values = type_declaration[0]["values"]
+                    if isinstance(values, list):
+                        ts = [self.convertTypeToCpp(i) for i in values]
+                        name = ", ".join(ts)
+                        return f"std::map<std::string, std::variant<{name}>>"
+                    else:
+                        i = self.convertTypeToCpp(values)
+                        return f"std::map<std::string, {i}>"
                 elif "type" in type_declaration[0] and type_declaration[0]["type"] in (
                     "record",
                     "https://w3id.org/cwl/salad#record",
@@ -618,6 +781,8 @@ def epilogue(self, root_loader: Optional[TypeDef]) -> None:
 template <typename T>
 auto toYaml(std::vector<T> const& v) -> YAML::Node;
 template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node;
+template <typename T>
 auto toYaml(T const& t) -> YAML::Node;
 template <typename ...Args>
 auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
@@ -626,6 +791,8 @@ def epilogue(self, root_loader: Optional[TypeDef]) -> None:
 template <typename T>
 void fromYaml(YAML::Node const& n, std::vector<T>& v);
 template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v);
+template <typename T>
 void fromYaml(YAML::Node const& n, T& t);
 template <typename ...Args>
 void fromYaml(YAML::Node const& n, std::variant<Args...>& t);
@@ -673,6 +840,17 @@ def epilogue(self, root_loader: Optional[TypeDef]) -> None:
     }
 };
 
+template <typename T>
+struct DetectAndExtractFromYaml<std::map<std::string, T>> {
+    auto operator()(YAML::Node const& n) const -> std::optional<std::map<std::string, T>> {
+        if (!n.IsDefined()) return std::nullopt;
+        if (!n.IsMap()) return std::nullopt;
+        auto res = std::map<std::string, T>{};
+        fromYaml(n, res);
+        return res;
+    }
+};
+
 template <typename T>
 class heap_object {
     std::unique_ptr<T> data = std::make_unique<T>();
@@ -738,6 +916,10 @@ class heap_object {
 
         for key in self.classDefinitions:
             self.classDefinitions[key].writeFwdDeclaration(self.target, "", "    ")
+        for key in self.mapDefinitions:
+            self.mapDefinitions[key].writeFwdDeclaration(self.target, "", "    ")
+        for key in self.unionDefinitions:
+            self.unionDefinitions[key].writeFwdDeclaration(self.target, "", "    ")
 
         # remove parent classes, that are specialized/templated versions
         for key in self.classDefinitions:
@@ -760,12 +942,23 @@ class heap_object {
                 if not found:
                     self.classDefinitions[key].fields.append(field)
 
+        # write definitions
         for key in self.enumDefinitions:
             self.enumDefinitions[key].writeDefinition(self.target, "    ")
         for key in self.classDefinitions:
             self.classDefinitions[key].writeDefinition(self.target, "", "    ")
+        for key in self.mapDefinitions:
+            self.mapDefinitions[key].writeDefinition(self.target, "    ")
+        for key in self.unionDefinitions:
+            self.unionDefinitions[key].writeDefinition(self.target, "    ")
+
+        # write implementations
         for key in self.classDefinitions:
             self.classDefinitions[key].writeImplDefinition(self.target, "", "    ")
+        for key in self.mapDefinitions:
+            self.mapDefinitions[key].writeImplDefinition(self.target, "", "    ")
+        for key in self.unionDefinitions:
+            self.unionDefinitions[key].writeImplDefinition(self.target, "", "    ")
 
         self.target.write(
             """
@@ -778,6 +971,15 @@ class heap_object {
     return n;
 }
 
+template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node {
+    auto n = YAML::Node(YAML::NodeType::Map);
+    for (auto const& [key, value] : v) {
+        n[key] = toYaml(value);
+    }
+    return n;
+}
+
 template <typename T>
 auto toYaml(T const& t) -> YAML::Node {
     if constexpr (std::is_enum_v<T>) {
@@ -803,6 +1005,15 @@ class heap_object {
     }
 }
 
+template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v){
+    if (!n.IsMap()) return;
+    for (auto e : n) {
+        auto key = e.first.as<std::string>();
+        fromYaml(e.second, v[key]);
+    }
+}
+
 template <typename T>
 void fromYaml(YAML::Node const& n, T& t){
     if constexpr (std::is_enum_v<T>) {
@@ -886,6 +1097,24 @@ def parseRecordSchema(self, stype: Dict[str, Any]) -> None:
 
         self.classDefinitions[stype["name"]] = cd
 
+    def parseMapSchema(self, stype: Dict[str, Any]) -> str:
+        """Parse a map schema."""
+        name = cast(str, stype["name"])
+        if name not in self.mapDefinitions:
+            self.mapDefinitions[name] = MapDefinition(
+                name, list(map(self.convertTypeToCpp, stype["values"]))
+            )
+        return name
+
+    def parseUnionSchema(self, stype: Dict[str, Any]) -> str:
+        """Parse a union schema."""
+        name = cast(str, stype["name"])
+        if name not in self.unionDefinitions:
+            self.unionDefinitions[name] = UnionDefinition(
+                name, list(map(self.convertTypeToCpp, stype["names"]))
+            )
+        return name
+
     def parseEnum(self, stype: Dict[str, Any]) -> str:
         """Parse a schema salad enum."""
         name = cast(str, stype["name"])
@@ -911,6 +1140,10 @@ def parse(self, items: List[Dict[str, Any]]) -> None:
             # parsing a record
             if isRecordSchema(stype):
                 self.parseRecordSchema(stype)
+            elif isMapSchema(stype):
+                self.parseMapSchema(stype)
+            elif isUnionSchema(stype):
+                self.parseUnionSchema(stype)
             elif isEnumSchema(stype):
                 self.parseEnum(stype)
             else:
diff --git a/schema_salad/dotnet/util/Loaders/ArrayLoader.cs b/schema_salad/dotnet/util/Loaders/ArrayLoader.cs
index fe5eaad5e..2180f961a 100644
--- a/schema_salad/dotnet/util/Loaders/ArrayLoader.cs
+++ b/schema_salad/dotnet/util/Loaders/ArrayLoader.cs
@@ -38,7 +38,8 @@ public List<T> Load(in object doc, in string baseuri, in LoadingOptions loadingO
             try
             {
                 dynamic loadedField = unionLoader.LoadField(e1, baseuri, loadingOptions);
-                if (loadedField is IList)
+                bool flatten = !String.Equals("@list", loadingOptions.container);
+                if (flatten && loadedField is IList)
                 {
                     returnValue.AddRange((List<T>)loadedField);
                 }
diff --git a/schema_salad/dotnet/util/Loaders/MapLoader.cs b/schema_salad/dotnet/util/Loaders/MapLoader.cs
new file mode 100644
index 000000000..13129f4a5
--- /dev/null
+++ b/schema_salad/dotnet/util/Loaders/MapLoader.cs
@@ -0,0 +1,71 @@
+﻿using System.Collections;
+
+namespace ${project_name};
+
+internal class MapLoader<T> : ILoader<Dictionary<string, T>>
+{
+    private readonly ILoader valueLoader;
+    private readonly string? container;
+    private readonly bool? noLinkCheck;
+
+    public MapLoader(in ILoader valueLoader, in string? container, in bool? noLinkCheck)
+    {
+        this.valueLoader = valueLoader;
+        this.container = container;
+        this.noLinkCheck = noLinkCheck;
+    }
+
+    public Dictionary<string, T> Load(in object doc, in string baseuri, in LoadingOptions loadingOptions, in string? docRoot = null)
+    {
+        if (doc == null)
+        {
+            throw new ValidationException("Expected non null");
+        }
+
+        if (doc is not IDictionary)
+        {
+            throw new ValidationException("Expected list");
+        }
+
+        LoadingOptions innerLoadingOptions = loadingOptions;
+        if (this.container != null || this.noLinkCheck != null)
+        {
+            innerLoadingOptions = new LoadingOptions(copyFrom: loadingOptions, container: this.container, noLinkCheck: this.noLinkCheck);
+        }
+
+        IDictionary docDictionary = (IDictionary)doc;
+        Dictionary<string, T> returnValue = new();
+        List<ILoader> loaders = new()
+        {
+            this,
+            valueLoader
+        };
+        ILoader<object> unionLoader = new UnionLoader(loaders);
+        List<ValidationException> errors = new();
+
+        foreach (KeyValuePair<string, T> item in docDictionary)
+        {
+            try
+            {
+                dynamic loadedField = unionLoader.LoadField(item.Value, baseuri, innerLoadingOptions);
+                returnValue[item.Key] = loadedField;
+            }
+            catch (ValidationException e)
+            {
+                errors.Add(e);
+            }
+        }
+
+        if (errors.Count > 0)
+        {
+            throw new ValidationException("", errors);
+        }
+
+        return returnValue;
+    }
+
+    object ILoader.Load(in object doc, in string baseuri, in LoadingOptions loadingOptions, in string? docRoot)
+    {
+        return Load(doc, baseuri, loadingOptions, docRoot);
+    }
+}
diff --git a/schema_salad/dotnet/util/Loaders/RecordLoader.cs b/schema_salad/dotnet/util/Loaders/RecordLoader.cs
index e09d37cea..8c2dab370 100644
--- a/schema_salad/dotnet/util/Loaders/RecordLoader.cs
+++ b/schema_salad/dotnet/util/Loaders/RecordLoader.cs
@@ -4,6 +4,15 @@ namespace ${project_name};
 
 internal class RecordLoader<T> : ILoader<T> where T : ISaveable
 {
+    private readonly string? container;
+    private readonly bool? noLinkCheck;
+
+    public RecordLoader(in string? container, in bool? noLinkCheck)
+    {
+        this.container = container;
+        this.noLinkCheck = noLinkCheck;
+    }
+
     public T Load(in object doc, in string baseUri, in LoadingOptions loadingOptions, in string? docRoot = null)
     {
         if (doc is not IDictionary)
@@ -11,7 +20,13 @@ public T Load(in object doc, in string baseUri, in LoadingOptions loadingOptions
             throw new ValidationException($"Expected object with type of Dictionary but got {doc.GetType()}");
         }
 
-        return (T)T.FromDoc(doc, baseUri, loadingOptions, docRoot);
+        LoadingOptions innerLoadingOptions = loadingOptions;
+        if (this.container != null || this.noLinkCheck != null)
+        {
+            innerLoadingOptions = new LoadingOptions(copyFrom: loadingOptions, container: this.container, noLinkCheck: this.noLinkCheck);
+        }
+
+        return (T)T.FromDoc(doc, baseUri, innerLoadingOptions, docRoot);
     }
 
     object ILoader.Load(in object doc, in string baseuri, in LoadingOptions loadingOptions, in string? docRoot)
diff --git a/schema_salad/dotnet/util/Loaders/UnionLoader.cs b/schema_salad/dotnet/util/Loaders/UnionLoader.cs
index 77ccdb87e..f092b7b6d 100644
--- a/schema_salad/dotnet/util/Loaders/UnionLoader.cs
+++ b/schema_salad/dotnet/util/Loaders/UnionLoader.cs
@@ -9,6 +9,11 @@ public UnionLoader(in List<ILoader> alternatives)
         this.alternatives = alternatives;
     }
 
+    public void addLoaders(IEnumerable<ILoader> loaders)
+    {
+        this.alternatives.AddRange(loaders);
+    }
+
     public object Load(in object doc, in string baseuri, in LoadingOptions loadingOptions, in string? docRoot = null)
     {
         List<ValidationException> errors = new();
diff --git a/schema_salad/dotnet/util/Loaders/UriLoader.cs b/schema_salad/dotnet/util/Loaders/UriLoader.cs
index 9b1d558b4..bb4bb1d22 100644
--- a/schema_salad/dotnet/util/Loaders/UriLoader.cs
+++ b/schema_salad/dotnet/util/Loaders/UriLoader.cs
@@ -24,7 +24,7 @@ public object Load(in object doc_, in string baseuri, in LoadingOptions loadingO
         LoadingOptions innerLoadingOptions = loadingOptions;
         if (this.noLinkCheck != null)
         {
-            innerLoadingOptions = new LoadingOptions(copyFrom: innerLoadingOptions, noLinkCheck: this.noLinkCheck);
+            innerLoadingOptions = new LoadingOptions(copyFrom: loadingOptions, noLinkCheck: this.noLinkCheck);
         }
         object doc = doc_;
         if (doc is IList)
diff --git a/schema_salad/dotnet/util/LoadingOptions.cs b/schema_salad/dotnet/util/LoadingOptions.cs
index 519d15632..f99898fa3 100644
--- a/schema_salad/dotnet/util/LoadingOptions.cs
+++ b/schema_salad/dotnet/util/LoadingOptions.cs
@@ -6,6 +6,7 @@ public class LoadingOptions
     public string? fileUri;
     public Dictionary<string, string>? namespaces;
     public bool? noLinkCheck;
+    public string? container;
     public List<string>? schemas;
     public Dictionary<string, object> idx;
     public Dictionary<string, string> vocab;
@@ -17,6 +18,7 @@ public LoadingOptions(
         in string? fileUri = null,
         in Dictionary<string, string>? namespaces = null,
         in bool? noLinkCheck = null,
+        in string? container = null,
         in List<string>? schemas = null,
         in Dictionary<string, object>? idx = null,
         LoadingOptions? copyFrom = null)
@@ -24,6 +26,7 @@ public LoadingOptions(
         this.fileUri = fileUri;
         this.namespaces = namespaces;
         this.noLinkCheck = noLinkCheck;
+        this.container = container;
         this.schemas = schemas;
         this.idx = idx ?? new Dictionary<string, object>();
 
@@ -50,6 +53,11 @@ public LoadingOptions(
                 this.noLinkCheck = copyFrom.noLinkCheck;
             }
 
+            if (container == null)
+            {
+                this.container = copyFrom.container;
+            }
+
             if (schemas == null)
             {
                 this.schemas = copyFrom.schemas;
diff --git a/schema_salad/dotnet_codegen.py b/schema_salad/dotnet_codegen.py
index fe118ca51..c7d55495e 100644
--- a/schema_salad/dotnet_codegen.py
+++ b/schema_salad/dotnet_codegen.py
@@ -19,7 +19,7 @@
 from importlib_resources import files
 
 from . import _logger, schema
-from .codegen_base import CodeGenBase, TypeDef
+from .codegen_base import CodeGenBase, LazyInitDef, TypeDef
 from .exceptions import SchemaException
 from .java_codegen import _ensure_directory_and_write, _safe_makedirs
 from .schema import shortname
@@ -394,7 +394,12 @@ def end_class(self, classname: str, field_names: List[str]) -> None:
 """
             )
 
-    def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -> TypeDef:
+    def type_loader(
+        self,
+        type_declaration: Union[List[Any], Dict[str, Any], str],
+        container: Optional[str] = None,
+        no_link_check: Optional[bool] = None,
+    ) -> TypeDef:
         """Parse the given type declaration and declare its components."""
         if isinstance(type_declaration, MutableSequence):
             sub_types = [self.type_loader(i) for i in type_declaration]
@@ -424,6 +429,24 @@ def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -
                         init=f"new ArrayLoader<{i.instance_type}>({i.name})",
                     )
                 )
+            if type_declaration["type"] in (
+                "map",
+                "https://w3id.org/cwl/salad#map",
+            ):
+                i = self.type_loader(type_declaration["values"])
+                return self.declare_type(
+                    TypeDef(
+                        instance_type=f"Dictionary<string, {i.instance_type}>",
+                        name=f"map_of_{i.name}",
+                        loader_type=f"ILoader<Dictionary<string, {i.instance_type}>>",
+                        init="new MapLoader<{}>({}, {}, {})".format(
+                            i.instance_type,
+                            i.name,
+                            f"'{container}'" if container is not None else None,
+                            self.to_dotnet(no_link_check),
+                        ),
+                    )
+                )
             if type_declaration["type"] in ("enum", "https://w3id.org/cwl/salad#enum"):
                 return self.type_loader_enum(type_declaration)
             if type_declaration["type"] in (
@@ -434,13 +457,40 @@ def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -
                     TypeDef(
                         instance_type=self.safe_name(type_declaration["name"]),
                         name=self.safe_name(type_declaration["name"]) + "Loader",
-                        init="new RecordLoader<{}>()".format(
+                        init="new RecordLoader<{}>({}, {})".format(
                             self.safe_name(type_declaration["name"]),
+                            f"'{container}'" if container is not None else None,
+                            self.to_dotnet(no_link_check),
                         ),
                         loader_type="ILoader<{}>".format(self.safe_name(type_declaration["name"])),
                         abstract=type_declaration.get("abstract", False),
                     )
                 )
+            if type_declaration["type"] in (
+                "union",
+                "https://w3id.org/cwl/salad#union",
+            ):
+                # Declare the named loader to handle recursive union definitions
+                loader_name = self.safe_name(type_declaration["name"]) + "Loader"
+                loader_type = TypeDef(
+                    name=loader_name,
+                    init="new UnionLoader(new List<ILoader>())",
+                    instance_type="object",
+                    loader_type="ILoader<object>",
+                )
+                self.declare_type(loader_type)
+                # Parse inner types
+                sub_types = [self.type_loader(i) for i in type_declaration["names"]]
+                # Register lazy initialization for the loader
+                self.add_lazy_init(
+                    LazyInitDef(
+                        loader_name,
+                        "((UnionLoader){}).addLoaders(new List<ILoader> {{ {} }});".format(
+                            loader_name, ", ".join(s.name for s in sub_types)
+                        ),
+                    )
+                )
+                return loader_type
             raise SchemaException("wft {}".format(type_declaration["type"]))
         if type_declaration in prims:
             return prims[type_declaration]
@@ -792,7 +842,7 @@ def uri_loader(
         scoped_id: bool,
         vocab_term: bool,
         ref_scope: Optional[int],
-        no_link_check: Optional[bool],
+        no_link_check: Optional[bool] = None,
     ) -> TypeDef:
         """Construct the TypeDef for the given URI loader."""
         instance_type = inner.instance_type or "object"
@@ -902,8 +952,13 @@ def expand_resource_template_to(resource: str, path: Path) -> None:
                     collected_type.loader_type, collected_type.name, collected_type.init
                 )
 
-        example_tests = ""
+        if self.lazy_inits:
+            loader_instances += "\n    static LoaderInstances()\n    {\n"
+            for lazy_init in self.lazy_inits.values():
+                loader_instances += f"        {lazy_init.init}\n"
+            loader_instances += "    }\n"
 
+        example_tests = ""
         if self.examples:
             _safe_makedirs(self.test_resources_dir)
             utils_resources = self.test_resources_dir / "examples"
diff --git a/schema_salad/java/main_utils/ArrayLoader.java b/schema_salad/java/main_utils/ArrayLoader.java
index ca237c470..4341f6d35 100644
--- a/schema_salad/java/main_utils/ArrayLoader.java
+++ b/schema_salad/java/main_utils/ArrayLoader.java
@@ -25,7 +25,8 @@ public List<T> load(
     for (final Object el : docList) {
       try {
         final Object loadedField = unionLoader.loadField(el, baseUri, loadingOptions);
-        if (loadedField instanceof List) {
+        final boolean flatten = !"@list".equals(loadingOptions.container);
+        if (flatten && loadedField instanceof List) {
           r.addAll((List<T>) loadedField);
         } else {
           r.add((T) loadedField);
diff --git a/schema_salad/java/main_utils/LoadingOptions.java b/schema_salad/java/main_utils/LoadingOptions.java
index b66db9860..d1937b370 100644
--- a/schema_salad/java/main_utils/LoadingOptions.java
+++ b/schema_salad/java/main_utils/LoadingOptions.java
@@ -12,6 +12,7 @@ public class LoadingOptions {
   Map<String, String> namespaces;
   List<String> schemas;
   Boolean noLinkCheck;
+  String container;
   Map<String, Object> idx;
   Map<String, String> vocab;
   Map<String, String> rvocab;
@@ -22,12 +23,14 @@ public class LoadingOptions {
       final Map<String, String> namespaces,
       final List<String> schemas,
       final Boolean noLinkCheck,
+      final String container,
       final Map<String, Object> idx) {
     this.fetcher = fetcher;
     this.fileUri = fileUri;
     this.namespaces = namespaces;
     this.schemas = schemas;
     this.noLinkCheck = noLinkCheck;
+    this.container = container;
     this.idx = idx;
 
     if (namespaces != null) {
diff --git a/schema_salad/java/main_utils/LoadingOptionsBuilder.java b/schema_salad/java/main_utils/LoadingOptionsBuilder.java
index 1908cd198..41d3c85ad 100644
--- a/schema_salad/java/main_utils/LoadingOptionsBuilder.java
+++ b/schema_salad/java/main_utils/LoadingOptionsBuilder.java
@@ -12,6 +12,7 @@ public class LoadingOptionsBuilder {
   private Optional<List<String>> schemas = Optional.empty();
   private Optional<LoadingOptions> copyFrom = Optional.empty();
   private Optional<Boolean> noLinkCheck = Optional.empty();
+  private Optional<String> container = Optional.empty();
 
   public LoadingOptionsBuilder() {}
 
@@ -40,12 +41,18 @@ public LoadingOptionsBuilder setNoLinkCheck(final Boolean noLinkCheck) {
     return this;
   }
 
+  public LoadingOptionsBuilder setContainer(final String container) {
+    this.container = Optional.of(container);
+    return this;
+  }
+
   public LoadingOptions build() {
     Fetcher fetcher = this.fetcher.orElse(null);
     String fileUri = this.fileUri.orElse(null);
     List<String> schemas = this.schemas.orElse(null);
     Map<String, String> namespaces = this.namespaces.orElse(null);
     Boolean noLinkCheck = this.noLinkCheck.orElse(null);
+    String container = this.container.orElse(null);
     Map<String, Object> idx = new HashMap<String, Object>();
     if (this.copyFrom.isPresent()) {
       final LoadingOptions copyFrom = this.copyFrom.get();
@@ -63,10 +70,13 @@ public LoadingOptions build() {
       if (noLinkCheck == null) {
         noLinkCheck = copyFrom.noLinkCheck;
       }
+      if (container == null) {
+        container = copyFrom.container;
+      }
     }
     if (fetcher == null) {
       fetcher = new DefaultFetcher();
     }
-    return new LoadingOptions(fetcher, fileUri, namespaces, schemas, noLinkCheck, idx);
+    return new LoadingOptions(fetcher, fileUri, namespaces, schemas, noLinkCheck, container, idx);
   }
 }
diff --git a/schema_salad/java/main_utils/MapLoader.java b/schema_salad/java/main_utils/MapLoader.java
new file mode 100644
index 000000000..5b250846b
--- /dev/null
+++ b/schema_salad/java/main_utils/MapLoader.java
@@ -0,0 +1,51 @@
+package ${package}.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class MapLoader<T> implements Loader<Map<String, T>> {
+  private final Loader<T> valueLoader;
+  private final String container;
+  private final Boolean noLinkCheck;
+
+  public MapLoader(Loader<T> valueLoader, final String container, final Boolean noLinkCheck) {
+    this.valueLoader = valueLoader;
+    this.container = container;
+    this.noLinkCheck = noLinkCheck;
+  }
+
+  public Map<String, T> load(
+      final Object doc,
+      final String baseUri,
+      final LoadingOptions loadingOptions,
+      final String docRoot) {
+    final Map<String, Object> docMap = (Map<String, Object>) Loader.validateOfJavaType(Map.class, doc);
+    LoadingOptions innerLoadingOptions = loadingOptions;
+    if (this.container != null || this.noLinkCheck != null) {
+      LoadingOptionsBuilder builder = new LoadingOptionsBuilder().copiedFrom(loadingOptions);
+      if (this.container != null) {
+        builder.setContainer(this.container);
+      }
+      if (this.noLinkCheck != null) {
+        builder.setNoLinkCheck(this.noLinkCheck);
+      }
+      innerLoadingOptions = builder.build();
+    }
+    final Map<String, T> r = new HashMap();
+    final List<ValidationException> errors = new ArrayList();
+    for (final Map.Entry<String, Object> entry : docMap.entrySet()) {
+      try {
+        final Object loadedField = this.valueLoader.loadField(entry.getValue(), baseUri, innerLoadingOptions);
+        r.put(entry.getKey(), (T) loadedField);
+      } catch (final ValidationException e) {
+        errors.add(e);
+      }
+    }
+    if (!errors.isEmpty()) {
+      throw new ValidationException("", errors);
+    }
+    return r;
+  }
+}
diff --git a/schema_salad/java/main_utils/RecordLoader.java b/schema_salad/java/main_utils/RecordLoader.java
index f8435726e..ad40e9d41 100644
--- a/schema_salad/java/main_utils/RecordLoader.java
+++ b/schema_salad/java/main_utils/RecordLoader.java
@@ -5,9 +5,13 @@
 
 public class RecordLoader<T extends Saveable> implements Loader<T> {
   private final Class<? extends T> saveableClass;
+  private final String container;
+  private final Boolean noLinkCheck;
 
-  public RecordLoader(final Class<? extends T> saveableClass) {
+  public RecordLoader(final Class<? extends T> saveableClass, final String container, final Boolean noLinkCheck) {
     this.saveableClass = saveableClass;
+    this.container = container;
+    this.noLinkCheck = noLinkCheck;
   }
 
   public T load(
@@ -20,7 +24,18 @@ public T load(
       final Constructor<? extends T> constructor =
           this.saveableClass.getConstructor(
               new Class[] {Object.class, String.class, LoadingOptions.class, String.class});
-      final T ret = constructor.newInstance(doc, baseUri, loadingOptions, docRoot);
+      LoadingOptions innerLoadingOptions = loadingOptions;
+      if (this.container != null || this.noLinkCheck != null) {
+        LoadingOptionsBuilder builder = new LoadingOptionsBuilder().copiedFrom(loadingOptions);
+        if (this.container != null) {
+          builder.setContainer(this.container);
+        }
+        if (this.noLinkCheck != null) {
+          builder.setNoLinkCheck(this.noLinkCheck);
+        }
+        innerLoadingOptions = builder.build();
+      }
+      final T ret = constructor.newInstance(doc, baseUri, innerLoadingOptions, docRoot);
       return ret;
     } catch (InvocationTargetException e) {
       final Throwable cause = e.getCause();
diff --git a/schema_salad/java/main_utils/UnionLoader.java b/schema_salad/java/main_utils/UnionLoader.java
index ba58a41f3..f8a24b584 100644
--- a/schema_salad/java/main_utils/UnionLoader.java
+++ b/schema_salad/java/main_utils/UnionLoader.java
@@ -15,6 +15,14 @@ public UnionLoader(Loader[] alternates) {
     this(Arrays.asList(alternates));
   }
 
+  public void addLoaders(List<Loader> loaders) {
+    this.alternates.addAll(loaders);
+  }
+
+   public void addLoaders(Loader[] loaders) {
+    this.addLoaders(Arrays.asList(loaders));
+  }
+
   public Object load(
       final Object doc,
       final String baseUri,
diff --git a/schema_salad/java_codegen.py b/schema_salad/java_codegen.py
index 4bb916a4b..ddf091360 100644
--- a/schema_salad/java_codegen.py
+++ b/schema_salad/java_codegen.py
@@ -19,7 +19,7 @@
 from importlib_resources import files
 
 from . import _logger, schema
-from .codegen_base import CodeGenBase, TypeDef
+from .codegen_base import CodeGenBase, LazyInitDef, TypeDef
 from .exceptions import SchemaException
 from .schema import shortname
 
@@ -373,7 +373,12 @@ def end_class(self, classname: str, field_names: List[str]) -> None:
 """
             )
 
-    def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -> TypeDef:
+    def type_loader(
+        self,
+        type_declaration: Union[List[Any], Dict[str, Any], str],
+        container: Optional[str] = None,
+        no_link_check: Optional[bool] = None,
+    ) -> TypeDef:
         """Parse the given type declaration and declare its components."""
         if isinstance(type_declaration, MutableSequence):
             sub = [self.type_loader(i) for i in type_declaration]
@@ -453,6 +458,25 @@ def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -
                         loader_type=f"Loader<java.util.List<{i.instance_type}>>",
                     )
                 )
+            if type_declaration["type"] in (
+                "map",
+                "https://w3id.org/cwl/salad#map",
+            ):
+                i = self.type_loader(type_declaration["values"])
+                return self.declare_type(
+                    TypeDef(
+                        # special doesn't work out with subclassing, gotta be more clever
+                        # instance_type="Map<String, {}>".format(i.instance_type),
+                        instance_type=f"java.util.Map<String, {i.instance_type}>",
+                        name=f"map_of_{i.name}",
+                        init="new MapLoader({}, {}, {})".format(
+                            i.name,
+                            f"'{container}'" if container is not None else None,
+                            self.to_java(no_link_check),
+                        ),
+                        loader_type=f"Loader<java.util.Map<String, {i.instance_type}>>",
+                    )
+                )
             if type_declaration["type"] in ("enum", "https://w3id.org/cwl/salad#enum"):
                 return self.type_loader_enum(type_declaration)
             if type_declaration["type"] in (
@@ -465,13 +489,84 @@ def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -
                     TypeDef(
                         instance_type=self.safe_name(type_declaration["name"]),
                         name=self.safe_name(type_declaration["name"]),
-                        init="new RecordLoader<{clazz}>({clazz}{ext}.class)".format(
+                        init="new RecordLoader<{clazz}>({clazz}{ext}.class, "
+                        "{container}, {no_link_check})".format(
                             clazz=fqclass,
                             ext="Impl" if not is_abstract else "",
+                            container=f"'{container}'" if container is not None else None,
+                            no_link_check=self.to_java(no_link_check),
                         ),
                         loader_type=f"Loader<{fqclass}>",
                     )
                 )
+            if type_declaration["type"] in (
+                "union",
+                "https://w3id.org/cwl/salad#union",
+            ):
+                # Declare the named loader to handle recursive union definitions
+                loader_name = self.safe_name(type_declaration["name"])
+                loader_type = TypeDef(
+                    instance_type="Object",
+                    init="new UnionLoader(new Loader[] {})",
+                    name=loader_name,
+                    loader_type="Loader<Object>",
+                )
+                self.declare_type(loader_type)
+                # Parse inner types
+                sub = [self.type_loader(i) for i in type_declaration["names"]]
+
+                if len(sub) == 2:
+                    type_1 = sub[0]
+                    type_2 = sub[1]
+                    type_1_name = type_1.name
+                    type_2_name = type_2.name
+                    if type_1_name == "NullInstance" or type_2_name == "NullInstance":
+                        non_null_type = type_1 if type_1.name != "NullInstance" else type_2
+                        sub = [
+                            TypeDef(
+                                instance_type="java.util.Optional<{}>".format(
+                                    non_null_type.instance_type
+                                ),
+                                init=f"new OptionalLoader({non_null_type.name})",
+                                name=f"optional_{non_null_type.name}",
+                                loader_type="Loader<java.util.Optional<{}>>".format(
+                                    non_null_type.instance_type
+                                ),
+                            )
+                        ]
+                    elif (
+                        type_1_name == f"array_of_{type_2_name}"
+                        or type_2_name == f"array_of_{type_1_name}"
+                    ) and USE_ONE_OR_LIST_OF_TYPES:
+                        if type_1_name == f"array_of_{type_2_name}":
+                            single_type = type_2
+                            array_type = type_1
+                        else:
+                            single_type = type_1
+                            array_type = type_2
+                        fqclass = f"{self.package}.{single_type.instance_type}"
+                        sub = [
+                            TypeDef(
+                                instance_type=f"{self.package}.utils.OneOrListOf<{fqclass}>",
+                                init="new OneOrListOfLoader<{}>({}, {})".format(
+                                    fqclass, single_type.name, array_type.name
+                                ),
+                                name=f"one_or_array_of_{single_type.name}",
+                                loader_type="Loader<{}.utils.OneOrListOf<{}>>".format(
+                                    self.package, fqclass
+                                ),
+                            )
+                        ]
+                # Register lazy initialization for the loader
+                self.add_lazy_init(
+                    LazyInitDef(
+                        loader_name,
+                        "((UnionLoader) {}).addLoaders(new Loader[] {{ {} }});".format(
+                            loader_name, ", ".join(s.name for s in sub)
+                        ),
+                    )
+                )
+                return loader_type
             raise SchemaException("wft {}".format(type_declaration["type"]))
         if type_declaration in prims:
             return prims[type_declaration]
@@ -715,7 +810,7 @@ def uri_loader(
         scoped_id: bool,
         vocab_term: bool,
         ref_scope: Optional[int],
-        no_link_check: Optional[bool],
+        no_link_check: Optional[bool] = None,
     ) -> TypeDef:
         instance_type = inner.instance_type or "Object"
         return self.declare_type(
@@ -825,6 +920,12 @@ def expand_resource_template_to(resource: str, path: Path) -> None:
                 collected_type.loader_type, collected_type.name, collected_type.init
             )
 
+        if self.lazy_inits:
+            loader_instances += "\n  static {\n"
+            for lazy_init in self.lazy_inits.values():
+                loader_instances += f"    {lazy_init.init}\n"
+            loader_instances += "  }\n"
+
         example_tests = ""
         if self.examples:
             _safe_makedirs(self.test_resources_dir)
diff --git a/schema_salad/jsonld_context.py b/schema_salad/jsonld_context.py
index 95a9cb31b..2554b8008 100755
--- a/schema_salad/jsonld_context.py
+++ b/schema_salad/jsonld_context.py
@@ -33,10 +33,10 @@ def pred(
     context: ContextType,
     defaultBase: str,
     namespaces: Dict[str, rdflib.namespace.Namespace],
-) -> Union[Dict[str, Union[str, None]], str]:
+) -> Union[Dict[str, Optional[str]], str]:
     split = urlsplit(name)
 
-    vee = None  # type: Optional[str]
+    vee: Optional[str] = None
 
     if split.scheme != "":
         vee = name
@@ -46,7 +46,7 @@ def pred(
             vee = str(namespaces[ns[0:-1]][ln])
         _logger.debug("name, v %s %s", name, vee)
 
-    v = None  # type: Optional[Union[Dict[str, Union[str, None]], str]]
+    v: Optional[Union[Dict[str, Optional[str]], str]] = None
 
     if field is not None and "jsonldPredicate" in field:
         if isinstance(field["jsonldPredicate"], MutableMapping):
@@ -132,9 +132,9 @@ def process_type(
 
             _logger.debug("Processing field %s", i)
 
-            v = pred(
+            v: Union[Dict[Any, Any], str, None] = pred(
                 t, i, fieldname, context, defaultPrefix, namespaces
-            )  # type: Union[Dict[Any, Any], str, None]
+            )
 
             if isinstance(v, str):
                 v = v if v[0] != "@" else None
@@ -189,7 +189,7 @@ def process_type(
 def salad_to_jsonld_context(
     j: Iterable[MutableMapping[str, Any]], schema_ctx: MutableMapping[str, Any]
 ) -> Tuple[ContextType, Graph]:
-    context = {}  # type: ContextType
+    context: ContextType = {}
     namespaces = {}
     g = Graph()
     defaultPrefix = ""
diff --git a/schema_salad/makedoc.py b/schema_salad/makedoc.py
index bb3cab10e..b3adb120e 100644
--- a/schema_salad/makedoc.py
+++ b/schema_salad/makedoc.py
@@ -416,17 +416,21 @@ def __init__(
                     fields: Union[str, List[Dict[str, str]]] = entry.get("fields", [])
                     if isinstance(fields, str):
                         raise KeyError("record fields must be a list of mappings")
-                    for f in fields:  # type: Dict[str, str]
-                        p = has_types(f)
-                        for tp in p:
-                            if tp not in self.uses:
-                                self.uses[tp] = []
-                            if (entry["name"], f["name"]) not in self.uses[tp]:
-                                _, frg1 = urldefrag(t["name"])
-                                _, frg2 = urldefrag(f["name"])
-                                self.uses[tp].append((frg1, frg2))
-                            if tp not in basicTypes and tp not in self.record_refs[entry["name"]]:
-                                self.record_refs[entry["name"]].append(tp)
+                    else:
+                        for f in fields:
+                            p = has_types(f)
+                            for tp in p:
+                                if tp not in self.uses:
+                                    self.uses[tp] = []
+                                if (entry["name"], f["name"]) not in self.uses[tp]:
+                                    _, frg1 = urldefrag(t["name"])
+                                    _, frg2 = urldefrag(f["name"])
+                                    self.uses[tp].append((frg1, frg2))
+                                if (
+                                    tp not in basicTypes
+                                    and tp not in self.record_refs[entry["name"]]
+                                ):
+                                    self.record_refs[entry["name"]].append(tp)
             except KeyError:
                 _logger.error("Did not find 'type' in %s", t)
                 _logger.error("record refs is %s", self.record_refs)
diff --git a/schema_salad/metaschema.py b/schema_salad/metaschema.py
index e6eb44f3a..6062b6ed9 100644
--- a/schema_salad/metaschema.py
+++ b/schema_salad/metaschema.py
@@ -11,6 +11,7 @@
 import xml.sax  # nosec
 from abc import ABC, abstractmethod
 from io import StringIO
+from itertools import chain
 from typing import (
     Any,
     Dict,
@@ -59,6 +60,8 @@ class LoadingOptions:
     cache: CacheType
     imports: List[str]
     includes: List[str]
+    no_link_check: Optional[bool]
+    container: Optional[str]
 
     def __init__(
         self,
@@ -73,7 +76,8 @@ def __init__(
         idx: Optional[IdxType] = None,
         imports: Optional[List[str]] = None,
         includes: Optional[List[str]] = None,
-        no_link_check: bool = False,
+        no_link_check: Optional[bool] = None,
+        container: Optional[str] = None,
     ) -> None:
         """Create a LoadingOptions object."""
         self.original_doc = original_doc
@@ -118,7 +122,15 @@ def __init__(
         else:
             self.includes = copyfrom.includes if copyfrom is not None else []
 
-        self.no_link_check = no_link_check
+        if no_link_check is not None:
+            self.no_link_check = no_link_check
+        else:
+            self.no_link_check = copyfrom.no_link_check if copyfrom is not None else False
+
+        if container is not None:
+            self.container = container
+        else:
+            self.container = copyfrom.container if copyfrom is not None else None
 
         if fetcher is not None:
             self.fetcher = fetcher
@@ -141,10 +153,10 @@ def __init__(
         self.vocab = _vocab
         self.rvocab = _rvocab
 
-        if namespaces is not None:
+        if self.namespaces is not None:
             self.vocab = self.vocab.copy()
             self.rvocab = self.rvocab.copy()
-            for k, v in namespaces.items():
+            for k, v in self.namespaces.items():
                 self.vocab[k] = v
                 self.rvocab[v] = k
 
@@ -207,8 +219,13 @@ def save(
         """Convert this object to a JSON/YAML friendly dictionary."""
 
 
-def load_field(val, fieldtype, baseuri, loadingOptions, lc=None):
-    # type: (Union[str, Dict[str, str]], _Loader, str, LoadingOptions, Optional[List[Any]]) -> Any
+def load_field(
+    val: Union[str, Dict[str, str]],
+    fieldtype: "_Loader",
+    baseuri: str,
+    loadingOptions: LoadingOptions,
+    lc: Optional[List[Any]] = None,
+) -> Any:
     """Load field."""
     if isinstance(val, MutableMapping):
         if "$import" in val:
@@ -340,14 +357,13 @@ def save_with_metadata(
 
 
 def expand_url(
-    url,  # type: str
-    base_url,  # type: str
-    loadingOptions,  # type: LoadingOptions
-    scoped_id=False,  # type: bool
-    vocab_term=False,  # type: bool
-    scoped_ref=None,  # type: Optional[int]
-):
-    # type: (...) -> str
+    url: str,
+    base_url: str,
+    loadingOptions: LoadingOptions,
+    scoped_id: bool = False,
+    vocab_term: bool = False,
+    scoped_ref: Optional[int] = None,
+) -> str:
     if url in ("@id", "@type"):
         return url
 
@@ -408,58 +424,80 @@ def expand_url(
 
 
 class _Loader:
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         pass
 
 
 class _AnyLoader(_Loader):
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if doc is not None:
             return doc
         raise ValidationException("Expected non-null")
 
 
 class _PrimitiveLoader(_Loader):
-    def __init__(self, tp):
-        # type: (Union[type, Tuple[Type[str], Type[str]]]) -> None
+    def __init__(self, tp: Union[type, Tuple[Type[str], Type[str]]]) -> None:
         self.tp = tp
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if not isinstance(doc, self.tp):
             raise ValidationException(
                 "Expected a {} but got {}".format(self.tp, doc.__class__.__name__)
             )
         return doc
 
-    def __repr__(self):  # type: () -> str
+    def __repr__(self) -> str:
         return str(self.tp)
 
 
 class _ArrayLoader(_Loader):
-    def __init__(self, items):
-        # type: (_Loader) -> None
+    def __init__(self, items: _Loader) -> None:
         self.items = items
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if not isinstance(doc, MutableSequence):
             raise ValidationException(
                 f"Value is a {convert_typing(extract_type(type(doc)))}, "
                 f"but valid type for this field is an array."
             )
-        r = []  # type: List[Any]
-        errors = []  # type: List[SchemaSaladException]
-        fields = []  # type: List[str]
-
+        r: List[Any] = []
+        errors: List[SchemaSaladException] = []
+        fields: List[str] = []
         for i in range(0, len(doc)):
             try:
                 lf = load_field(
                     doc[i], _UnionLoader(([self, self.items])), baseuri, loadingOptions, lc=lc
                 )
-                if isinstance(lf, MutableSequence):
+                flatten = loadingOptions.container != "@list"
+                if flatten and isinstance(lf, MutableSequence):
                     r.extend(lf)
                 else:
                     r.append(lf)
@@ -486,32 +524,86 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
             raise ValidationException("", None, errors)
         return r
 
-    def __repr__(self):  # type: () -> str
+    def __repr__(self) -> str:
         return f"array<{self.items}>"
 
 
+class _MapLoader(_Loader):
+    def __init__(
+        self,
+        values: _Loader,
+        name: Optional[str] = None,
+        container: Optional[str] = None,
+        no_link_check: Optional[bool] = None,
+    ) -> None:
+        self.values = values
+        self.name = name
+        self.container = container
+        self.no_link_check = no_link_check
+
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
+        if not isinstance(doc, MutableMapping):
+            raise ValidationException(f"Expected a map, was {type(doc)}")
+        if self.container is not None or self.no_link_check is not None:
+            loadingOptions = LoadingOptions(
+                copyfrom=loadingOptions, container=self.container, no_link_check=self.no_link_check
+            )
+        r: Dict[str, Any] = {}
+        errors: List[SchemaSaladException] = []
+        for k, v in doc.items():
+            try:
+                lf = load_field(v, self.values, baseuri, loadingOptions, lc)
+                r[k] = lf
+            except ValidationException as e:
+                errors.append(e.with_sourceline(SourceLine(doc, k, str)))
+        if errors:
+            raise ValidationException("", None, errors)
+        return r
+
+    def __repr__(self) -> str:
+        return self.name if self.name is not None else f"map<string, {self.values}>"
+
+
 class _EnumLoader(_Loader):
     def __init__(self, symbols: Sequence[str], name: str) -> None:
         self.symbols = symbols
         self.name = name
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if doc in self.symbols:
             return doc
         raise ValidationException(f"Expected one of {self.symbols}")
 
-    def __repr__(self):  # type: () -> str
+    def __repr__(self) -> str:
         return self.name
 
 
 class _SecondaryDSLLoader(_Loader):
-    def __init__(self, inner):
-        # type: (_Loader) -> None
+    def __init__(self, inner: _Loader) -> None:
         self.inner = inner
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         r: List[Dict[str, Any]] = []
         if isinstance(doc, MutableSequence):
             for d in doc:
@@ -573,20 +665,36 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
 
 
 class _RecordLoader(_Loader):
-    def __init__(self, classtype):
-        # type: (Type[Saveable]) -> None
+    def __init__(
+        self,
+        classtype: Type[Saveable],
+        container: Optional[str] = None,
+        no_link_check: Optional[bool] = None,
+    ) -> None:
         self.classtype = classtype
+        self.container = container
+        self.no_link_check = no_link_check
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if not isinstance(doc, MutableMapping):
             raise ValidationException(
                 f"Value is a {convert_typing(extract_type(type(doc)))}, "
                 f"but valid type for this field is an object."
             )
+        if self.container is not None or self.no_link_check is not None:
+            loadingOptions = LoadingOptions(
+                copyfrom=loadingOptions, container=self.container, no_link_check=self.no_link_check
+            )
         return self.classtype.fromDoc(doc, baseuri, loadingOptions, docRoot=docRoot)
 
-    def __repr__(self):  # type: () -> str
+    def __repr__(self) -> str:
         return str(self.classtype.__name__)
 
 
@@ -594,8 +702,14 @@ class _ExpressionLoader(_Loader):
     def __init__(self, items: Type[str]) -> None:
         self.items = items
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if not isinstance(doc, str):
             raise ValidationException(
                 f"Value is a {convert_typing(extract_type(type(doc)))}, "
@@ -605,11 +719,21 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
 
 
 class _UnionLoader(_Loader):
-    def __init__(self, alternates: Sequence[_Loader]) -> None:
+    def __init__(self, alternates: Sequence[_Loader], name: Optional[str] = None) -> None:
         self.alternates = alternates
+        self.name = name
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def add_loaders(self, loaders: Sequence[_Loader]) -> None:
+        self.alternates = tuple(loader for loader in chain(self.alternates, loaders))
+
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         errors = []
 
         if lc is None:
@@ -680,21 +804,33 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
                 )
         raise ValidationException("", None, errors, "*")
 
-    def __repr__(self):  # type: () -> str
-        return " | ".join(str(a) for a in self.alternates)
+    def __repr__(self) -> str:
+        return self.name if self.name is not None else " | ".join(str(a) for a in self.alternates)
 
 
 class _URILoader(_Loader):
-    def __init__(self, inner, scoped_id, vocab_term, scoped_ref, no_link_check):
-        # type: (_Loader, bool, bool, Union[int, None], Union[bool, None]) -> None
+    def __init__(
+        self,
+        inner: _Loader,
+        scoped_id: bool,
+        vocab_term: bool,
+        scoped_ref: Optional[int],
+        no_link_check: Optional[bool],
+    ) -> None:
         self.inner = inner
         self.scoped_id = scoped_id
         self.vocab_term = vocab_term
         self.scoped_ref = scoped_ref
         self.no_link_check = no_link_check
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if self.no_link_check is not None:
             loadingOptions = LoadingOptions(
                 copyfrom=loadingOptions, no_link_check=self.no_link_check
@@ -741,19 +877,17 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
 
 
 class _TypeDSLLoader(_Loader):
-    def __init__(self, inner, refScope, salad_version):
-        # type: (_Loader, Union[int, None], str) -> None
+    def __init__(self, inner: _Loader, refScope: Optional[int], salad_version: str) -> None:
         self.inner = inner
         self.refScope = refScope
         self.salad_version = salad_version
 
     def resolve(
         self,
-        doc,  # type: str
-        baseuri,  # type: str
-        loadingOptions,  # type: LoadingOptions
-    ):
-        # type: (...) -> Union[List[Union[Dict[str, Any], str]], Dict[str, Any], str]
+        doc: str,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+    ) -> Union[List[Union[Dict[str, Any], str]], Dict[str, Any], str]:
         doc_ = doc
         optional = False
         if doc_.endswith("?"):
@@ -762,7 +896,7 @@ def resolve(
 
         if doc_.endswith("[]"):
             salad_versions = [int(v) for v in self.salad_version[1:].split(".")]
-            items = ""  # type: Union[List[Union[Dict[str, Any], str]], Dict[str, Any], str]
+            items: Union[List[Union[Dict[str, Any], str]], Dict[str, Any], str] = ""
             rest = doc_[0:-2]
             if salad_versions < [1, 3]:
                 if rest.endswith("[]"):
@@ -774,7 +908,7 @@ def resolve(
                 items = self.resolve(rest, baseuri, loadingOptions)
                 if isinstance(items, str):
                     items = expand_url(items, baseuri, loadingOptions, False, True, self.refScope)
-            expanded = {"type": "array", "items": items}  # type: Union[Dict[str, Any], str]
+            expanded: Union[Dict[str, Any], str] = {"type": "array", "items": items}
         else:
             expanded = expand_url(doc_, baseuri, loadingOptions, False, True, self.refScope)
 
@@ -783,10 +917,16 @@ def resolve(
         else:
             return expanded
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if isinstance(doc, MutableSequence):
-            r = []  # type: List[Any]
+            r: List[Any] = []
             for d in doc:
                 if isinstance(d, str):
                     resolved = self.resolve(d, baseuri, loadingOptions)
@@ -807,16 +947,21 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
 
 
 class _IdMapLoader(_Loader):
-    def __init__(self, inner, mapSubject, mapPredicate):
-        # type: (_Loader, str, Union[str, None]) -> None
+    def __init__(self, inner: _Loader, mapSubject: str, mapPredicate: Optional[str]) -> None:
         self.inner = inner
         self.mapSubject = mapSubject
         self.mapPredicate = mapPredicate
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if isinstance(doc, MutableMapping):
-            r = []  # type: List[Any]
+            r: List[Any] = []
             for k in sorted(doc.keys()):
                 val = doc[k]
                 if isinstance(val, CommentedMap):
@@ -941,7 +1086,8 @@ def _document_load_by_url(
     return loadingOptions.idx[url]
 
 
-def file_uri(path, split_frag=False):  # type: (str, bool) -> str
+def file_uri(path: str, split_frag: bool = False) -> str:
+    """Transform a file path into a URL with file scheme."""
     if path.startswith("file://"):
         return path
     if split_frag:
@@ -1076,9 +1222,6 @@ def fromDoc(
         _errors__ = []
         if "name" in _doc:
             try:
-                if _doc.get("name") is None:
-                    raise ValidationException("missing required field `name`", None, [])
-
                 name = load_field(
                     _doc.get("name"),
                     uri_strtype_True_False_None_None,
@@ -1130,9 +1273,6 @@ def fromDoc(
             baseuri = name
         if "doc" in _doc:
             try:
-                if _doc.get("doc") is None:
-                    raise ValidationException("missing required field `doc`", None, [])
-
                 doc = load_field(
                     _doc.get("doc"),
                     union_of_None_type_or_strtype_or_array_of_strtype,
@@ -1179,7 +1319,7 @@ def fromDoc(
 
             type_ = load_field(
                 _doc.get("type"),
-                typedsl_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_2,
+                typedsl_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_2,
                 baseuri,
                 loadingOptions,
                 lc=_doc.get("type")
@@ -1326,9 +1466,6 @@ def fromDoc(
         _errors__ = []
         if "fields" in _doc:
             try:
-                if _doc.get("fields") is None:
-                    raise ValidationException("missing required field `fields`", None, [])
-
                 fields = load_field(
                     _doc.get("fields"),
                     idmap_fields_union_of_None_type_or_array_of_RecordFieldLoader,
@@ -1528,9 +1665,6 @@ def fromDoc(
         _errors__ = []
         if "name" in _doc:
             try:
-                if _doc.get("name") is None:
-                    raise ValidationException("missing required field `name`", None, [])
-
                 name = load_field(
                     _doc.get("name"),
                     uri_union_of_None_type_or_strtype_True_False_None_None,
@@ -1778,7 +1912,7 @@ def fromDoc(
 
             items = load_field(
                 _doc.get("items"),
-                uri_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_False_True_2_None,
+                uri_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_False_True_2_None,
                 baseuri,
                 loadingOptions,
                 lc=_doc.get("items")
@@ -1918,26 +2052,11 @@ def save(
     attrs = frozenset(["items", "type"])
 
 
-class JsonldPredicate(Saveable):
-    """
-    Attached to a record field to define how the parent record field is handled for
-    URI resolution and JSON-LD context generation.
-
-    """
-
+class MapSchema(Saveable):
     def __init__(
         self,
-        _id: Optional[Any] = None,
-        _type: Optional[Any] = None,
-        _container: Optional[Any] = None,
-        identity: Optional[Any] = None,
-        noLinkCheck: Optional[Any] = None,
-        mapSubject: Optional[Any] = None,
-        mapPredicate: Optional[Any] = None,
-        refScope: Optional[Any] = None,
-        typeDSL: Optional[Any] = None,
-        secondaryFilesDSL: Optional[Any] = None,
-        subscope: Optional[Any] = None,
+        type_: Any,
+        values: Any,
         extension_fields: Optional[Dict[str, Any]] = None,
         loadingOptions: Optional[LoadingOptions] = None,
     ) -> None:
@@ -1949,51 +2068,16 @@ def __init__(
             self.loadingOptions = loadingOptions
         else:
             self.loadingOptions = LoadingOptions()
-        self._id = _id
-        self._type = _type
-        self._container = _container
-        self.identity = identity
-        self.noLinkCheck = noLinkCheck
-        self.mapSubject = mapSubject
-        self.mapPredicate = mapPredicate
-        self.refScope = refScope
-        self.typeDSL = typeDSL
-        self.secondaryFilesDSL = secondaryFilesDSL
-        self.subscope = subscope
+        self.type_ = type_
+        self.values = values
 
     def __eq__(self, other: Any) -> bool:
-        if isinstance(other, JsonldPredicate):
-            return bool(
-                self._id == other._id
-                and self._type == other._type
-                and self._container == other._container
-                and self.identity == other.identity
-                and self.noLinkCheck == other.noLinkCheck
-                and self.mapSubject == other.mapSubject
-                and self.mapPredicate == other.mapPredicate
-                and self.refScope == other.refScope
-                and self.typeDSL == other.typeDSL
-                and self.secondaryFilesDSL == other.secondaryFilesDSL
-                and self.subscope == other.subscope
-            )
+        if isinstance(other, MapSchema):
+            return bool(self.type_ == other.type_ and self.values == other.values)
         return False
 
     def __hash__(self) -> int:
-        return hash(
-            (
-                self._id,
-                self._type,
-                self._container,
-                self.identity,
-                self.noLinkCheck,
-                self.mapSubject,
-                self.mapPredicate,
-                self.refScope,
-                self.typeDSL,
-                self.secondaryFilesDSL,
-                self.subscope,
-            )
-        )
+        return hash((self.type_, self.values))
 
     @classmethod
     def fromDoc(
@@ -2002,120 +2086,1886 @@ def fromDoc(
         baseuri: str,
         loadingOptions: LoadingOptions,
         docRoot: Optional[str] = None
-    ) -> "JsonldPredicate":
+    ) -> "MapSchema":
         _doc = copy.copy(doc)
 
         if hasattr(doc, "lc"):
             _doc.lc.data = doc.lc.data
             _doc.lc.filename = doc.lc.filename
         _errors__ = []
-        if "_id" in _doc:
-            try:
-                if _doc.get("_id") is None:
-                    raise ValidationException("missing required field `_id`", None, [])
+        try:
+            if _doc.get("type") is None:
+                raise ValidationException("missing required field `type`", None, [])
 
-                _id = load_field(
-                    _doc.get("_id"),
-                    uri_union_of_None_type_or_strtype_True_False_None_None,
-                    baseuri,
-                    loadingOptions,
-                    lc=_doc.get("_id")
-                )
+            type_ = load_field(
+                _doc.get("type"),
+                typedsl_Map_nameLoader_2,
+                baseuri,
+                loadingOptions,
+                lc=_doc.get("type")
+            )
 
-            except ValidationException as e:
-                error_message, to_print, verb_tensage = parse_errors(str(e))
+        except ValidationException as e:
+            error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `_id`":
+            if str(e) == "missing required field `type`":
+                _errors__.append(
+                    ValidationException(
+                        str(e),
+                        None
+                    )
+                )
+            else:
+                if error_message != str(e):
+                    val_type = convert_typing(extract_type(type(_doc.get("type"))))
                     _errors__.append(
                         ValidationException(
-                            str(e),
-                            None
+                            "the `type` field is not valid because:",
+                            SourceLine(_doc, "type", str),
+                            [ValidationException(f"Value is a {val_type}, "
+                                                 f"but valid {to_print} for this field "
+                                                 f"{verb_tensage} {error_message}")],
                         )
                     )
                 else:
-                    if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("_id"))))
-                        _errors__.append(
-                            ValidationException(
-                                "the `_id` field is not valid because:",
-                                SourceLine(_doc, "_id", str),
-                                [ValidationException(f"Value is a {val_type}, "
-                                                     f"but valid {to_print} for this field "
-                                                     f"{verb_tensage} {error_message}")],
-                            )
-                        )
-                    else:
-                        _errors__.append(
-                            ValidationException(
-                                "the `_id` field is not valid because:",
-                                SourceLine(_doc, "_id", str),
-                                [e],
-                            )
-                        )
-        else:
-            _id = None
-        if "_type" in _doc:
-            try:
-                if _doc.get("_type") is None:
-                    raise ValidationException("missing required field `_type`", None, [])
-
-                _type = load_field(
-                    _doc.get("_type"),
-                    union_of_None_type_or_strtype,
-                    baseuri,
-                    loadingOptions,
-                    lc=_doc.get("_type")
-                )
-
-            except ValidationException as e:
-                error_message, to_print, verb_tensage = parse_errors(str(e))
-
-                if str(e) == "missing required field `_type`":
                     _errors__.append(
                         ValidationException(
-                            str(e),
-                            None
+                            "the `type` field is not valid because:",
+                            SourceLine(_doc, "type", str),
+                            [e],
                         )
                     )
-                else:
+        try:
+            if _doc.get("values") is None:
+                raise ValidationException("missing required field `values`", None, [])
+
+            values = load_field(
+                _doc.get("values"),
+                uri_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_False_True_2_None,
+                baseuri,
+                loadingOptions,
+                lc=_doc.get("values")
+            )
+
+        except ValidationException as e:
+            error_message, to_print, verb_tensage = parse_errors(str(e))
+
+            if str(e) == "missing required field `values`":
+                _errors__.append(
+                    ValidationException(
+                        str(e),
+                        None
+                    )
+                )
+            else:
+                if error_message != str(e):
+                    val_type = convert_typing(extract_type(type(_doc.get("values"))))
+                    _errors__.append(
+                        ValidationException(
+                            "the `values` field is not valid because:",
+                            SourceLine(_doc, "values", str),
+                            [ValidationException(f"Value is a {val_type}, "
+                                                 f"but valid {to_print} for this field "
+                                                 f"{verb_tensage} {error_message}")],
+                        )
+                    )
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "the `values` field is not valid because:",
+                            SourceLine(_doc, "values", str),
+                            [e],
+                        )
+                    )
+        extension_fields: Dict[str, Any] = {}
+        for k in _doc.keys():
+            if k not in cls.attrs:
+                if not k:
+                    _errors__.append(
+                        ValidationException("mapping with implicit null key")
+                    )
+                elif ":" in k:
+                    ex = expand_url(
+                        k, "", loadingOptions, scoped_id=False, vocab_term=False
+                    )
+                    extension_fields[ex] = _doc[k]
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "invalid field `{}`, expected one of: `type`, `values`".format(
+                                k
+                            ),
+                            SourceLine(_doc, k, str),
+                        )
+                    )
+
+        if _errors__:
+            raise ValidationException("", None, _errors__, "*")
+        _constructed = cls(
+            type_=type_,
+            values=values,
+            extension_fields=extension_fields,
+            loadingOptions=loadingOptions,
+        )
+        return _constructed
+
+    def save(
+        self, top: bool = False, base_url: str = "", relative_uris: bool = True
+    ) -> Dict[str, Any]:
+        r: Dict[str, Any] = {}
+
+        if relative_uris:
+            for ef in self.extension_fields:
+                r[prefix_url(ef, self.loadingOptions.vocab)] = self.extension_fields[ef]
+        else:
+            for ef in self.extension_fields:
+                r[ef] = self.extension_fields[ef]
+        if self.type_ is not None:
+            r["type"] = save(
+                self.type_, top=False, base_url=base_url, relative_uris=relative_uris
+            )
+        if self.values is not None:
+            u = save_relative_uri(self.values, base_url, False, 2, relative_uris)
+            r["values"] = u
+
+        # top refers to the directory level
+        if top:
+            if self.loadingOptions.namespaces:
+                r["$namespaces"] = self.loadingOptions.namespaces
+            if self.loadingOptions.schemas:
+                r["$schemas"] = self.loadingOptions.schemas
+        return r
+
+    attrs = frozenset(["type", "values"])
+
+
+class UnionSchema(Saveable):
+    def __init__(
+        self,
+        names: Any,
+        type_: Any,
+        extension_fields: Optional[Dict[str, Any]] = None,
+        loadingOptions: Optional[LoadingOptions] = None,
+    ) -> None:
+        if extension_fields:
+            self.extension_fields = extension_fields
+        else:
+            self.extension_fields = CommentedMap()
+        if loadingOptions:
+            self.loadingOptions = loadingOptions
+        else:
+            self.loadingOptions = LoadingOptions()
+        self.names = names
+        self.type_ = type_
+
+    def __eq__(self, other: Any) -> bool:
+        if isinstance(other, UnionSchema):
+            return bool(self.names == other.names and self.type_ == other.type_)
+        return False
+
+    def __hash__(self) -> int:
+        return hash((self.names, self.type_))
+
+    @classmethod
+    def fromDoc(
+        cls,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None
+    ) -> "UnionSchema":
+        _doc = copy.copy(doc)
+
+        if hasattr(doc, "lc"):
+            _doc.lc.data = doc.lc.data
+            _doc.lc.filename = doc.lc.filename
+        _errors__ = []
+        try:
+            if _doc.get("names") is None:
+                raise ValidationException("missing required field `names`", None, [])
+
+            names = load_field(
+                _doc.get("names"),
+                uri_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_False_True_2_None,
+                baseuri,
+                loadingOptions,
+                lc=_doc.get("names")
+            )
+
+        except ValidationException as e:
+            error_message, to_print, verb_tensage = parse_errors(str(e))
+
+            if str(e) == "missing required field `names`":
+                _errors__.append(
+                    ValidationException(
+                        str(e),
+                        None
+                    )
+                )
+            else:
+                if error_message != str(e):
+                    val_type = convert_typing(extract_type(type(_doc.get("names"))))
+                    _errors__.append(
+                        ValidationException(
+                            "the `names` field is not valid because:",
+                            SourceLine(_doc, "names", str),
+                            [ValidationException(f"Value is a {val_type}, "
+                                                 f"but valid {to_print} for this field "
+                                                 f"{verb_tensage} {error_message}")],
+                        )
+                    )
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "the `names` field is not valid because:",
+                            SourceLine(_doc, "names", str),
+                            [e],
+                        )
+                    )
+        try:
+            if _doc.get("type") is None:
+                raise ValidationException("missing required field `type`", None, [])
+
+            type_ = load_field(
+                _doc.get("type"),
+                typedsl_Union_nameLoader_2,
+                baseuri,
+                loadingOptions,
+                lc=_doc.get("type")
+            )
+
+        except ValidationException as e:
+            error_message, to_print, verb_tensage = parse_errors(str(e))
+
+            if str(e) == "missing required field `type`":
+                _errors__.append(
+                    ValidationException(
+                        str(e),
+                        None
+                    )
+                )
+            else:
+                if error_message != str(e):
+                    val_type = convert_typing(extract_type(type(_doc.get("type"))))
+                    _errors__.append(
+                        ValidationException(
+                            "the `type` field is not valid because:",
+                            SourceLine(_doc, "type", str),
+                            [ValidationException(f"Value is a {val_type}, "
+                                                 f"but valid {to_print} for this field "
+                                                 f"{verb_tensage} {error_message}")],
+                        )
+                    )
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "the `type` field is not valid because:",
+                            SourceLine(_doc, "type", str),
+                            [e],
+                        )
+                    )
+        extension_fields: Dict[str, Any] = {}
+        for k in _doc.keys():
+            if k not in cls.attrs:
+                if not k:
+                    _errors__.append(
+                        ValidationException("mapping with implicit null key")
+                    )
+                elif ":" in k:
+                    ex = expand_url(
+                        k, "", loadingOptions, scoped_id=False, vocab_term=False
+                    )
+                    extension_fields[ex] = _doc[k]
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "invalid field `{}`, expected one of: `names`, `type`".format(
+                                k
+                            ),
+                            SourceLine(_doc, k, str),
+                        )
+                    )
+
+        if _errors__:
+            raise ValidationException("", None, _errors__, "*")
+        _constructed = cls(
+            names=names,
+            type_=type_,
+            extension_fields=extension_fields,
+            loadingOptions=loadingOptions,
+        )
+        return _constructed
+
+    def save(
+        self, top: bool = False, base_url: str = "", relative_uris: bool = True
+    ) -> Dict[str, Any]:
+        r: Dict[str, Any] = {}
+
+        if relative_uris:
+            for ef in self.extension_fields:
+                r[prefix_url(ef, self.loadingOptions.vocab)] = self.extension_fields[ef]
+        else:
+            for ef in self.extension_fields:
+                r[ef] = self.extension_fields[ef]
+        if self.names is not None:
+            u = save_relative_uri(self.names, base_url, False, 2, relative_uris)
+            r["names"] = u
+        if self.type_ is not None:
+            r["type"] = save(
+                self.type_, top=False, base_url=base_url, relative_uris=relative_uris
+            )
+
+        # top refers to the directory level
+        if top:
+            if self.loadingOptions.namespaces:
+                r["$namespaces"] = self.loadingOptions.namespaces
+            if self.loadingOptions.schemas:
+                r["$schemas"] = self.loadingOptions.schemas
+        return r
+
+    attrs = frozenset(["names", "type"])
+
+
+class JsonldPredicate(Saveable):
+    """
+    Attached to a record field to define how the parent record field is handled for
+    URI resolution and JSON-LD context generation.
+
+    """
+
+    def __init__(
+        self,
+        _id: Optional[Any] = None,
+        _type: Optional[Any] = None,
+        _container: Optional[Any] = None,
+        identity: Optional[Any] = None,
+        noLinkCheck: Optional[Any] = None,
+        mapSubject: Optional[Any] = None,
+        mapPredicate: Optional[Any] = None,
+        refScope: Optional[Any] = None,
+        typeDSL: Optional[Any] = None,
+        secondaryFilesDSL: Optional[Any] = None,
+        subscope: Optional[Any] = None,
+        extension_fields: Optional[Dict[str, Any]] = None,
+        loadingOptions: Optional[LoadingOptions] = None,
+    ) -> None:
+        if extension_fields:
+            self.extension_fields = extension_fields
+        else:
+            self.extension_fields = CommentedMap()
+        if loadingOptions:
+            self.loadingOptions = loadingOptions
+        else:
+            self.loadingOptions = LoadingOptions()
+        self._id = _id
+        self._type = _type
+        self._container = _container
+        self.identity = identity
+        self.noLinkCheck = noLinkCheck
+        self.mapSubject = mapSubject
+        self.mapPredicate = mapPredicate
+        self.refScope = refScope
+        self.typeDSL = typeDSL
+        self.secondaryFilesDSL = secondaryFilesDSL
+        self.subscope = subscope
+
+    def __eq__(self, other: Any) -> bool:
+        if isinstance(other, JsonldPredicate):
+            return bool(
+                self._id == other._id
+                and self._type == other._type
+                and self._container == other._container
+                and self.identity == other.identity
+                and self.noLinkCheck == other.noLinkCheck
+                and self.mapSubject == other.mapSubject
+                and self.mapPredicate == other.mapPredicate
+                and self.refScope == other.refScope
+                and self.typeDSL == other.typeDSL
+                and self.secondaryFilesDSL == other.secondaryFilesDSL
+                and self.subscope == other.subscope
+            )
+        return False
+
+    def __hash__(self) -> int:
+        return hash(
+            (
+                self._id,
+                self._type,
+                self._container,
+                self.identity,
+                self.noLinkCheck,
+                self.mapSubject,
+                self.mapPredicate,
+                self.refScope,
+                self.typeDSL,
+                self.secondaryFilesDSL,
+                self.subscope,
+            )
+        )
+
+    @classmethod
+    def fromDoc(
+        cls,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None
+    ) -> "JsonldPredicate":
+        _doc = copy.copy(doc)
+
+        if hasattr(doc, "lc"):
+            _doc.lc.data = doc.lc.data
+            _doc.lc.filename = doc.lc.filename
+        _errors__ = []
+        if "_id" in _doc:
+            try:
+                _id = load_field(
+                    _doc.get("_id"),
+                    uri_union_of_None_type_or_strtype_True_False_None_None,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("_id")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `_id`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("_id"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `_id` field is not valid because:",
+                                SourceLine(_doc, "_id", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `_id` field is not valid because:",
+                                SourceLine(_doc, "_id", str),
+                                [e],
+                            )
+                        )
+        else:
+            _id = None
+        if "_type" in _doc:
+            try:
+                _type = load_field(
+                    _doc.get("_type"),
+                    union_of_None_type_or_strtype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("_type")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `_type`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("_type"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `_type` field is not valid because:",
+                                SourceLine(_doc, "_type", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `_type` field is not valid because:",
+                                SourceLine(_doc, "_type", str),
+                                [e],
+                            )
+                        )
+        else:
+            _type = None
+        if "_container" in _doc:
+            try:
+                _container = load_field(
+                    _doc.get("_container"),
+                    union_of_None_type_or_strtype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("_container")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `_container`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("_container"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `_container` field is not valid because:",
+                                SourceLine(_doc, "_container", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `_container` field is not valid because:",
+                                SourceLine(_doc, "_container", str),
+                                [e],
+                            )
+                        )
+        else:
+            _container = None
+        if "identity" in _doc:
+            try:
+                identity = load_field(
+                    _doc.get("identity"),
+                    union_of_None_type_or_booltype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("identity")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `identity`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("identity"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `identity` field is not valid because:",
+                                SourceLine(_doc, "identity", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `identity` field is not valid because:",
+                                SourceLine(_doc, "identity", str),
+                                [e],
+                            )
+                        )
+        else:
+            identity = None
+        if "noLinkCheck" in _doc:
+            try:
+                noLinkCheck = load_field(
+                    _doc.get("noLinkCheck"),
+                    union_of_None_type_or_booltype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("noLinkCheck")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `noLinkCheck`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("noLinkCheck"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `noLinkCheck` field is not valid because:",
+                                SourceLine(_doc, "noLinkCheck", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `noLinkCheck` field is not valid because:",
+                                SourceLine(_doc, "noLinkCheck", str),
+                                [e],
+                            )
+                        )
+        else:
+            noLinkCheck = None
+        if "mapSubject" in _doc:
+            try:
+                mapSubject = load_field(
+                    _doc.get("mapSubject"),
+                    union_of_None_type_or_strtype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("mapSubject")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `mapSubject`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("mapSubject"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `mapSubject` field is not valid because:",
+                                SourceLine(_doc, "mapSubject", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `mapSubject` field is not valid because:",
+                                SourceLine(_doc, "mapSubject", str),
+                                [e],
+                            )
+                        )
+        else:
+            mapSubject = None
+        if "mapPredicate" in _doc:
+            try:
+                mapPredicate = load_field(
+                    _doc.get("mapPredicate"),
+                    union_of_None_type_or_strtype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("mapPredicate")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `mapPredicate`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("mapPredicate"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `mapPredicate` field is not valid because:",
+                                SourceLine(_doc, "mapPredicate", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `mapPredicate` field is not valid because:",
+                                SourceLine(_doc, "mapPredicate", str),
+                                [e],
+                            )
+                        )
+        else:
+            mapPredicate = None
+        if "refScope" in _doc:
+            try:
+                refScope = load_field(
+                    _doc.get("refScope"),
+                    union_of_None_type_or_inttype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("refScope")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `refScope`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("refScope"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `refScope` field is not valid because:",
+                                SourceLine(_doc, "refScope", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `refScope` field is not valid because:",
+                                SourceLine(_doc, "refScope", str),
+                                [e],
+                            )
+                        )
+        else:
+            refScope = None
+        if "typeDSL" in _doc:
+            try:
+                typeDSL = load_field(
+                    _doc.get("typeDSL"),
+                    union_of_None_type_or_booltype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("typeDSL")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `typeDSL`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("typeDSL"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `typeDSL` field is not valid because:",
+                                SourceLine(_doc, "typeDSL", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `typeDSL` field is not valid because:",
+                                SourceLine(_doc, "typeDSL", str),
+                                [e],
+                            )
+                        )
+        else:
+            typeDSL = None
+        if "secondaryFilesDSL" in _doc:
+            try:
+                secondaryFilesDSL = load_field(
+                    _doc.get("secondaryFilesDSL"),
+                    union_of_None_type_or_booltype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("secondaryFilesDSL")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `secondaryFilesDSL`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("secondaryFilesDSL"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `secondaryFilesDSL` field is not valid because:",
+                                SourceLine(_doc, "secondaryFilesDSL", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `secondaryFilesDSL` field is not valid because:",
+                                SourceLine(_doc, "secondaryFilesDSL", str),
+                                [e],
+                            )
+                        )
+        else:
+            secondaryFilesDSL = None
+        if "subscope" in _doc:
+            try:
+                subscope = load_field(
+                    _doc.get("subscope"),
+                    union_of_None_type_or_strtype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("subscope")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `subscope`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("subscope"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `subscope` field is not valid because:",
+                                SourceLine(_doc, "subscope", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `subscope` field is not valid because:",
+                                SourceLine(_doc, "subscope", str),
+                                [e],
+                            )
+                        )
+        else:
+            subscope = None
+        extension_fields: Dict[str, Any] = {}
+        for k in _doc.keys():
+            if k not in cls.attrs:
+                if not k:
+                    _errors__.append(
+                        ValidationException("mapping with implicit null key")
+                    )
+                elif ":" in k:
+                    ex = expand_url(
+                        k, "", loadingOptions, scoped_id=False, vocab_term=False
+                    )
+                    extension_fields[ex] = _doc[k]
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "invalid field `{}`, expected one of: `_id`, `_type`, `_container`, `identity`, `noLinkCheck`, `mapSubject`, `mapPredicate`, `refScope`, `typeDSL`, `secondaryFilesDSL`, `subscope`".format(
+                                k
+                            ),
+                            SourceLine(_doc, k, str),
+                        )
+                    )
+
+        if _errors__:
+            raise ValidationException("", None, _errors__, "*")
+        _constructed = cls(
+            _id=_id,
+            _type=_type,
+            _container=_container,
+            identity=identity,
+            noLinkCheck=noLinkCheck,
+            mapSubject=mapSubject,
+            mapPredicate=mapPredicate,
+            refScope=refScope,
+            typeDSL=typeDSL,
+            secondaryFilesDSL=secondaryFilesDSL,
+            subscope=subscope,
+            extension_fields=extension_fields,
+            loadingOptions=loadingOptions,
+        )
+        return _constructed
+
+    def save(
+        self, top: bool = False, base_url: str = "", relative_uris: bool = True
+    ) -> Dict[str, Any]:
+        r: Dict[str, Any] = {}
+
+        if relative_uris:
+            for ef in self.extension_fields:
+                r[prefix_url(ef, self.loadingOptions.vocab)] = self.extension_fields[ef]
+        else:
+            for ef in self.extension_fields:
+                r[ef] = self.extension_fields[ef]
+        if self._id is not None:
+            u = save_relative_uri(self._id, base_url, True, None, relative_uris)
+            r["_id"] = u
+        if self._type is not None:
+            r["_type"] = save(
+                self._type, top=False, base_url=base_url, relative_uris=relative_uris
+            )
+        if self._container is not None:
+            r["_container"] = save(
+                self._container,
+                top=False,
+                base_url=base_url,
+                relative_uris=relative_uris,
+            )
+        if self.identity is not None:
+            r["identity"] = save(
+                self.identity, top=False, base_url=base_url, relative_uris=relative_uris
+            )
+        if self.noLinkCheck is not None:
+            r["noLinkCheck"] = save(
+                self.noLinkCheck,
+                top=False,
+                base_url=base_url,
+                relative_uris=relative_uris,
+            )
+        if self.mapSubject is not None:
+            r["mapSubject"] = save(
+                self.mapSubject,
+                top=False,
+                base_url=base_url,
+                relative_uris=relative_uris,
+            )
+        if self.mapPredicate is not None:
+            r["mapPredicate"] = save(
+                self.mapPredicate,
+                top=False,
+                base_url=base_url,
+                relative_uris=relative_uris,
+            )
+        if self.refScope is not None:
+            r["refScope"] = save(
+                self.refScope, top=False, base_url=base_url, relative_uris=relative_uris
+            )
+        if self.typeDSL is not None:
+            r["typeDSL"] = save(
+                self.typeDSL, top=False, base_url=base_url, relative_uris=relative_uris
+            )
+        if self.secondaryFilesDSL is not None:
+            r["secondaryFilesDSL"] = save(
+                self.secondaryFilesDSL,
+                top=False,
+                base_url=base_url,
+                relative_uris=relative_uris,
+            )
+        if self.subscope is not None:
+            r["subscope"] = save(
+                self.subscope, top=False, base_url=base_url, relative_uris=relative_uris
+            )
+
+        # top refers to the directory level
+        if top:
+            if self.loadingOptions.namespaces:
+                r["$namespaces"] = self.loadingOptions.namespaces
+            if self.loadingOptions.schemas:
+                r["$schemas"] = self.loadingOptions.schemas
+        return r
+
+    attrs = frozenset(
+        [
+            "_id",
+            "_type",
+            "_container",
+            "identity",
+            "noLinkCheck",
+            "mapSubject",
+            "mapPredicate",
+            "refScope",
+            "typeDSL",
+            "secondaryFilesDSL",
+            "subscope",
+        ]
+    )
+
+
+class SpecializeDef(Saveable):
+    def __init__(
+        self,
+        specializeFrom: Any,
+        specializeTo: Any,
+        extension_fields: Optional[Dict[str, Any]] = None,
+        loadingOptions: Optional[LoadingOptions] = None,
+    ) -> None:
+        if extension_fields:
+            self.extension_fields = extension_fields
+        else:
+            self.extension_fields = CommentedMap()
+        if loadingOptions:
+            self.loadingOptions = loadingOptions
+        else:
+            self.loadingOptions = LoadingOptions()
+        self.specializeFrom = specializeFrom
+        self.specializeTo = specializeTo
+
+    def __eq__(self, other: Any) -> bool:
+        if isinstance(other, SpecializeDef):
+            return bool(
+                self.specializeFrom == other.specializeFrom
+                and self.specializeTo == other.specializeTo
+            )
+        return False
+
+    def __hash__(self) -> int:
+        return hash((self.specializeFrom, self.specializeTo))
+
+    @classmethod
+    def fromDoc(
+        cls,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None
+    ) -> "SpecializeDef":
+        _doc = copy.copy(doc)
+
+        if hasattr(doc, "lc"):
+            _doc.lc.data = doc.lc.data
+            _doc.lc.filename = doc.lc.filename
+        _errors__ = []
+        try:
+            if _doc.get("specializeFrom") is None:
+                raise ValidationException("missing required field `specializeFrom`", None, [])
+
+            specializeFrom = load_field(
+                _doc.get("specializeFrom"),
+                uri_strtype_False_False_1_None,
+                baseuri,
+                loadingOptions,
+                lc=_doc.get("specializeFrom")
+            )
+
+        except ValidationException as e:
+            error_message, to_print, verb_tensage = parse_errors(str(e))
+
+            if str(e) == "missing required field `specializeFrom`":
+                _errors__.append(
+                    ValidationException(
+                        str(e),
+                        None
+                    )
+                )
+            else:
+                if error_message != str(e):
+                    val_type = convert_typing(extract_type(type(_doc.get("specializeFrom"))))
+                    _errors__.append(
+                        ValidationException(
+                            "the `specializeFrom` field is not valid because:",
+                            SourceLine(_doc, "specializeFrom", str),
+                            [ValidationException(f"Value is a {val_type}, "
+                                                 f"but valid {to_print} for this field "
+                                                 f"{verb_tensage} {error_message}")],
+                        )
+                    )
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "the `specializeFrom` field is not valid because:",
+                            SourceLine(_doc, "specializeFrom", str),
+                            [e],
+                        )
+                    )
+        try:
+            if _doc.get("specializeTo") is None:
+                raise ValidationException("missing required field `specializeTo`", None, [])
+
+            specializeTo = load_field(
+                _doc.get("specializeTo"),
+                uri_strtype_False_False_1_None,
+                baseuri,
+                loadingOptions,
+                lc=_doc.get("specializeTo")
+            )
+
+        except ValidationException as e:
+            error_message, to_print, verb_tensage = parse_errors(str(e))
+
+            if str(e) == "missing required field `specializeTo`":
+                _errors__.append(
+                    ValidationException(
+                        str(e),
+                        None
+                    )
+                )
+            else:
+                if error_message != str(e):
+                    val_type = convert_typing(extract_type(type(_doc.get("specializeTo"))))
+                    _errors__.append(
+                        ValidationException(
+                            "the `specializeTo` field is not valid because:",
+                            SourceLine(_doc, "specializeTo", str),
+                            [ValidationException(f"Value is a {val_type}, "
+                                                 f"but valid {to_print} for this field "
+                                                 f"{verb_tensage} {error_message}")],
+                        )
+                    )
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "the `specializeTo` field is not valid because:",
+                            SourceLine(_doc, "specializeTo", str),
+                            [e],
+                        )
+                    )
+        extension_fields: Dict[str, Any] = {}
+        for k in _doc.keys():
+            if k not in cls.attrs:
+                if not k:
+                    _errors__.append(
+                        ValidationException("mapping with implicit null key")
+                    )
+                elif ":" in k:
+                    ex = expand_url(
+                        k, "", loadingOptions, scoped_id=False, vocab_term=False
+                    )
+                    extension_fields[ex] = _doc[k]
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "invalid field `{}`, expected one of: `specializeFrom`, `specializeTo`".format(
+                                k
+                            ),
+                            SourceLine(_doc, k, str),
+                        )
+                    )
+
+        if _errors__:
+            raise ValidationException("", None, _errors__, "*")
+        _constructed = cls(
+            specializeFrom=specializeFrom,
+            specializeTo=specializeTo,
+            extension_fields=extension_fields,
+            loadingOptions=loadingOptions,
+        )
+        return _constructed
+
+    def save(
+        self, top: bool = False, base_url: str = "", relative_uris: bool = True
+    ) -> Dict[str, Any]:
+        r: Dict[str, Any] = {}
+
+        if relative_uris:
+            for ef in self.extension_fields:
+                r[prefix_url(ef, self.loadingOptions.vocab)] = self.extension_fields[ef]
+        else:
+            for ef in self.extension_fields:
+                r[ef] = self.extension_fields[ef]
+        if self.specializeFrom is not None:
+            u = save_relative_uri(
+                self.specializeFrom, base_url, False, 1, relative_uris
+            )
+            r["specializeFrom"] = u
+        if self.specializeTo is not None:
+            u = save_relative_uri(self.specializeTo, base_url, False, 1, relative_uris)
+            r["specializeTo"] = u
+
+        # top refers to the directory level
+        if top:
+            if self.loadingOptions.namespaces:
+                r["$namespaces"] = self.loadingOptions.namespaces
+            if self.loadingOptions.schemas:
+                r["$schemas"] = self.loadingOptions.schemas
+        return r
+
+    attrs = frozenset(["specializeFrom", "specializeTo"])
+
+
+class NamedType(Saveable):
+    pass
+
+
+class DocType(Documented):
+    pass
+
+
+class SchemaDefinedType(DocType):
+    """
+    Abstract base for schema-defined types.
+
+    """
+
+    pass
+
+
+class SaladRecordField(RecordField):
+    """
+    A field of a record.
+    """
+
+    def __init__(
+        self,
+        name: Any,
+        type_: Any,
+        doc: Optional[Any] = None,
+        jsonldPredicate: Optional[Any] = None,
+        default: Optional[Any] = None,
+        extension_fields: Optional[Dict[str, Any]] = None,
+        loadingOptions: Optional[LoadingOptions] = None,
+    ) -> None:
+        if extension_fields:
+            self.extension_fields = extension_fields
+        else:
+            self.extension_fields = CommentedMap()
+        if loadingOptions:
+            self.loadingOptions = loadingOptions
+        else:
+            self.loadingOptions = LoadingOptions()
+        self.doc = doc
+        self.name = name
+        self.type_ = type_
+        self.jsonldPredicate = jsonldPredicate
+        self.default = default
+
+    def __eq__(self, other: Any) -> bool:
+        if isinstance(other, SaladRecordField):
+            return bool(
+                self.doc == other.doc
+                and self.name == other.name
+                and self.type_ == other.type_
+                and self.jsonldPredicate == other.jsonldPredicate
+                and self.default == other.default
+            )
+        return False
+
+    def __hash__(self) -> int:
+        return hash(
+            (self.doc, self.name, self.type_, self.jsonldPredicate, self.default)
+        )
+
+    @classmethod
+    def fromDoc(
+        cls,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None
+    ) -> "SaladRecordField":
+        _doc = copy.copy(doc)
+
+        if hasattr(doc, "lc"):
+            _doc.lc.data = doc.lc.data
+            _doc.lc.filename = doc.lc.filename
+        _errors__ = []
+        if "name" in _doc:
+            try:
+                name = load_field(
+                    _doc.get("name"),
+                    uri_strtype_True_False_None_None,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("name")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `name`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("name"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `name` field is not valid because:",
+                                SourceLine(_doc, "name", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `name` field is not valid because:",
+                                SourceLine(_doc, "name", str),
+                                [e],
+                            )
+                        )
+        else:
+            name = None
+
+        __original_name_is_none = name is None
+        if name is None:
+            if docRoot is not None:
+                name = docRoot
+            else:
+                _errors__.append(ValidationException("missing name"))
+        if not __original_name_is_none:
+            baseuri = name
+        if "doc" in _doc:
+            try:
+                doc = load_field(
+                    _doc.get("doc"),
+                    union_of_None_type_or_strtype_or_array_of_strtype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("doc")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `doc`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("doc"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `doc` field is not valid because:",
+                                SourceLine(_doc, "doc", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `doc` field is not valid because:",
+                                SourceLine(_doc, "doc", str),
+                                [e],
+                            )
+                        )
+        else:
+            doc = None
+        try:
+            if _doc.get("type") is None:
+                raise ValidationException("missing required field `type`", None, [])
+
+            type_ = load_field(
+                _doc.get("type"),
+                typedsl_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_2,
+                baseuri,
+                loadingOptions,
+                lc=_doc.get("type")
+            )
+
+        except ValidationException as e:
+            error_message, to_print, verb_tensage = parse_errors(str(e))
+
+            if str(e) == "missing required field `type`":
+                _errors__.append(
+                    ValidationException(
+                        str(e),
+                        None
+                    )
+                )
+            else:
+                if error_message != str(e):
+                    val_type = convert_typing(extract_type(type(_doc.get("type"))))
+                    _errors__.append(
+                        ValidationException(
+                            "the `type` field is not valid because:",
+                            SourceLine(_doc, "type", str),
+                            [ValidationException(f"Value is a {val_type}, "
+                                                 f"but valid {to_print} for this field "
+                                                 f"{verb_tensage} {error_message}")],
+                        )
+                    )
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "the `type` field is not valid because:",
+                            SourceLine(_doc, "type", str),
+                            [e],
+                        )
+                    )
+        if "jsonldPredicate" in _doc:
+            try:
+                jsonldPredicate = load_field(
+                    _doc.get("jsonldPredicate"),
+                    union_of_None_type_or_strtype_or_JsonldPredicateLoader,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("jsonldPredicate")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `jsonldPredicate`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("jsonldPredicate"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `jsonldPredicate` field is not valid because:",
+                                SourceLine(_doc, "jsonldPredicate", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `jsonldPredicate` field is not valid because:",
+                                SourceLine(_doc, "jsonldPredicate", str),
+                                [e],
+                            )
+                        )
+        else:
+            jsonldPredicate = None
+        if "default" in _doc:
+            try:
+                default = load_field(
+                    _doc.get("default"),
+                    union_of_None_type_or_Any_type,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("default")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `default`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("default"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `default` field is not valid because:",
+                                SourceLine(_doc, "default", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `default` field is not valid because:",
+                                SourceLine(_doc, "default", str),
+                                [e],
+                            )
+                        )
+        else:
+            default = None
+        extension_fields: Dict[str, Any] = {}
+        for k in _doc.keys():
+            if k not in cls.attrs:
+                if not k:
+                    _errors__.append(
+                        ValidationException("mapping with implicit null key")
+                    )
+                elif ":" in k:
+                    ex = expand_url(
+                        k, "", loadingOptions, scoped_id=False, vocab_term=False
+                    )
+                    extension_fields[ex] = _doc[k]
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "invalid field `{}`, expected one of: `doc`, `name`, `type`, `jsonldPredicate`, `default`".format(
+                                k
+                            ),
+                            SourceLine(_doc, k, str),
+                        )
+                    )
+
+        if _errors__:
+            raise ValidationException("", None, _errors__, "*")
+        _constructed = cls(
+            doc=doc,
+            name=name,
+            type_=type_,
+            jsonldPredicate=jsonldPredicate,
+            default=default,
+            extension_fields=extension_fields,
+            loadingOptions=loadingOptions,
+        )
+        loadingOptions.idx[name] = (_constructed, loadingOptions)
+        return _constructed
+
+    def save(
+        self, top: bool = False, base_url: str = "", relative_uris: bool = True
+    ) -> Dict[str, Any]:
+        r: Dict[str, Any] = {}
+
+        if relative_uris:
+            for ef in self.extension_fields:
+                r[prefix_url(ef, self.loadingOptions.vocab)] = self.extension_fields[ef]
+        else:
+            for ef in self.extension_fields:
+                r[ef] = self.extension_fields[ef]
+        if self.name is not None:
+            u = save_relative_uri(self.name, base_url, True, None, relative_uris)
+            r["name"] = u
+        if self.doc is not None:
+            r["doc"] = save(
+                self.doc, top=False, base_url=self.name, relative_uris=relative_uris
+            )
+        if self.type_ is not None:
+            r["type"] = save(
+                self.type_, top=False, base_url=self.name, relative_uris=relative_uris
+            )
+        if self.jsonldPredicate is not None:
+            r["jsonldPredicate"] = save(
+                self.jsonldPredicate,
+                top=False,
+                base_url=self.name,
+                relative_uris=relative_uris,
+            )
+        if self.default is not None:
+            r["default"] = save(
+                self.default, top=False, base_url=self.name, relative_uris=relative_uris
+            )
+
+        # top refers to the directory level
+        if top:
+            if self.loadingOptions.namespaces:
+                r["$namespaces"] = self.loadingOptions.namespaces
+            if self.loadingOptions.schemas:
+                r["$schemas"] = self.loadingOptions.schemas
+        return r
+
+    attrs = frozenset(["doc", "name", "type", "jsonldPredicate", "default"])
+
+
+class SaladRecordSchema(NamedType, RecordSchema, SchemaDefinedType):
+    def __init__(
+        self,
+        name: Any,
+        type_: Any,
+        inVocab: Optional[Any] = None,
+        fields: Optional[Any] = None,
+        doc: Optional[Any] = None,
+        docParent: Optional[Any] = None,
+        docChild: Optional[Any] = None,
+        docAfter: Optional[Any] = None,
+        jsonldPredicate: Optional[Any] = None,
+        documentRoot: Optional[Any] = None,
+        abstract: Optional[Any] = None,
+        extends: Optional[Any] = None,
+        specialize: Optional[Any] = None,
+        extension_fields: Optional[Dict[str, Any]] = None,
+        loadingOptions: Optional[LoadingOptions] = None,
+    ) -> None:
+        if extension_fields:
+            self.extension_fields = extension_fields
+        else:
+            self.extension_fields = CommentedMap()
+        if loadingOptions:
+            self.loadingOptions = loadingOptions
+        else:
+            self.loadingOptions = LoadingOptions()
+        self.name = name
+        self.inVocab = inVocab
+        self.fields = fields
+        self.type_ = type_
+        self.doc = doc
+        self.docParent = docParent
+        self.docChild = docChild
+        self.docAfter = docAfter
+        self.jsonldPredicate = jsonldPredicate
+        self.documentRoot = documentRoot
+        self.abstract = abstract
+        self.extends = extends
+        self.specialize = specialize
+
+    def __eq__(self, other: Any) -> bool:
+        if isinstance(other, SaladRecordSchema):
+            return bool(
+                self.name == other.name
+                and self.inVocab == other.inVocab
+                and self.fields == other.fields
+                and self.type_ == other.type_
+                and self.doc == other.doc
+                and self.docParent == other.docParent
+                and self.docChild == other.docChild
+                and self.docAfter == other.docAfter
+                and self.jsonldPredicate == other.jsonldPredicate
+                and self.documentRoot == other.documentRoot
+                and self.abstract == other.abstract
+                and self.extends == other.extends
+                and self.specialize == other.specialize
+            )
+        return False
+
+    def __hash__(self) -> int:
+        return hash(
+            (
+                self.name,
+                self.inVocab,
+                self.fields,
+                self.type_,
+                self.doc,
+                self.docParent,
+                self.docChild,
+                self.docAfter,
+                self.jsonldPredicate,
+                self.documentRoot,
+                self.abstract,
+                self.extends,
+                self.specialize,
+            )
+        )
+
+    @classmethod
+    def fromDoc(
+        cls,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None
+    ) -> "SaladRecordSchema":
+        _doc = copy.copy(doc)
+
+        if hasattr(doc, "lc"):
+            _doc.lc.data = doc.lc.data
+            _doc.lc.filename = doc.lc.filename
+        _errors__ = []
+        if "name" in _doc:
+            try:
+                name = load_field(
+                    _doc.get("name"),
+                    uri_strtype_True_False_None_None,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("name")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `name`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("_type"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("name"))))
                         _errors__.append(
                             ValidationException(
-                                "the `_type` field is not valid because:",
-                                SourceLine(_doc, "_type", str),
+                                "the `name` field is not valid because:",
+                                SourceLine(_doc, "name", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `name` field is not valid because:",
+                                SourceLine(_doc, "name", str),
+                                [e],
+                            )
+                        )
+        else:
+            name = None
+
+        __original_name_is_none = name is None
+        if name is None:
+            if docRoot is not None:
+                name = docRoot
+            else:
+                _errors__.append(ValidationException("missing name"))
+        if not __original_name_is_none:
+            baseuri = name
+        if "inVocab" in _doc:
+            try:
+                inVocab = load_field(
+                    _doc.get("inVocab"),
+                    union_of_None_type_or_booltype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("inVocab")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `inVocab`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("inVocab"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `inVocab` field is not valid because:",
+                                SourceLine(_doc, "inVocab", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `inVocab` field is not valid because:",
+                                SourceLine(_doc, "inVocab", str),
+                                [e],
+                            )
+                        )
+        else:
+            inVocab = None
+        if "fields" in _doc:
+            try:
+                fields = load_field(
+                    _doc.get("fields"),
+                    idmap_fields_union_of_None_type_or_array_of_SaladRecordFieldLoader,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("fields")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `fields`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("fields"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `fields` field is not valid because:",
+                                SourceLine(_doc, "fields", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
                             )
                         )
-                    else:
-                        _errors__.append(
-                            ValidationException(
-                                "the `_type` field is not valid because:",
-                                SourceLine(_doc, "_type", str),
-                                [e],
-                            )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `fields` field is not valid because:",
+                                SourceLine(_doc, "fields", str),
+                                [e],
+                            )
+                        )
+        else:
+            fields = None
+        try:
+            if _doc.get("type") is None:
+                raise ValidationException("missing required field `type`", None, [])
+
+            type_ = load_field(
+                _doc.get("type"),
+                typedsl_Record_nameLoader_2,
+                baseuri,
+                loadingOptions,
+                lc=_doc.get("type")
+            )
+
+        except ValidationException as e:
+            error_message, to_print, verb_tensage = parse_errors(str(e))
+
+            if str(e) == "missing required field `type`":
+                _errors__.append(
+                    ValidationException(
+                        str(e),
+                        None
+                    )
+                )
+            else:
+                if error_message != str(e):
+                    val_type = convert_typing(extract_type(type(_doc.get("type"))))
+                    _errors__.append(
+                        ValidationException(
+                            "the `type` field is not valid because:",
+                            SourceLine(_doc, "type", str),
+                            [ValidationException(f"Value is a {val_type}, "
+                                                 f"but valid {to_print} for this field "
+                                                 f"{verb_tensage} {error_message}")],
+                        )
+                    )
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "the `type` field is not valid because:",
+                            SourceLine(_doc, "type", str),
+                            [e],
                         )
-        else:
-            _type = None
-        if "_container" in _doc:
+                    )
+        if "doc" in _doc:
             try:
-                if _doc.get("_container") is None:
-                    raise ValidationException("missing required field `_container`", None, [])
-
-                _container = load_field(
-                    _doc.get("_container"),
-                    union_of_None_type_or_strtype,
+                doc = load_field(
+                    _doc.get("doc"),
+                    union_of_None_type_or_strtype_or_array_of_strtype,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("_container")
+                    lc=_doc.get("doc")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `_container`":
+                if str(e) == "missing required field `doc`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2124,11 +3974,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("_container"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("doc"))))
                         _errors__.append(
                             ValidationException(
-                                "the `_container` field is not valid because:",
-                                SourceLine(_doc, "_container", str),
+                                "the `doc` field is not valid because:",
+                                SourceLine(_doc, "doc", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2137,30 +3987,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `_container` field is not valid because:",
-                                SourceLine(_doc, "_container", str),
+                                "the `doc` field is not valid because:",
+                                SourceLine(_doc, "doc", str),
                                 [e],
                             )
                         )
         else:
-            _container = None
-        if "identity" in _doc:
+            doc = None
+        if "docParent" in _doc:
             try:
-                if _doc.get("identity") is None:
-                    raise ValidationException("missing required field `identity`", None, [])
-
-                identity = load_field(
-                    _doc.get("identity"),
-                    union_of_None_type_or_booltype,
+                docParent = load_field(
+                    _doc.get("docParent"),
+                    uri_union_of_None_type_or_strtype_False_False_None_None,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("identity")
+                    lc=_doc.get("docParent")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `identity`":
+                if str(e) == "missing required field `docParent`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2169,11 +4016,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("identity"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("docParent"))))
                         _errors__.append(
                             ValidationException(
-                                "the `identity` field is not valid because:",
-                                SourceLine(_doc, "identity", str),
+                                "the `docParent` field is not valid because:",
+                                SourceLine(_doc, "docParent", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2182,30 +4029,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `identity` field is not valid because:",
-                                SourceLine(_doc, "identity", str),
+                                "the `docParent` field is not valid because:",
+                                SourceLine(_doc, "docParent", str),
                                 [e],
                             )
                         )
         else:
-            identity = None
-        if "noLinkCheck" in _doc:
+            docParent = None
+        if "docChild" in _doc:
             try:
-                if _doc.get("noLinkCheck") is None:
-                    raise ValidationException("missing required field `noLinkCheck`", None, [])
-
-                noLinkCheck = load_field(
-                    _doc.get("noLinkCheck"),
-                    union_of_None_type_or_booltype,
+                docChild = load_field(
+                    _doc.get("docChild"),
+                    uri_union_of_None_type_or_strtype_or_array_of_strtype_False_False_None_None,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("noLinkCheck")
+                    lc=_doc.get("docChild")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `noLinkCheck`":
+                if str(e) == "missing required field `docChild`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2214,11 +4058,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("noLinkCheck"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("docChild"))))
                         _errors__.append(
                             ValidationException(
-                                "the `noLinkCheck` field is not valid because:",
-                                SourceLine(_doc, "noLinkCheck", str),
+                                "the `docChild` field is not valid because:",
+                                SourceLine(_doc, "docChild", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2227,30 +4071,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `noLinkCheck` field is not valid because:",
-                                SourceLine(_doc, "noLinkCheck", str),
+                                "the `docChild` field is not valid because:",
+                                SourceLine(_doc, "docChild", str),
                                 [e],
                             )
                         )
         else:
-            noLinkCheck = None
-        if "mapSubject" in _doc:
+            docChild = None
+        if "docAfter" in _doc:
             try:
-                if _doc.get("mapSubject") is None:
-                    raise ValidationException("missing required field `mapSubject`", None, [])
-
-                mapSubject = load_field(
-                    _doc.get("mapSubject"),
-                    union_of_None_type_or_strtype,
+                docAfter = load_field(
+                    _doc.get("docAfter"),
+                    uri_union_of_None_type_or_strtype_False_False_None_None,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("mapSubject")
+                    lc=_doc.get("docAfter")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `mapSubject`":
+                if str(e) == "missing required field `docAfter`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2259,11 +4100,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("mapSubject"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("docAfter"))))
                         _errors__.append(
                             ValidationException(
-                                "the `mapSubject` field is not valid because:",
-                                SourceLine(_doc, "mapSubject", str),
+                                "the `docAfter` field is not valid because:",
+                                SourceLine(_doc, "docAfter", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2272,30 +4113,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `mapSubject` field is not valid because:",
-                                SourceLine(_doc, "mapSubject", str),
+                                "the `docAfter` field is not valid because:",
+                                SourceLine(_doc, "docAfter", str),
                                 [e],
                             )
                         )
         else:
-            mapSubject = None
-        if "mapPredicate" in _doc:
+            docAfter = None
+        if "jsonldPredicate" in _doc:
             try:
-                if _doc.get("mapPredicate") is None:
-                    raise ValidationException("missing required field `mapPredicate`", None, [])
-
-                mapPredicate = load_field(
-                    _doc.get("mapPredicate"),
-                    union_of_None_type_or_strtype,
+                jsonldPredicate = load_field(
+                    _doc.get("jsonldPredicate"),
+                    union_of_None_type_or_strtype_or_JsonldPredicateLoader,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("mapPredicate")
+                    lc=_doc.get("jsonldPredicate")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `mapPredicate`":
+                if str(e) == "missing required field `jsonldPredicate`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2304,11 +4142,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("mapPredicate"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("jsonldPredicate"))))
                         _errors__.append(
                             ValidationException(
-                                "the `mapPredicate` field is not valid because:",
-                                SourceLine(_doc, "mapPredicate", str),
+                                "the `jsonldPredicate` field is not valid because:",
+                                SourceLine(_doc, "jsonldPredicate", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2317,30 +4155,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `mapPredicate` field is not valid because:",
-                                SourceLine(_doc, "mapPredicate", str),
+                                "the `jsonldPredicate` field is not valid because:",
+                                SourceLine(_doc, "jsonldPredicate", str),
                                 [e],
                             )
                         )
         else:
-            mapPredicate = None
-        if "refScope" in _doc:
+            jsonldPredicate = None
+        if "documentRoot" in _doc:
             try:
-                if _doc.get("refScope") is None:
-                    raise ValidationException("missing required field `refScope`", None, [])
-
-                refScope = load_field(
-                    _doc.get("refScope"),
-                    union_of_None_type_or_inttype,
+                documentRoot = load_field(
+                    _doc.get("documentRoot"),
+                    union_of_None_type_or_booltype,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("refScope")
+                    lc=_doc.get("documentRoot")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `refScope`":
+                if str(e) == "missing required field `documentRoot`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2349,11 +4184,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("refScope"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("documentRoot"))))
                         _errors__.append(
                             ValidationException(
-                                "the `refScope` field is not valid because:",
-                                SourceLine(_doc, "refScope", str),
+                                "the `documentRoot` field is not valid because:",
+                                SourceLine(_doc, "documentRoot", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2362,30 +4197,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `refScope` field is not valid because:",
-                                SourceLine(_doc, "refScope", str),
+                                "the `documentRoot` field is not valid because:",
+                                SourceLine(_doc, "documentRoot", str),
                                 [e],
                             )
                         )
         else:
-            refScope = None
-        if "typeDSL" in _doc:
+            documentRoot = None
+        if "abstract" in _doc:
             try:
-                if _doc.get("typeDSL") is None:
-                    raise ValidationException("missing required field `typeDSL`", None, [])
-
-                typeDSL = load_field(
-                    _doc.get("typeDSL"),
+                abstract = load_field(
+                    _doc.get("abstract"),
                     union_of_None_type_or_booltype,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("typeDSL")
+                    lc=_doc.get("abstract")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `typeDSL`":
+                if str(e) == "missing required field `abstract`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2394,11 +4226,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("typeDSL"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("abstract"))))
                         _errors__.append(
                             ValidationException(
-                                "the `typeDSL` field is not valid because:",
-                                SourceLine(_doc, "typeDSL", str),
+                                "the `abstract` field is not valid because:",
+                                SourceLine(_doc, "abstract", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2407,30 +4239,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `typeDSL` field is not valid because:",
-                                SourceLine(_doc, "typeDSL", str),
+                                "the `abstract` field is not valid because:",
+                                SourceLine(_doc, "abstract", str),
                                 [e],
                             )
                         )
         else:
-            typeDSL = None
-        if "secondaryFilesDSL" in _doc:
+            abstract = None
+        if "extends" in _doc:
             try:
-                if _doc.get("secondaryFilesDSL") is None:
-                    raise ValidationException("missing required field `secondaryFilesDSL`", None, [])
-
-                secondaryFilesDSL = load_field(
-                    _doc.get("secondaryFilesDSL"),
-                    union_of_None_type_or_booltype,
+                extends = load_field(
+                    _doc.get("extends"),
+                    uri_union_of_None_type_or_strtype_or_array_of_strtype_False_False_1_None,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("secondaryFilesDSL")
+                    lc=_doc.get("extends")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `secondaryFilesDSL`":
+                if str(e) == "missing required field `extends`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2439,11 +4268,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("secondaryFilesDSL"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("extends"))))
                         _errors__.append(
                             ValidationException(
-                                "the `secondaryFilesDSL` field is not valid because:",
-                                SourceLine(_doc, "secondaryFilesDSL", str),
+                                "the `extends` field is not valid because:",
+                                SourceLine(_doc, "extends", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2452,30 +4281,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `secondaryFilesDSL` field is not valid because:",
-                                SourceLine(_doc, "secondaryFilesDSL", str),
+                                "the `extends` field is not valid because:",
+                                SourceLine(_doc, "extends", str),
                                 [e],
                             )
                         )
         else:
-            secondaryFilesDSL = None
-        if "subscope" in _doc:
+            extends = None
+        if "specialize" in _doc:
             try:
-                if _doc.get("subscope") is None:
-                    raise ValidationException("missing required field `subscope`", None, [])
-
-                subscope = load_field(
-                    _doc.get("subscope"),
-                    union_of_None_type_or_strtype,
+                specialize = load_field(
+                    _doc.get("specialize"),
+                    idmap_specialize_union_of_None_type_or_array_of_SpecializeDefLoader,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("subscope")
+                    lc=_doc.get("specialize")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `subscope`":
+                if str(e) == "missing required field `specialize`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2484,11 +4310,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("subscope"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("specialize"))))
                         _errors__.append(
                             ValidationException(
-                                "the `subscope` field is not valid because:",
-                                SourceLine(_doc, "subscope", str),
+                                "the `specialize` field is not valid because:",
+                                SourceLine(_doc, "specialize", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2497,13 +4323,13 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `subscope` field is not valid because:",
-                                SourceLine(_doc, "subscope", str),
+                                "the `specialize` field is not valid because:",
+                                SourceLine(_doc, "specialize", str),
                                 [e],
                             )
                         )
         else:
-            subscope = None
+            specialize = None
         extension_fields: Dict[str, Any] = {}
         for k in _doc.keys():
             if k not in cls.attrs:
@@ -2519,7 +4345,7 @@ def fromDoc(
                 else:
                     _errors__.append(
                         ValidationException(
-                            "invalid field `{}`, expected one of: `_id`, `_type`, `_container`, `identity`, `noLinkCheck`, `mapSubject`, `mapPredicate`, `refScope`, `typeDSL`, `secondaryFilesDSL`, `subscope`".format(
+                            "invalid field `{}`, expected one of: `name`, `inVocab`, `fields`, `type`, `doc`, `docParent`, `docChild`, `docAfter`, `jsonldPredicate`, `documentRoot`, `abstract`, `extends`, `specialize`".format(
                                 k
                             ),
                             SourceLine(_doc, k, str),
@@ -2527,22 +4353,25 @@ def fromDoc(
                     )
 
         if _errors__:
-            raise ValidationException("", None, _errors__, "*")
-        _constructed = cls(
-            _id=_id,
-            _type=_type,
-            _container=_container,
-            identity=identity,
-            noLinkCheck=noLinkCheck,
-            mapSubject=mapSubject,
-            mapPredicate=mapPredicate,
-            refScope=refScope,
-            typeDSL=typeDSL,
-            secondaryFilesDSL=secondaryFilesDSL,
-            subscope=subscope,
+            raise ValidationException("", None, _errors__, "*")
+        _constructed = cls(
+            name=name,
+            inVocab=inVocab,
+            fields=fields,
+            type_=type_,
+            doc=doc,
+            docParent=docParent,
+            docChild=docChild,
+            docAfter=docAfter,
+            jsonldPredicate=jsonldPredicate,
+            documentRoot=documentRoot,
+            abstract=abstract,
+            extends=extends,
+            specialize=specialize,
             extension_fields=extension_fields,
             loadingOptions=loadingOptions,
         )
+        loadingOptions.idx[name] = (_constructed, loadingOptions)
         return _constructed
 
     def save(
@@ -2556,64 +4385,65 @@ def save(
         else:
             for ef in self.extension_fields:
                 r[ef] = self.extension_fields[ef]
-        if self._id is not None:
-            u = save_relative_uri(self._id, base_url, True, None, relative_uris)
-            r["_id"] = u
-        if self._type is not None:
-            r["_type"] = save(
-                self._type, top=False, base_url=base_url, relative_uris=relative_uris
+        if self.name is not None:
+            u = save_relative_uri(self.name, base_url, True, None, relative_uris)
+            r["name"] = u
+        if self.inVocab is not None:
+            r["inVocab"] = save(
+                self.inVocab, top=False, base_url=self.name, relative_uris=relative_uris
             )
-        if self._container is not None:
-            r["_container"] = save(
-                self._container,
-                top=False,
-                base_url=base_url,
-                relative_uris=relative_uris,
+        if self.fields is not None:
+            r["fields"] = save(
+                self.fields, top=False, base_url=self.name, relative_uris=relative_uris
             )
-        if self.identity is not None:
-            r["identity"] = save(
-                self.identity, top=False, base_url=base_url, relative_uris=relative_uris
+        if self.type_ is not None:
+            r["type"] = save(
+                self.type_, top=False, base_url=self.name, relative_uris=relative_uris
             )
-        if self.noLinkCheck is not None:
-            r["noLinkCheck"] = save(
-                self.noLinkCheck,
+        if self.doc is not None:
+            r["doc"] = save(
+                self.doc, top=False, base_url=self.name, relative_uris=relative_uris
+            )
+        if self.docParent is not None:
+            u = save_relative_uri(self.docParent, self.name, False, None, relative_uris)
+            r["docParent"] = u
+        if self.docChild is not None:
+            u = save_relative_uri(self.docChild, self.name, False, None, relative_uris)
+            r["docChild"] = u
+        if self.docAfter is not None:
+            u = save_relative_uri(self.docAfter, self.name, False, None, relative_uris)
+            r["docAfter"] = u
+        if self.jsonldPredicate is not None:
+            r["jsonldPredicate"] = save(
+                self.jsonldPredicate,
                 top=False,
-                base_url=base_url,
+                base_url=self.name,
                 relative_uris=relative_uris,
             )
-        if self.mapSubject is not None:
-            r["mapSubject"] = save(
-                self.mapSubject,
+        if self.documentRoot is not None:
+            r["documentRoot"] = save(
+                self.documentRoot,
                 top=False,
-                base_url=base_url,
+                base_url=self.name,
                 relative_uris=relative_uris,
             )
-        if self.mapPredicate is not None:
-            r["mapPredicate"] = save(
-                self.mapPredicate,
+        if self.abstract is not None:
+            r["abstract"] = save(
+                self.abstract,
                 top=False,
-                base_url=base_url,
+                base_url=self.name,
                 relative_uris=relative_uris,
             )
-        if self.refScope is not None:
-            r["refScope"] = save(
-                self.refScope, top=False, base_url=base_url, relative_uris=relative_uris
-            )
-        if self.typeDSL is not None:
-            r["typeDSL"] = save(
-                self.typeDSL, top=False, base_url=base_url, relative_uris=relative_uris
-            )
-        if self.secondaryFilesDSL is not None:
-            r["secondaryFilesDSL"] = save(
-                self.secondaryFilesDSL,
+        if self.extends is not None:
+            u = save_relative_uri(self.extends, self.name, False, 1, relative_uris)
+            r["extends"] = u
+        if self.specialize is not None:
+            r["specialize"] = save(
+                self.specialize,
                 top=False,
-                base_url=base_url,
+                base_url=self.name,
                 relative_uris=relative_uris,
             )
-        if self.subscope is not None:
-            r["subscope"] = save(
-                self.subscope, top=False, base_url=base_url, relative_uris=relative_uris
-            )
 
         # top refers to the directory level
         if top:
@@ -2625,26 +4455,42 @@ def save(
 
     attrs = frozenset(
         [
-            "_id",
-            "_type",
-            "_container",
-            "identity",
-            "noLinkCheck",
-            "mapSubject",
-            "mapPredicate",
-            "refScope",
-            "typeDSL",
-            "secondaryFilesDSL",
-            "subscope",
+            "name",
+            "inVocab",
+            "fields",
+            "type",
+            "doc",
+            "docParent",
+            "docChild",
+            "docAfter",
+            "jsonldPredicate",
+            "documentRoot",
+            "abstract",
+            "extends",
+            "specialize",
         ]
     )
 
 
-class SpecializeDef(Saveable):
+class SaladEnumSchema(NamedType, EnumSchema, SchemaDefinedType):
+    """
+    Define an enumerated type.
+
+    """
+
     def __init__(
         self,
-        specializeFrom: Any,
-        specializeTo: Any,
+        symbols: Any,
+        type_: Any,
+        name: Optional[Any] = None,
+        inVocab: Optional[Any] = None,
+        doc: Optional[Any] = None,
+        docParent: Optional[Any] = None,
+        docChild: Optional[Any] = None,
+        docAfter: Optional[Any] = None,
+        jsonldPredicate: Optional[Any] = None,
+        documentRoot: Optional[Any] = None,
+        extends: Optional[Any] = None,
         extension_fields: Optional[Dict[str, Any]] = None,
         loadingOptions: Optional[LoadingOptions] = None,
     ) -> None:
@@ -2656,50 +4502,175 @@ def __init__(
             self.loadingOptions = loadingOptions
         else:
             self.loadingOptions = LoadingOptions()
-        self.specializeFrom = specializeFrom
-        self.specializeTo = specializeTo
+        self.name = name
+        self.inVocab = inVocab
+        self.symbols = symbols
+        self.type_ = type_
+        self.doc = doc
+        self.docParent = docParent
+        self.docChild = docChild
+        self.docAfter = docAfter
+        self.jsonldPredicate = jsonldPredicate
+        self.documentRoot = documentRoot
+        self.extends = extends
 
     def __eq__(self, other: Any) -> bool:
-        if isinstance(other, SpecializeDef):
+        if isinstance(other, SaladEnumSchema):
             return bool(
-                self.specializeFrom == other.specializeFrom
-                and self.specializeTo == other.specializeTo
+                self.name == other.name
+                and self.inVocab == other.inVocab
+                and self.symbols == other.symbols
+                and self.type_ == other.type_
+                and self.doc == other.doc
+                and self.docParent == other.docParent
+                and self.docChild == other.docChild
+                and self.docAfter == other.docAfter
+                and self.jsonldPredicate == other.jsonldPredicate
+                and self.documentRoot == other.documentRoot
+                and self.extends == other.extends
             )
         return False
 
     def __hash__(self) -> int:
-        return hash((self.specializeFrom, self.specializeTo))
+        return hash(
+            (
+                self.name,
+                self.inVocab,
+                self.symbols,
+                self.type_,
+                self.doc,
+                self.docParent,
+                self.docChild,
+                self.docAfter,
+                self.jsonldPredicate,
+                self.documentRoot,
+                self.extends,
+            )
+        )
+
+    @classmethod
+    def fromDoc(
+        cls,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None
+    ) -> "SaladEnumSchema":
+        _doc = copy.copy(doc)
+
+        if hasattr(doc, "lc"):
+            _doc.lc.data = doc.lc.data
+            _doc.lc.filename = doc.lc.filename
+        _errors__ = []
+        if "name" in _doc:
+            try:
+                name = load_field(
+                    _doc.get("name"),
+                    uri_union_of_None_type_or_strtype_True_False_None_None,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("name")
+                )
+
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `name`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("name"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `name` field is not valid because:",
+                                SourceLine(_doc, "name", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `name` field is not valid because:",
+                                SourceLine(_doc, "name", str),
+                                [e],
+                            )
+                        )
+        else:
+            name = None
+
+        __original_name_is_none = name is None
+        if name is None:
+            if docRoot is not None:
+                name = docRoot
+            else:
+                name = "_:" + str(_uuid__.uuid4())
+        if not __original_name_is_none:
+            baseuri = name
+        if "inVocab" in _doc:
+            try:
+                inVocab = load_field(
+                    _doc.get("inVocab"),
+                    union_of_None_type_or_booltype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("inVocab")
+                )
 
-    @classmethod
-    def fromDoc(
-        cls,
-        doc: Any,
-        baseuri: str,
-        loadingOptions: LoadingOptions,
-        docRoot: Optional[str] = None
-    ) -> "SpecializeDef":
-        _doc = copy.copy(doc)
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
 
-        if hasattr(doc, "lc"):
-            _doc.lc.data = doc.lc.data
-            _doc.lc.filename = doc.lc.filename
-        _errors__ = []
+                if str(e) == "missing required field `inVocab`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("inVocab"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `inVocab` field is not valid because:",
+                                SourceLine(_doc, "inVocab", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `inVocab` field is not valid because:",
+                                SourceLine(_doc, "inVocab", str),
+                                [e],
+                            )
+                        )
+        else:
+            inVocab = None
         try:
-            if _doc.get("specializeFrom") is None:
-                raise ValidationException("missing required field `specializeFrom`", None, [])
+            if _doc.get("symbols") is None:
+                raise ValidationException("missing required field `symbols`", None, [])
 
-            specializeFrom = load_field(
-                _doc.get("specializeFrom"),
-                uri_strtype_False_False_1_None,
+            symbols = load_field(
+                _doc.get("symbols"),
+                uri_array_of_strtype_True_False_None_None,
                 baseuri,
                 loadingOptions,
-                lc=_doc.get("specializeFrom")
+                lc=_doc.get("symbols")
             )
 
         except ValidationException as e:
             error_message, to_print, verb_tensage = parse_errors(str(e))
 
-            if str(e) == "missing required field `specializeFrom`":
+            if str(e) == "missing required field `symbols`":
                 _errors__.append(
                     ValidationException(
                         str(e),
@@ -2708,11 +4679,11 @@ def fromDoc(
                 )
             else:
                 if error_message != str(e):
-                    val_type = convert_typing(extract_type(type(_doc.get("specializeFrom"))))
+                    val_type = convert_typing(extract_type(type(_doc.get("symbols"))))
                     _errors__.append(
                         ValidationException(
-                            "the `specializeFrom` field is not valid because:",
-                            SourceLine(_doc, "specializeFrom", str),
+                            "the `symbols` field is not valid because:",
+                            SourceLine(_doc, "symbols", str),
                             [ValidationException(f"Value is a {val_type}, "
                                                  f"but valid {to_print} for this field "
                                                  f"{verb_tensage} {error_message}")],
@@ -2721,27 +4692,27 @@ def fromDoc(
                 else:
                     _errors__.append(
                         ValidationException(
-                            "the `specializeFrom` field is not valid because:",
-                            SourceLine(_doc, "specializeFrom", str),
+                            "the `symbols` field is not valid because:",
+                            SourceLine(_doc, "symbols", str),
                             [e],
                         )
                     )
         try:
-            if _doc.get("specializeTo") is None:
-                raise ValidationException("missing required field `specializeTo`", None, [])
+            if _doc.get("type") is None:
+                raise ValidationException("missing required field `type`", None, [])
 
-            specializeTo = load_field(
-                _doc.get("specializeTo"),
-                uri_strtype_False_False_1_None,
+            type_ = load_field(
+                _doc.get("type"),
+                typedsl_Enum_nameLoader_2,
                 baseuri,
                 loadingOptions,
-                lc=_doc.get("specializeTo")
+                lc=_doc.get("type")
             )
 
         except ValidationException as e:
             error_message, to_print, verb_tensage = parse_errors(str(e))
 
-            if str(e) == "missing required field `specializeTo`":
+            if str(e) == "missing required field `type`":
                 _errors__.append(
                     ValidationException(
                         str(e),
@@ -2750,11 +4721,11 @@ def fromDoc(
                 )
             else:
                 if error_message != str(e):
-                    val_type = convert_typing(extract_type(type(_doc.get("specializeTo"))))
+                    val_type = convert_typing(extract_type(type(_doc.get("type"))))
                     _errors__.append(
                         ValidationException(
-                            "the `specializeTo` field is not valid because:",
-                            SourceLine(_doc, "specializeTo", str),
+                            "the `type` field is not valid because:",
+                            SourceLine(_doc, "type", str),
                             [ValidationException(f"Value is a {val_type}, "
                                                  f"but valid {to_print} for this field "
                                                  f"{verb_tensage} {error_message}")],
@@ -2763,167 +4734,67 @@ def fromDoc(
                 else:
                     _errors__.append(
                         ValidationException(
-                            "the `specializeTo` field is not valid because:",
-                            SourceLine(_doc, "specializeTo", str),
+                            "the `type` field is not valid because:",
+                            SourceLine(_doc, "type", str),
                             [e],
                         )
                     )
-        extension_fields: Dict[str, Any] = {}
-        for k in _doc.keys():
-            if k not in cls.attrs:
-                if not k:
-                    _errors__.append(
-                        ValidationException("mapping with implicit null key")
-                    )
-                elif ":" in k:
-                    ex = expand_url(
-                        k, "", loadingOptions, scoped_id=False, vocab_term=False
-                    )
-                    extension_fields[ex] = _doc[k]
-                else:
-                    _errors__.append(
-                        ValidationException(
-                            "invalid field `{}`, expected one of: `specializeFrom`, `specializeTo`".format(
-                                k
-                            ),
-                            SourceLine(_doc, k, str),
-                        )
-                    )
-
-        if _errors__:
-            raise ValidationException("", None, _errors__, "*")
-        _constructed = cls(
-            specializeFrom=specializeFrom,
-            specializeTo=specializeTo,
-            extension_fields=extension_fields,
-            loadingOptions=loadingOptions,
-        )
-        return _constructed
-
-    def save(
-        self, top: bool = False, base_url: str = "", relative_uris: bool = True
-    ) -> Dict[str, Any]:
-        r: Dict[str, Any] = {}
-
-        if relative_uris:
-            for ef in self.extension_fields:
-                r[prefix_url(ef, self.loadingOptions.vocab)] = self.extension_fields[ef]
-        else:
-            for ef in self.extension_fields:
-                r[ef] = self.extension_fields[ef]
-        if self.specializeFrom is not None:
-            u = save_relative_uri(
-                self.specializeFrom, base_url, False, 1, relative_uris
-            )
-            r["specializeFrom"] = u
-        if self.specializeTo is not None:
-            u = save_relative_uri(self.specializeTo, base_url, False, 1, relative_uris)
-            r["specializeTo"] = u
-
-        # top refers to the directory level
-        if top:
-            if self.loadingOptions.namespaces:
-                r["$namespaces"] = self.loadingOptions.namespaces
-            if self.loadingOptions.schemas:
-                r["$schemas"] = self.loadingOptions.schemas
-        return r
-
-    attrs = frozenset(["specializeFrom", "specializeTo"])
-
-
-class NamedType(Saveable):
-    pass
-
-
-class DocType(Documented):
-    pass
-
-
-class SchemaDefinedType(DocType):
-    """
-    Abstract base for schema-defined types.
-
-    """
-
-    pass
-
-
-class SaladRecordField(RecordField):
-    """
-    A field of a record.
-    """
-
-    def __init__(
-        self,
-        name: Any,
-        type_: Any,
-        doc: Optional[Any] = None,
-        jsonldPredicate: Optional[Any] = None,
-        default: Optional[Any] = None,
-        extension_fields: Optional[Dict[str, Any]] = None,
-        loadingOptions: Optional[LoadingOptions] = None,
-    ) -> None:
-        if extension_fields:
-            self.extension_fields = extension_fields
-        else:
-            self.extension_fields = CommentedMap()
-        if loadingOptions:
-            self.loadingOptions = loadingOptions
-        else:
-            self.loadingOptions = LoadingOptions()
-        self.doc = doc
-        self.name = name
-        self.type_ = type_
-        self.jsonldPredicate = jsonldPredicate
-        self.default = default
-
-    def __eq__(self, other: Any) -> bool:
-        if isinstance(other, SaladRecordField):
-            return bool(
-                self.doc == other.doc
-                and self.name == other.name
-                and self.type_ == other.type_
-                and self.jsonldPredicate == other.jsonldPredicate
-                and self.default == other.default
-            )
-        return False
-
-    def __hash__(self) -> int:
-        return hash(
-            (self.doc, self.name, self.type_, self.jsonldPredicate, self.default)
-        )
+        if "doc" in _doc:
+            try:
+                doc = load_field(
+                    _doc.get("doc"),
+                    union_of_None_type_or_strtype_or_array_of_strtype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("doc")
+                )
 
-    @classmethod
-    def fromDoc(
-        cls,
-        doc: Any,
-        baseuri: str,
-        loadingOptions: LoadingOptions,
-        docRoot: Optional[str] = None
-    ) -> "SaladRecordField":
-        _doc = copy.copy(doc)
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
 
-        if hasattr(doc, "lc"):
-            _doc.lc.data = doc.lc.data
-            _doc.lc.filename = doc.lc.filename
-        _errors__ = []
-        if "name" in _doc:
+                if str(e) == "missing required field `doc`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("doc"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `doc` field is not valid because:",
+                                SourceLine(_doc, "doc", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `doc` field is not valid because:",
+                                SourceLine(_doc, "doc", str),
+                                [e],
+                            )
+                        )
+        else:
+            doc = None
+        if "docParent" in _doc:
             try:
-                if _doc.get("name") is None:
-                    raise ValidationException("missing required field `name`", None, [])
-
-                name = load_field(
-                    _doc.get("name"),
-                    uri_strtype_True_False_None_None,
+                docParent = load_field(
+                    _doc.get("docParent"),
+                    uri_union_of_None_type_or_strtype_False_False_None_None,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("name")
+                    lc=_doc.get("docParent")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `name`":
+                if str(e) == "missing required field `docParent`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2932,11 +4803,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("name"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("docParent"))))
                         _errors__.append(
                             ValidationException(
-                                "the `name` field is not valid because:",
-                                SourceLine(_doc, "name", str),
+                                "the `docParent` field is not valid because:",
+                                SourceLine(_doc, "docParent", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2945,39 +4816,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `name` field is not valid because:",
-                                SourceLine(_doc, "name", str),
+                                "the `docParent` field is not valid because:",
+                                SourceLine(_doc, "docParent", str),
                                 [e],
                             )
                         )
         else:
-            name = None
-
-        __original_name_is_none = name is None
-        if name is None:
-            if docRoot is not None:
-                name = docRoot
-            else:
-                _errors__.append(ValidationException("missing name"))
-        if not __original_name_is_none:
-            baseuri = name
-        if "doc" in _doc:
+            docParent = None
+        if "docChild" in _doc:
             try:
-                if _doc.get("doc") is None:
-                    raise ValidationException("missing required field `doc`", None, [])
-
-                doc = load_field(
-                    _doc.get("doc"),
-                    union_of_None_type_or_strtype_or_array_of_strtype,
+                docChild = load_field(
+                    _doc.get("docChild"),
+                    uri_union_of_None_type_or_strtype_or_array_of_strtype_False_False_None_None,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("doc")
+                    lc=_doc.get("docChild")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `doc`":
+                if str(e) == "missing required field `docChild`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -2986,11 +4845,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("doc"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("docChild"))))
                         _errors__.append(
                             ValidationException(
-                                "the `doc` field is not valid because:",
-                                SourceLine(_doc, "doc", str),
+                                "the `docChild` field is not valid because:",
+                                SourceLine(_doc, "docChild", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -2999,60 +4858,57 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `doc` field is not valid because:",
-                                SourceLine(_doc, "doc", str),
+                                "the `docChild` field is not valid because:",
+                                SourceLine(_doc, "docChild", str),
                                 [e],
                             )
                         )
         else:
-            doc = None
-        try:
-            if _doc.get("type") is None:
-                raise ValidationException("missing required field `type`", None, [])
-
-            type_ = load_field(
-                _doc.get("type"),
-                typedsl_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_2,
-                baseuri,
-                loadingOptions,
-                lc=_doc.get("type")
-            )
+            docChild = None
+        if "docAfter" in _doc:
+            try:
+                docAfter = load_field(
+                    _doc.get("docAfter"),
+                    uri_union_of_None_type_or_strtype_False_False_None_None,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("docAfter")
+                )
 
-        except ValidationException as e:
-            error_message, to_print, verb_tensage = parse_errors(str(e))
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
 
-            if str(e) == "missing required field `type`":
-                _errors__.append(
-                    ValidationException(
-                        str(e),
-                        None
-                    )
-                )
-            else:
-                if error_message != str(e):
-                    val_type = convert_typing(extract_type(type(_doc.get("type"))))
+                if str(e) == "missing required field `docAfter`":
                     _errors__.append(
                         ValidationException(
-                            "the `type` field is not valid because:",
-                            SourceLine(_doc, "type", str),
-                            [ValidationException(f"Value is a {val_type}, "
-                                                 f"but valid {to_print} for this field "
-                                                 f"{verb_tensage} {error_message}")],
+                            str(e),
+                            None
                         )
                     )
                 else:
-                    _errors__.append(
-                        ValidationException(
-                            "the `type` field is not valid because:",
-                            SourceLine(_doc, "type", str),
-                            [e],
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("docAfter"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `docAfter` field is not valid because:",
+                                SourceLine(_doc, "docAfter", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
                         )
-                    )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `docAfter` field is not valid because:",
+                                SourceLine(_doc, "docAfter", str),
+                                [e],
+                            )
+                        )
+        else:
+            docAfter = None
         if "jsonldPredicate" in _doc:
             try:
-                if _doc.get("jsonldPredicate") is None:
-                    raise ValidationException("missing required field `jsonldPredicate`", None, [])
-
                 jsonldPredicate = load_field(
                     _doc.get("jsonldPredicate"),
                     union_of_None_type_or_strtype_or_JsonldPredicateLoader,
@@ -3093,23 +4949,62 @@ def fromDoc(
                         )
         else:
             jsonldPredicate = None
-        if "default" in _doc:
+        if "documentRoot" in _doc:
             try:
-                if _doc.get("default") is None:
-                    raise ValidationException("missing required field `default`", None, [])
+                documentRoot = load_field(
+                    _doc.get("documentRoot"),
+                    union_of_None_type_or_booltype,
+                    baseuri,
+                    loadingOptions,
+                    lc=_doc.get("documentRoot")
+                )
 
-                default = load_field(
-                    _doc.get("default"),
-                    union_of_None_type_or_Any_type,
+            except ValidationException as e:
+                error_message, to_print, verb_tensage = parse_errors(str(e))
+
+                if str(e) == "missing required field `documentRoot`":
+                    _errors__.append(
+                        ValidationException(
+                            str(e),
+                            None
+                        )
+                    )
+                else:
+                    if error_message != str(e):
+                        val_type = convert_typing(extract_type(type(_doc.get("documentRoot"))))
+                        _errors__.append(
+                            ValidationException(
+                                "the `documentRoot` field is not valid because:",
+                                SourceLine(_doc, "documentRoot", str),
+                                [ValidationException(f"Value is a {val_type}, "
+                                                     f"but valid {to_print} for this field "
+                                                     f"{verb_tensage} {error_message}")],
+                            )
+                        )
+                    else:
+                        _errors__.append(
+                            ValidationException(
+                                "the `documentRoot` field is not valid because:",
+                                SourceLine(_doc, "documentRoot", str),
+                                [e],
+                            )
+                        )
+        else:
+            documentRoot = None
+        if "extends" in _doc:
+            try:
+                extends = load_field(
+                    _doc.get("extends"),
+                    uri_union_of_None_type_or_strtype_or_array_of_strtype_False_False_1_None,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("default")
+                    lc=_doc.get("extends")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `default`":
+                if str(e) == "missing required field `extends`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -3118,11 +5013,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("default"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("extends"))))
                         _errors__.append(
                             ValidationException(
-                                "the `default` field is not valid because:",
-                                SourceLine(_doc, "default", str),
+                                "the `extends` field is not valid because:",
+                                SourceLine(_doc, "extends", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -3131,13 +5026,13 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `default` field is not valid because:",
-                                SourceLine(_doc, "default", str),
+                                "the `extends` field is not valid because:",
+                                SourceLine(_doc, "extends", str),
                                 [e],
                             )
                         )
         else:
-            default = None
+            extends = None
         extension_fields: Dict[str, Any] = {}
         for k in _doc.keys():
             if k not in cls.attrs:
@@ -3153,7 +5048,7 @@ def fromDoc(
                 else:
                     _errors__.append(
                         ValidationException(
-                            "invalid field `{}`, expected one of: `doc`, `name`, `type`, `jsonldPredicate`, `default`".format(
+                            "invalid field `{}`, expected one of: `name`, `inVocab`, `symbols`, `type`, `doc`, `docParent`, `docChild`, `docAfter`, `jsonldPredicate`, `documentRoot`, `extends`".format(
                                 k
                             ),
                             SourceLine(_doc, k, str),
@@ -3163,11 +5058,17 @@ def fromDoc(
         if _errors__:
             raise ValidationException("", None, _errors__, "*")
         _constructed = cls(
-            doc=doc,
             name=name,
+            inVocab=inVocab,
+            symbols=symbols,
             type_=type_,
+            doc=doc,
+            docParent=docParent,
+            docChild=docChild,
+            docAfter=docAfter,
             jsonldPredicate=jsonldPredicate,
-            default=default,
+            documentRoot=documentRoot,
+            extends=extends,
             extension_fields=extension_fields,
             loadingOptions=loadingOptions,
         )
@@ -3188,14 +5089,30 @@ def save(
         if self.name is not None:
             u = save_relative_uri(self.name, base_url, True, None, relative_uris)
             r["name"] = u
-        if self.doc is not None:
-            r["doc"] = save(
-                self.doc, top=False, base_url=self.name, relative_uris=relative_uris
+        if self.inVocab is not None:
+            r["inVocab"] = save(
+                self.inVocab, top=False, base_url=self.name, relative_uris=relative_uris
             )
+        if self.symbols is not None:
+            u = save_relative_uri(self.symbols, self.name, True, None, relative_uris)
+            r["symbols"] = u
         if self.type_ is not None:
             r["type"] = save(
                 self.type_, top=False, base_url=self.name, relative_uris=relative_uris
             )
+        if self.doc is not None:
+            r["doc"] = save(
+                self.doc, top=False, base_url=self.name, relative_uris=relative_uris
+            )
+        if self.docParent is not None:
+            u = save_relative_uri(self.docParent, self.name, False, None, relative_uris)
+            r["docParent"] = u
+        if self.docChild is not None:
+            u = save_relative_uri(self.docChild, self.name, False, None, relative_uris)
+            r["docChild"] = u
+        if self.docAfter is not None:
+            u = save_relative_uri(self.docAfter, self.name, False, None, relative_uris)
+            r["docAfter"] = u
         if self.jsonldPredicate is not None:
             r["jsonldPredicate"] = save(
                 self.jsonldPredicate,
@@ -3203,10 +5120,16 @@ def save(
                 base_url=self.name,
                 relative_uris=relative_uris,
             )
-        if self.default is not None:
-            r["default"] = save(
-                self.default, top=False, base_url=self.name, relative_uris=relative_uris
-            )
+        if self.documentRoot is not None:
+            r["documentRoot"] = save(
+                self.documentRoot,
+                top=False,
+                base_url=self.name,
+                relative_uris=relative_uris,
+            )
+        if self.extends is not None:
+            u = save_relative_uri(self.extends, self.name, False, 1, relative_uris)
+            r["extends"] = u
 
         # top refers to the directory level
         if top:
@@ -3216,25 +5139,41 @@ def save(
                 r["$schemas"] = self.loadingOptions.schemas
         return r
 
-    attrs = frozenset(["doc", "name", "type", "jsonldPredicate", "default"])
+    attrs = frozenset(
+        [
+            "name",
+            "inVocab",
+            "symbols",
+            "type",
+            "doc",
+            "docParent",
+            "docChild",
+            "docAfter",
+            "jsonldPredicate",
+            "documentRoot",
+            "extends",
+        ]
+    )
 
 
-class SaladRecordSchema(NamedType, RecordSchema, SchemaDefinedType):
+class SaladMapSchema(NamedType, MapSchema, SchemaDefinedType):
+    """
+    Define a map type.
+
+    """
+
     def __init__(
         self,
         name: Any,
         type_: Any,
+        values: Any,
         inVocab: Optional[Any] = None,
-        fields: Optional[Any] = None,
         doc: Optional[Any] = None,
         docParent: Optional[Any] = None,
         docChild: Optional[Any] = None,
         docAfter: Optional[Any] = None,
         jsonldPredicate: Optional[Any] = None,
         documentRoot: Optional[Any] = None,
-        abstract: Optional[Any] = None,
-        extends: Optional[Any] = None,
-        specialize: Optional[Any] = None,
         extension_fields: Optional[Dict[str, Any]] = None,
         loadingOptions: Optional[LoadingOptions] = None,
     ) -> None:
@@ -3248,34 +5187,28 @@ def __init__(
             self.loadingOptions = LoadingOptions()
         self.name = name
         self.inVocab = inVocab
-        self.fields = fields
         self.type_ = type_
+        self.values = values
         self.doc = doc
         self.docParent = docParent
         self.docChild = docChild
         self.docAfter = docAfter
         self.jsonldPredicate = jsonldPredicate
         self.documentRoot = documentRoot
-        self.abstract = abstract
-        self.extends = extends
-        self.specialize = specialize
 
     def __eq__(self, other: Any) -> bool:
-        if isinstance(other, SaladRecordSchema):
+        if isinstance(other, SaladMapSchema):
             return bool(
                 self.name == other.name
                 and self.inVocab == other.inVocab
-                and self.fields == other.fields
                 and self.type_ == other.type_
+                and self.values == other.values
                 and self.doc == other.doc
                 and self.docParent == other.docParent
                 and self.docChild == other.docChild
                 and self.docAfter == other.docAfter
                 and self.jsonldPredicate == other.jsonldPredicate
                 and self.documentRoot == other.documentRoot
-                and self.abstract == other.abstract
-                and self.extends == other.extends
-                and self.specialize == other.specialize
             )
         return False
 
@@ -3284,17 +5217,14 @@ def __hash__(self) -> int:
             (
                 self.name,
                 self.inVocab,
-                self.fields,
                 self.type_,
+                self.values,
                 self.doc,
                 self.docParent,
                 self.docChild,
                 self.docAfter,
                 self.jsonldPredicate,
                 self.documentRoot,
-                self.abstract,
-                self.extends,
-                self.specialize,
             )
         )
 
@@ -3305,7 +5235,7 @@ def fromDoc(
         baseuri: str,
         loadingOptions: LoadingOptions,
         docRoot: Optional[str] = None
-    ) -> "SaladRecordSchema":
+    ) -> "SaladMapSchema":
         _doc = copy.copy(doc)
 
         if hasattr(doc, "lc"):
@@ -3314,9 +5244,6 @@ def fromDoc(
         _errors__ = []
         if "name" in _doc:
             try:
-                if _doc.get("name") is None:
-                    raise ValidationException("missing required field `name`", None, [])
-
                 name = load_field(
                     _doc.get("name"),
                     uri_strtype_True_False_None_None,
@@ -3368,9 +5295,6 @@ def fromDoc(
             baseuri = name
         if "inVocab" in _doc:
             try:
-                if _doc.get("inVocab") is None:
-                    raise ValidationException("missing required field `inVocab`", None, [])
-
                 inVocab = load_field(
                     _doc.get("inVocab"),
                     union_of_None_type_or_booltype,
@@ -3411,58 +5335,13 @@ def fromDoc(
                         )
         else:
             inVocab = None
-        if "fields" in _doc:
-            try:
-                if _doc.get("fields") is None:
-                    raise ValidationException("missing required field `fields`", None, [])
-
-                fields = load_field(
-                    _doc.get("fields"),
-                    idmap_fields_union_of_None_type_or_array_of_SaladRecordFieldLoader,
-                    baseuri,
-                    loadingOptions,
-                    lc=_doc.get("fields")
-                )
-
-            except ValidationException as e:
-                error_message, to_print, verb_tensage = parse_errors(str(e))
-
-                if str(e) == "missing required field `fields`":
-                    _errors__.append(
-                        ValidationException(
-                            str(e),
-                            None
-                        )
-                    )
-                else:
-                    if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("fields"))))
-                        _errors__.append(
-                            ValidationException(
-                                "the `fields` field is not valid because:",
-                                SourceLine(_doc, "fields", str),
-                                [ValidationException(f"Value is a {val_type}, "
-                                                     f"but valid {to_print} for this field "
-                                                     f"{verb_tensage} {error_message}")],
-                            )
-                        )
-                    else:
-                        _errors__.append(
-                            ValidationException(
-                                "the `fields` field is not valid because:",
-                                SourceLine(_doc, "fields", str),
-                                [e],
-                            )
-                        )
-        else:
-            fields = None
         try:
             if _doc.get("type") is None:
                 raise ValidationException("missing required field `type`", None, [])
 
             type_ = load_field(
                 _doc.get("type"),
-                typedsl_Record_nameLoader_2,
+                typedsl_Map_nameLoader_2,
                 baseuri,
                 loadingOptions,
                 lc=_doc.get("type")
@@ -3498,11 +5377,50 @@ def fromDoc(
                             [e],
                         )
                     )
+        try:
+            if _doc.get("values") is None:
+                raise ValidationException("missing required field `values`", None, [])
+
+            values = load_field(
+                _doc.get("values"),
+                uri_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_False_True_2_None,
+                baseuri,
+                loadingOptions,
+                lc=_doc.get("values")
+            )
+
+        except ValidationException as e:
+            error_message, to_print, verb_tensage = parse_errors(str(e))
+
+            if str(e) == "missing required field `values`":
+                _errors__.append(
+                    ValidationException(
+                        str(e),
+                        None
+                    )
+                )
+            else:
+                if error_message != str(e):
+                    val_type = convert_typing(extract_type(type(_doc.get("values"))))
+                    _errors__.append(
+                        ValidationException(
+                            "the `values` field is not valid because:",
+                            SourceLine(_doc, "values", str),
+                            [ValidationException(f"Value is a {val_type}, "
+                                                 f"but valid {to_print} for this field "
+                                                 f"{verb_tensage} {error_message}")],
+                        )
+                    )
+                else:
+                    _errors__.append(
+                        ValidationException(
+                            "the `values` field is not valid because:",
+                            SourceLine(_doc, "values", str),
+                            [e],
+                        )
+                    )
         if "doc" in _doc:
             try:
-                if _doc.get("doc") is None:
-                    raise ValidationException("missing required field `doc`", None, [])
-
                 doc = load_field(
                     _doc.get("doc"),
                     union_of_None_type_or_strtype_or_array_of_strtype,
@@ -3545,9 +5463,6 @@ def fromDoc(
             doc = None
         if "docParent" in _doc:
             try:
-                if _doc.get("docParent") is None:
-                    raise ValidationException("missing required field `docParent`", None, [])
-
                 docParent = load_field(
                     _doc.get("docParent"),
                     uri_union_of_None_type_or_strtype_False_False_None_None,
@@ -3590,9 +5505,6 @@ def fromDoc(
             docParent = None
         if "docChild" in _doc:
             try:
-                if _doc.get("docChild") is None:
-                    raise ValidationException("missing required field `docChild`", None, [])
-
                 docChild = load_field(
                     _doc.get("docChild"),
                     uri_union_of_None_type_or_strtype_or_array_of_strtype_False_False_None_None,
@@ -3635,156 +5547,18 @@ def fromDoc(
             docChild = None
         if "docAfter" in _doc:
             try:
-                if _doc.get("docAfter") is None:
-                    raise ValidationException("missing required field `docAfter`", None, [])
-
                 docAfter = load_field(
                     _doc.get("docAfter"),
-                    uri_union_of_None_type_or_strtype_False_False_None_None,
-                    baseuri,
-                    loadingOptions,
-                    lc=_doc.get("docAfter")
-                )
-
-            except ValidationException as e:
-                error_message, to_print, verb_tensage = parse_errors(str(e))
-
-                if str(e) == "missing required field `docAfter`":
-                    _errors__.append(
-                        ValidationException(
-                            str(e),
-                            None
-                        )
-                    )
-                else:
-                    if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("docAfter"))))
-                        _errors__.append(
-                            ValidationException(
-                                "the `docAfter` field is not valid because:",
-                                SourceLine(_doc, "docAfter", str),
-                                [ValidationException(f"Value is a {val_type}, "
-                                                     f"but valid {to_print} for this field "
-                                                     f"{verb_tensage} {error_message}")],
-                            )
-                        )
-                    else:
-                        _errors__.append(
-                            ValidationException(
-                                "the `docAfter` field is not valid because:",
-                                SourceLine(_doc, "docAfter", str),
-                                [e],
-                            )
-                        )
-        else:
-            docAfter = None
-        if "jsonldPredicate" in _doc:
-            try:
-                if _doc.get("jsonldPredicate") is None:
-                    raise ValidationException("missing required field `jsonldPredicate`", None, [])
-
-                jsonldPredicate = load_field(
-                    _doc.get("jsonldPredicate"),
-                    union_of_None_type_or_strtype_or_JsonldPredicateLoader,
-                    baseuri,
-                    loadingOptions,
-                    lc=_doc.get("jsonldPredicate")
-                )
-
-            except ValidationException as e:
-                error_message, to_print, verb_tensage = parse_errors(str(e))
-
-                if str(e) == "missing required field `jsonldPredicate`":
-                    _errors__.append(
-                        ValidationException(
-                            str(e),
-                            None
-                        )
-                    )
-                else:
-                    if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("jsonldPredicate"))))
-                        _errors__.append(
-                            ValidationException(
-                                "the `jsonldPredicate` field is not valid because:",
-                                SourceLine(_doc, "jsonldPredicate", str),
-                                [ValidationException(f"Value is a {val_type}, "
-                                                     f"but valid {to_print} for this field "
-                                                     f"{verb_tensage} {error_message}")],
-                            )
-                        )
-                    else:
-                        _errors__.append(
-                            ValidationException(
-                                "the `jsonldPredicate` field is not valid because:",
-                                SourceLine(_doc, "jsonldPredicate", str),
-                                [e],
-                            )
-                        )
-        else:
-            jsonldPredicate = None
-        if "documentRoot" in _doc:
-            try:
-                if _doc.get("documentRoot") is None:
-                    raise ValidationException("missing required field `documentRoot`", None, [])
-
-                documentRoot = load_field(
-                    _doc.get("documentRoot"),
-                    union_of_None_type_or_booltype,
-                    baseuri,
-                    loadingOptions,
-                    lc=_doc.get("documentRoot")
-                )
-
-            except ValidationException as e:
-                error_message, to_print, verb_tensage = parse_errors(str(e))
-
-                if str(e) == "missing required field `documentRoot`":
-                    _errors__.append(
-                        ValidationException(
-                            str(e),
-                            None
-                        )
-                    )
-                else:
-                    if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("documentRoot"))))
-                        _errors__.append(
-                            ValidationException(
-                                "the `documentRoot` field is not valid because:",
-                                SourceLine(_doc, "documentRoot", str),
-                                [ValidationException(f"Value is a {val_type}, "
-                                                     f"but valid {to_print} for this field "
-                                                     f"{verb_tensage} {error_message}")],
-                            )
-                        )
-                    else:
-                        _errors__.append(
-                            ValidationException(
-                                "the `documentRoot` field is not valid because:",
-                                SourceLine(_doc, "documentRoot", str),
-                                [e],
-                            )
-                        )
-        else:
-            documentRoot = None
-        if "abstract" in _doc:
-            try:
-                if _doc.get("abstract") is None:
-                    raise ValidationException("missing required field `abstract`", None, [])
-
-                abstract = load_field(
-                    _doc.get("abstract"),
-                    union_of_None_type_or_booltype,
+                    uri_union_of_None_type_or_strtype_False_False_None_None,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("abstract")
+                    lc=_doc.get("docAfter")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `abstract`":
+                if str(e) == "missing required field `docAfter`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -3793,11 +5567,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("abstract"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("docAfter"))))
                         _errors__.append(
                             ValidationException(
-                                "the `abstract` field is not valid because:",
-                                SourceLine(_doc, "abstract", str),
+                                "the `docAfter` field is not valid because:",
+                                SourceLine(_doc, "docAfter", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -3806,30 +5580,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `abstract` field is not valid because:",
-                                SourceLine(_doc, "abstract", str),
+                                "the `docAfter` field is not valid because:",
+                                SourceLine(_doc, "docAfter", str),
                                 [e],
                             )
                         )
         else:
-            abstract = None
-        if "extends" in _doc:
+            docAfter = None
+        if "jsonldPredicate" in _doc:
             try:
-                if _doc.get("extends") is None:
-                    raise ValidationException("missing required field `extends`", None, [])
-
-                extends = load_field(
-                    _doc.get("extends"),
-                    uri_union_of_None_type_or_strtype_or_array_of_strtype_False_False_1_None,
+                jsonldPredicate = load_field(
+                    _doc.get("jsonldPredicate"),
+                    union_of_None_type_or_strtype_or_JsonldPredicateLoader,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("extends")
+                    lc=_doc.get("jsonldPredicate")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `extends`":
+                if str(e) == "missing required field `jsonldPredicate`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -3838,11 +5609,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("extends"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("jsonldPredicate"))))
                         _errors__.append(
                             ValidationException(
-                                "the `extends` field is not valid because:",
-                                SourceLine(_doc, "extends", str),
+                                "the `jsonldPredicate` field is not valid because:",
+                                SourceLine(_doc, "jsonldPredicate", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -3851,30 +5622,27 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `extends` field is not valid because:",
-                                SourceLine(_doc, "extends", str),
+                                "the `jsonldPredicate` field is not valid because:",
+                                SourceLine(_doc, "jsonldPredicate", str),
                                 [e],
                             )
                         )
         else:
-            extends = None
-        if "specialize" in _doc:
+            jsonldPredicate = None
+        if "documentRoot" in _doc:
             try:
-                if _doc.get("specialize") is None:
-                    raise ValidationException("missing required field `specialize`", None, [])
-
-                specialize = load_field(
-                    _doc.get("specialize"),
-                    idmap_specialize_union_of_None_type_or_array_of_SpecializeDefLoader,
+                documentRoot = load_field(
+                    _doc.get("documentRoot"),
+                    union_of_None_type_or_booltype,
                     baseuri,
                     loadingOptions,
-                    lc=_doc.get("specialize")
+                    lc=_doc.get("documentRoot")
                 )
 
             except ValidationException as e:
                 error_message, to_print, verb_tensage = parse_errors(str(e))
 
-                if str(e) == "missing required field `specialize`":
+                if str(e) == "missing required field `documentRoot`":
                     _errors__.append(
                         ValidationException(
                             str(e),
@@ -3883,11 +5651,11 @@ def fromDoc(
                     )
                 else:
                     if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("specialize"))))
+                        val_type = convert_typing(extract_type(type(_doc.get("documentRoot"))))
                         _errors__.append(
                             ValidationException(
-                                "the `specialize` field is not valid because:",
-                                SourceLine(_doc, "specialize", str),
+                                "the `documentRoot` field is not valid because:",
+                                SourceLine(_doc, "documentRoot", str),
                                 [ValidationException(f"Value is a {val_type}, "
                                                      f"but valid {to_print} for this field "
                                                      f"{verb_tensage} {error_message}")],
@@ -3896,13 +5664,13 @@ def fromDoc(
                     else:
                         _errors__.append(
                             ValidationException(
-                                "the `specialize` field is not valid because:",
-                                SourceLine(_doc, "specialize", str),
+                                "the `documentRoot` field is not valid because:",
+                                SourceLine(_doc, "documentRoot", str),
                                 [e],
                             )
                         )
         else:
-            specialize = None
+            documentRoot = None
         extension_fields: Dict[str, Any] = {}
         for k in _doc.keys():
             if k not in cls.attrs:
@@ -3918,7 +5686,7 @@ def fromDoc(
                 else:
                     _errors__.append(
                         ValidationException(
-                            "invalid field `{}`, expected one of: `name`, `inVocab`, `fields`, `type`, `doc`, `docParent`, `docChild`, `docAfter`, `jsonldPredicate`, `documentRoot`, `abstract`, `extends`, `specialize`".format(
+                            "invalid field `{}`, expected one of: `name`, `inVocab`, `type`, `values`, `doc`, `docParent`, `docChild`, `docAfter`, `jsonldPredicate`, `documentRoot`".format(
                                 k
                             ),
                             SourceLine(_doc, k, str),
@@ -3930,17 +5698,14 @@ def fromDoc(
         _constructed = cls(
             name=name,
             inVocab=inVocab,
-            fields=fields,
             type_=type_,
+            values=values,
             doc=doc,
             docParent=docParent,
             docChild=docChild,
             docAfter=docAfter,
             jsonldPredicate=jsonldPredicate,
             documentRoot=documentRoot,
-            abstract=abstract,
-            extends=extends,
-            specialize=specialize,
             extension_fields=extension_fields,
             loadingOptions=loadingOptions,
         )
@@ -3965,14 +5730,13 @@ def save(
             r["inVocab"] = save(
                 self.inVocab, top=False, base_url=self.name, relative_uris=relative_uris
             )
-        if self.fields is not None:
-            r["fields"] = save(
-                self.fields, top=False, base_url=self.name, relative_uris=relative_uris
-            )
         if self.type_ is not None:
             r["type"] = save(
                 self.type_, top=False, base_url=self.name, relative_uris=relative_uris
             )
+        if self.values is not None:
+            u = save_relative_uri(self.values, self.name, False, 2, relative_uris)
+            r["values"] = u
         if self.doc is not None:
             r["doc"] = save(
                 self.doc, top=False, base_url=self.name, relative_uris=relative_uris
@@ -4000,23 +5764,6 @@ def save(
                 base_url=self.name,
                 relative_uris=relative_uris,
             )
-        if self.abstract is not None:
-            r["abstract"] = save(
-                self.abstract,
-                top=False,
-                base_url=self.name,
-                relative_uris=relative_uris,
-            )
-        if self.extends is not None:
-            u = save_relative_uri(self.extends, self.name, False, 1, relative_uris)
-            r["extends"] = u
-        if self.specialize is not None:
-            r["specialize"] = save(
-                self.specialize,
-                top=False,
-                base_url=self.name,
-                relative_uris=relative_uris,
-            )
 
         # top refers to the directory level
         if top:
@@ -4030,40 +5777,35 @@ def save(
         [
             "name",
             "inVocab",
-            "fields",
             "type",
+            "values",
             "doc",
             "docParent",
             "docChild",
             "docAfter",
             "jsonldPredicate",
             "documentRoot",
-            "abstract",
-            "extends",
-            "specialize",
         ]
     )
 
 
-class SaladEnumSchema(NamedType, EnumSchema, SchemaDefinedType):
+class SaladUnionSchema(NamedType, UnionSchema, DocType):
     """
-    Define an enumerated type.
+    Define a union type.
 
     """
 
     def __init__(
         self,
-        symbols: Any,
+        name: Any,
+        names: Any,
         type_: Any,
-        name: Optional[Any] = None,
         inVocab: Optional[Any] = None,
         doc: Optional[Any] = None,
         docParent: Optional[Any] = None,
         docChild: Optional[Any] = None,
         docAfter: Optional[Any] = None,
-        jsonldPredicate: Optional[Any] = None,
         documentRoot: Optional[Any] = None,
-        extends: Optional[Any] = None,
         extension_fields: Optional[Dict[str, Any]] = None,
         loadingOptions: Optional[LoadingOptions] = None,
     ) -> None:
@@ -4077,30 +5819,26 @@ def __init__(
             self.loadingOptions = LoadingOptions()
         self.name = name
         self.inVocab = inVocab
-        self.symbols = symbols
+        self.names = names
         self.type_ = type_
         self.doc = doc
         self.docParent = docParent
         self.docChild = docChild
         self.docAfter = docAfter
-        self.jsonldPredicate = jsonldPredicate
         self.documentRoot = documentRoot
-        self.extends = extends
 
     def __eq__(self, other: Any) -> bool:
-        if isinstance(other, SaladEnumSchema):
+        if isinstance(other, SaladUnionSchema):
             return bool(
                 self.name == other.name
                 and self.inVocab == other.inVocab
-                and self.symbols == other.symbols
+                and self.names == other.names
                 and self.type_ == other.type_
                 and self.doc == other.doc
                 and self.docParent == other.docParent
                 and self.docChild == other.docChild
                 and self.docAfter == other.docAfter
-                and self.jsonldPredicate == other.jsonldPredicate
                 and self.documentRoot == other.documentRoot
-                and self.extends == other.extends
             )
         return False
 
@@ -4109,15 +5847,13 @@ def __hash__(self) -> int:
             (
                 self.name,
                 self.inVocab,
-                self.symbols,
+                self.names,
                 self.type_,
                 self.doc,
                 self.docParent,
                 self.docChild,
                 self.docAfter,
-                self.jsonldPredicate,
                 self.documentRoot,
-                self.extends,
             )
         )
 
@@ -4128,7 +5864,7 @@ def fromDoc(
         baseuri: str,
         loadingOptions: LoadingOptions,
         docRoot: Optional[str] = None
-    ) -> "SaladEnumSchema":
+    ) -> "SaladUnionSchema":
         _doc = copy.copy(doc)
 
         if hasattr(doc, "lc"):
@@ -4137,12 +5873,9 @@ def fromDoc(
         _errors__ = []
         if "name" in _doc:
             try:
-                if _doc.get("name") is None:
-                    raise ValidationException("missing required field `name`", None, [])
-
                 name = load_field(
                     _doc.get("name"),
-                    uri_union_of_None_type_or_strtype_True_False_None_None,
+                    uri_strtype_True_False_None_None,
                     baseuri,
                     loadingOptions,
                     lc=_doc.get("name")
@@ -4186,14 +5919,11 @@ def fromDoc(
             if docRoot is not None:
                 name = docRoot
             else:
-                name = "_:" + str(_uuid__.uuid4())
+                _errors__.append(ValidationException("missing name"))
         if not __original_name_is_none:
             baseuri = name
         if "inVocab" in _doc:
             try:
-                if _doc.get("inVocab") is None:
-                    raise ValidationException("missing required field `inVocab`", None, [])
-
                 inVocab = load_field(
                     _doc.get("inVocab"),
                     union_of_None_type_or_booltype,
@@ -4235,21 +5965,21 @@ def fromDoc(
         else:
             inVocab = None
         try:
-            if _doc.get("symbols") is None:
-                raise ValidationException("missing required field `symbols`", None, [])
+            if _doc.get("names") is None:
+                raise ValidationException("missing required field `names`", None, [])
 
-            symbols = load_field(
-                _doc.get("symbols"),
-                uri_array_of_strtype_True_False_None_None,
+            names = load_field(
+                _doc.get("names"),
+                uri_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_False_True_2_None,
                 baseuri,
                 loadingOptions,
-                lc=_doc.get("symbols")
+                lc=_doc.get("names")
             )
 
         except ValidationException as e:
             error_message, to_print, verb_tensage = parse_errors(str(e))
 
-            if str(e) == "missing required field `symbols`":
+            if str(e) == "missing required field `names`":
                 _errors__.append(
                     ValidationException(
                         str(e),
@@ -4258,11 +5988,11 @@ def fromDoc(
                 )
             else:
                 if error_message != str(e):
-                    val_type = convert_typing(extract_type(type(_doc.get("symbols"))))
+                    val_type = convert_typing(extract_type(type(_doc.get("names"))))
                     _errors__.append(
                         ValidationException(
-                            "the `symbols` field is not valid because:",
-                            SourceLine(_doc, "symbols", str),
+                            "the `names` field is not valid because:",
+                            SourceLine(_doc, "names", str),
                             [ValidationException(f"Value is a {val_type}, "
                                                  f"but valid {to_print} for this field "
                                                  f"{verb_tensage} {error_message}")],
@@ -4271,8 +6001,8 @@ def fromDoc(
                 else:
                     _errors__.append(
                         ValidationException(
-                            "the `symbols` field is not valid because:",
-                            SourceLine(_doc, "symbols", str),
+                            "the `names` field is not valid because:",
+                            SourceLine(_doc, "names", str),
                             [e],
                         )
                     )
@@ -4282,7 +6012,7 @@ def fromDoc(
 
             type_ = load_field(
                 _doc.get("type"),
-                typedsl_Enum_nameLoader_2,
+                typedsl_Union_nameLoader_2,
                 baseuri,
                 loadingOptions,
                 lc=_doc.get("type")
@@ -4320,9 +6050,6 @@ def fromDoc(
                     )
         if "doc" in _doc:
             try:
-                if _doc.get("doc") is None:
-                    raise ValidationException("missing required field `doc`", None, [])
-
                 doc = load_field(
                     _doc.get("doc"),
                     union_of_None_type_or_strtype_or_array_of_strtype,
@@ -4365,9 +6092,6 @@ def fromDoc(
             doc = None
         if "docParent" in _doc:
             try:
-                if _doc.get("docParent") is None:
-                    raise ValidationException("missing required field `docParent`", None, [])
-
                 docParent = load_field(
                     _doc.get("docParent"),
                     uri_union_of_None_type_or_strtype_False_False_None_None,
@@ -4410,9 +6134,6 @@ def fromDoc(
             docParent = None
         if "docChild" in _doc:
             try:
-                if _doc.get("docChild") is None:
-                    raise ValidationException("missing required field `docChild`", None, [])
-
                 docChild = load_field(
                     _doc.get("docChild"),
                     uri_union_of_None_type_or_strtype_or_array_of_strtype_False_False_None_None,
@@ -4455,9 +6176,6 @@ def fromDoc(
             docChild = None
         if "docAfter" in _doc:
             try:
-                if _doc.get("docAfter") is None:
-                    raise ValidationException("missing required field `docAfter`", None, [])
-
                 docAfter = load_field(
                     _doc.get("docAfter"),
                     uri_union_of_None_type_or_strtype_False_False_None_None,
@@ -4498,56 +6216,8 @@ def fromDoc(
                         )
         else:
             docAfter = None
-        if "jsonldPredicate" in _doc:
-            try:
-                if _doc.get("jsonldPredicate") is None:
-                    raise ValidationException("missing required field `jsonldPredicate`", None, [])
-
-                jsonldPredicate = load_field(
-                    _doc.get("jsonldPredicate"),
-                    union_of_None_type_or_strtype_or_JsonldPredicateLoader,
-                    baseuri,
-                    loadingOptions,
-                    lc=_doc.get("jsonldPredicate")
-                )
-
-            except ValidationException as e:
-                error_message, to_print, verb_tensage = parse_errors(str(e))
-
-                if str(e) == "missing required field `jsonldPredicate`":
-                    _errors__.append(
-                        ValidationException(
-                            str(e),
-                            None
-                        )
-                    )
-                else:
-                    if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("jsonldPredicate"))))
-                        _errors__.append(
-                            ValidationException(
-                                "the `jsonldPredicate` field is not valid because:",
-                                SourceLine(_doc, "jsonldPredicate", str),
-                                [ValidationException(f"Value is a {val_type}, "
-                                                     f"but valid {to_print} for this field "
-                                                     f"{verb_tensage} {error_message}")],
-                            )
-                        )
-                    else:
-                        _errors__.append(
-                            ValidationException(
-                                "the `jsonldPredicate` field is not valid because:",
-                                SourceLine(_doc, "jsonldPredicate", str),
-                                [e],
-                            )
-                        )
-        else:
-            jsonldPredicate = None
         if "documentRoot" in _doc:
             try:
-                if _doc.get("documentRoot") is None:
-                    raise ValidationException("missing required field `documentRoot`", None, [])
-
                 documentRoot = load_field(
                     _doc.get("documentRoot"),
                     union_of_None_type_or_booltype,
@@ -4588,51 +6258,6 @@ def fromDoc(
                         )
         else:
             documentRoot = None
-        if "extends" in _doc:
-            try:
-                if _doc.get("extends") is None:
-                    raise ValidationException("missing required field `extends`", None, [])
-
-                extends = load_field(
-                    _doc.get("extends"),
-                    uri_union_of_None_type_or_strtype_or_array_of_strtype_False_False_1_None,
-                    baseuri,
-                    loadingOptions,
-                    lc=_doc.get("extends")
-                )
-
-            except ValidationException as e:
-                error_message, to_print, verb_tensage = parse_errors(str(e))
-
-                if str(e) == "missing required field `extends`":
-                    _errors__.append(
-                        ValidationException(
-                            str(e),
-                            None
-                        )
-                    )
-                else:
-                    if error_message != str(e):
-                        val_type = convert_typing(extract_type(type(_doc.get("extends"))))
-                        _errors__.append(
-                            ValidationException(
-                                "the `extends` field is not valid because:",
-                                SourceLine(_doc, "extends", str),
-                                [ValidationException(f"Value is a {val_type}, "
-                                                     f"but valid {to_print} for this field "
-                                                     f"{verb_tensage} {error_message}")],
-                            )
-                        )
-                    else:
-                        _errors__.append(
-                            ValidationException(
-                                "the `extends` field is not valid because:",
-                                SourceLine(_doc, "extends", str),
-                                [e],
-                            )
-                        )
-        else:
-            extends = None
         extension_fields: Dict[str, Any] = {}
         for k in _doc.keys():
             if k not in cls.attrs:
@@ -4648,7 +6273,7 @@ def fromDoc(
                 else:
                     _errors__.append(
                         ValidationException(
-                            "invalid field `{}`, expected one of: `name`, `inVocab`, `symbols`, `type`, `doc`, `docParent`, `docChild`, `docAfter`, `jsonldPredicate`, `documentRoot`, `extends`".format(
+                            "invalid field `{}`, expected one of: `name`, `inVocab`, `names`, `type`, `doc`, `docParent`, `docChild`, `docAfter`, `documentRoot`".format(
                                 k
                             ),
                             SourceLine(_doc, k, str),
@@ -4660,15 +6285,13 @@ def fromDoc(
         _constructed = cls(
             name=name,
             inVocab=inVocab,
-            symbols=symbols,
+            names=names,
             type_=type_,
             doc=doc,
             docParent=docParent,
             docChild=docChild,
             docAfter=docAfter,
-            jsonldPredicate=jsonldPredicate,
             documentRoot=documentRoot,
-            extends=extends,
             extension_fields=extension_fields,
             loadingOptions=loadingOptions,
         )
@@ -4693,9 +6316,9 @@ def save(
             r["inVocab"] = save(
                 self.inVocab, top=False, base_url=self.name, relative_uris=relative_uris
             )
-        if self.symbols is not None:
-            u = save_relative_uri(self.symbols, self.name, True, None, relative_uris)
-            r["symbols"] = u
+        if self.names is not None:
+            u = save_relative_uri(self.names, self.name, False, 2, relative_uris)
+            r["names"] = u
         if self.type_ is not None:
             r["type"] = save(
                 self.type_, top=False, base_url=self.name, relative_uris=relative_uris
@@ -4713,13 +6336,6 @@ def save(
         if self.docAfter is not None:
             u = save_relative_uri(self.docAfter, self.name, False, None, relative_uris)
             r["docAfter"] = u
-        if self.jsonldPredicate is not None:
-            r["jsonldPredicate"] = save(
-                self.jsonldPredicate,
-                top=False,
-                base_url=self.name,
-                relative_uris=relative_uris,
-            )
         if self.documentRoot is not None:
             r["documentRoot"] = save(
                 self.documentRoot,
@@ -4727,9 +6343,6 @@ def save(
                 base_url=self.name,
                 relative_uris=relative_uris,
             )
-        if self.extends is not None:
-            u = save_relative_uri(self.extends, self.name, False, 1, relative_uris)
-            r["extends"] = u
 
         # top refers to the directory level
         if top:
@@ -4743,15 +6356,13 @@ def save(
         [
             "name",
             "inVocab",
-            "symbols",
+            "names",
             "type",
             "doc",
             "docParent",
             "docChild",
             "docAfter",
-            "jsonldPredicate",
             "documentRoot",
-            "extends",
         ]
     )
 
@@ -4833,9 +6444,6 @@ def fromDoc(
         _errors__ = []
         if "name" in _doc:
             try:
-                if _doc.get("name") is None:
-                    raise ValidationException("missing required field `name`", None, [])
-
                 name = load_field(
                     _doc.get("name"),
                     uri_strtype_True_False_None_None,
@@ -4887,9 +6495,6 @@ def fromDoc(
             baseuri = name
         if "inVocab" in _doc:
             try:
-                if _doc.get("inVocab") is None:
-                    raise ValidationException("missing required field `inVocab`", None, [])
-
                 inVocab = load_field(
                     _doc.get("inVocab"),
                     union_of_None_type_or_booltype,
@@ -4932,9 +6537,6 @@ def fromDoc(
             inVocab = None
         if "doc" in _doc:
             try:
-                if _doc.get("doc") is None:
-                    raise ValidationException("missing required field `doc`", None, [])
-
                 doc = load_field(
                     _doc.get("doc"),
                     union_of_None_type_or_strtype_or_array_of_strtype,
@@ -4977,9 +6579,6 @@ def fromDoc(
             doc = None
         if "docParent" in _doc:
             try:
-                if _doc.get("docParent") is None:
-                    raise ValidationException("missing required field `docParent`", None, [])
-
                 docParent = load_field(
                     _doc.get("docParent"),
                     uri_union_of_None_type_or_strtype_False_False_None_None,
@@ -5022,9 +6621,6 @@ def fromDoc(
             docParent = None
         if "docChild" in _doc:
             try:
-                if _doc.get("docChild") is None:
-                    raise ValidationException("missing required field `docChild`", None, [])
-
                 docChild = load_field(
                     _doc.get("docChild"),
                     uri_union_of_None_type_or_strtype_or_array_of_strtype_False_False_None_None,
@@ -5067,9 +6663,6 @@ def fromDoc(
             docChild = None
         if "docAfter" in _doc:
             try:
-                if _doc.get("docAfter") is None:
-                    raise ValidationException("missing required field `docAfter`", None, [])
-
                 docAfter = load_field(
                     _doc.get("docAfter"),
                     uri_union_of_None_type_or_strtype_False_False_None_None,
@@ -5247,15 +6840,19 @@ def save(
     "Documented": "https://w3id.org/cwl/salad#Documented",
     "EnumSchema": "https://w3id.org/cwl/salad#EnumSchema",
     "JsonldPredicate": "https://w3id.org/cwl/salad#JsonldPredicate",
+    "MapSchema": "https://w3id.org/cwl/salad#MapSchema",
     "NamedType": "https://w3id.org/cwl/salad#NamedType",
     "PrimitiveType": "https://w3id.org/cwl/salad#PrimitiveType",
     "RecordField": "https://w3id.org/cwl/salad#RecordField",
     "RecordSchema": "https://w3id.org/cwl/salad#RecordSchema",
     "SaladEnumSchema": "https://w3id.org/cwl/salad#SaladEnumSchema",
+    "SaladMapSchema": "https://w3id.org/cwl/salad#SaladMapSchema",
     "SaladRecordField": "https://w3id.org/cwl/salad#SaladRecordField",
     "SaladRecordSchema": "https://w3id.org/cwl/salad#SaladRecordSchema",
+    "SaladUnionSchema": "https://w3id.org/cwl/salad#SaladUnionSchema",
     "SchemaDefinedType": "https://w3id.org/cwl/salad#SchemaDefinedType",
     "SpecializeDef": "https://w3id.org/cwl/salad#SpecializeDef",
+    "UnionSchema": "https://w3id.org/cwl/salad#UnionSchema",
     "array": "https://w3id.org/cwl/salad#array",
     "boolean": "http://www.w3.org/2001/XMLSchema#boolean",
     "documentation": "https://w3id.org/cwl/salad#documentation",
@@ -5264,9 +6861,11 @@ def save(
     "float": "http://www.w3.org/2001/XMLSchema#float",
     "int": "http://www.w3.org/2001/XMLSchema#int",
     "long": "http://www.w3.org/2001/XMLSchema#long",
+    "map": "https://w3id.org/cwl/salad#map",
     "null": "https://w3id.org/cwl/salad#null",
     "record": "https://w3id.org/cwl/salad#record",
     "string": "http://www.w3.org/2001/XMLSchema#string",
+    "union": "https://w3id.org/cwl/salad#union",
 }
 _rvocab = {
     "https://w3id.org/cwl/salad#Any": "Any",
@@ -5276,15 +6875,19 @@ def save(
     "https://w3id.org/cwl/salad#Documented": "Documented",
     "https://w3id.org/cwl/salad#EnumSchema": "EnumSchema",
     "https://w3id.org/cwl/salad#JsonldPredicate": "JsonldPredicate",
+    "https://w3id.org/cwl/salad#MapSchema": "MapSchema",
     "https://w3id.org/cwl/salad#NamedType": "NamedType",
     "https://w3id.org/cwl/salad#PrimitiveType": "PrimitiveType",
     "https://w3id.org/cwl/salad#RecordField": "RecordField",
     "https://w3id.org/cwl/salad#RecordSchema": "RecordSchema",
     "https://w3id.org/cwl/salad#SaladEnumSchema": "SaladEnumSchema",
+    "https://w3id.org/cwl/salad#SaladMapSchema": "SaladMapSchema",
     "https://w3id.org/cwl/salad#SaladRecordField": "SaladRecordField",
     "https://w3id.org/cwl/salad#SaladRecordSchema": "SaladRecordSchema",
+    "https://w3id.org/cwl/salad#SaladUnionSchema": "SaladUnionSchema",
     "https://w3id.org/cwl/salad#SchemaDefinedType": "SchemaDefinedType",
     "https://w3id.org/cwl/salad#SpecializeDef": "SpecializeDef",
+    "https://w3id.org/cwl/salad#UnionSchema": "UnionSchema",
     "https://w3id.org/cwl/salad#array": "array",
     "http://www.w3.org/2001/XMLSchema#boolean": "boolean",
     "https://w3id.org/cwl/salad#documentation": "documentation",
@@ -5293,9 +6896,11 @@ def save(
     "http://www.w3.org/2001/XMLSchema#float": "float",
     "http://www.w3.org/2001/XMLSchema#int": "int",
     "http://www.w3.org/2001/XMLSchema#long": "long",
+    "https://w3id.org/cwl/salad#map": "map",
     "https://w3id.org/cwl/salad#null": "null",
     "https://w3id.org/cwl/salad#record": "record",
     "http://www.w3.org/2001/XMLSchema#string": "string",
+    "https://w3id.org/cwl/salad#union": "union",
 }
 
 strtype = _PrimitiveLoader(str)
@@ -5334,16 +6939,20 @@ def save(
 """
 The **Any** type validates for any non-null value.
 """
-RecordFieldLoader = _RecordLoader(RecordField)
-RecordSchemaLoader = _RecordLoader(RecordSchema)
-EnumSchemaLoader = _RecordLoader(EnumSchema)
-ArraySchemaLoader = _RecordLoader(ArraySchema)
-JsonldPredicateLoader = _RecordLoader(JsonldPredicate)
-SpecializeDefLoader = _RecordLoader(SpecializeDef)
-SaladRecordFieldLoader = _RecordLoader(SaladRecordField)
-SaladRecordSchemaLoader = _RecordLoader(SaladRecordSchema)
-SaladEnumSchemaLoader = _RecordLoader(SaladEnumSchema)
-DocumentationLoader = _RecordLoader(Documentation)
+RecordFieldLoader = _RecordLoader(RecordField, None, None)
+RecordSchemaLoader = _RecordLoader(RecordSchema, None, None)
+EnumSchemaLoader = _RecordLoader(EnumSchema, None, None)
+ArraySchemaLoader = _RecordLoader(ArraySchema, None, None)
+MapSchemaLoader = _RecordLoader(MapSchema, None, None)
+UnionSchemaLoader = _RecordLoader(UnionSchema, None, None)
+JsonldPredicateLoader = _RecordLoader(JsonldPredicate, None, None)
+SpecializeDefLoader = _RecordLoader(SpecializeDef, None, None)
+SaladRecordFieldLoader = _RecordLoader(SaladRecordField, None, None)
+SaladRecordSchemaLoader = _RecordLoader(SaladRecordSchema, None, None)
+SaladEnumSchemaLoader = _RecordLoader(SaladEnumSchema, None, None)
+SaladMapSchemaLoader = _RecordLoader(SaladMapSchema, None, None)
+SaladUnionSchemaLoader = _RecordLoader(SaladUnionSchema, None, None)
+DocumentationLoader = _RecordLoader(Documentation, None, None)
 array_of_strtype = _ArrayLoader(strtype)
 union_of_None_type_or_strtype_or_array_of_strtype = _UnionLoader(
     (
@@ -5353,30 +6962,34 @@ def save(
     )
 )
 uri_strtype_True_False_None_None = _URILoader(strtype, True, False, None, None)
-union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype = _UnionLoader(
+union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype = _UnionLoader(
     (
         PrimitiveTypeLoader,
         RecordSchemaLoader,
         EnumSchemaLoader,
         ArraySchemaLoader,
+        MapSchemaLoader,
+        UnionSchemaLoader,
         strtype,
     )
 )
-array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype = _ArrayLoader(
-    union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype
+array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype = _ArrayLoader(
+    union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype
 )
-union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype = _UnionLoader(
+union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype = _UnionLoader(
     (
         PrimitiveTypeLoader,
         RecordSchemaLoader,
         EnumSchemaLoader,
         ArraySchemaLoader,
+        MapSchemaLoader,
+        UnionSchemaLoader,
         strtype,
-        array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype,
+        array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype,
     )
 )
-typedsl_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_2 = _TypeDSLLoader(
-    union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype,
+typedsl_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_2 = _TypeDSLLoader(
+    union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype,
     2,
     "v1.1",
 )
@@ -5406,8 +7019,8 @@ def save(
 )
 Enum_nameLoader = _EnumLoader(("enum",), "Enum_name")
 typedsl_Enum_nameLoader_2 = _TypeDSLLoader(Enum_nameLoader, 2, "v1.1")
-uri_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_False_True_2_None = _URILoader(
-    union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_strtype,
+uri_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_False_True_2_None = _URILoader(
+    union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype_or_array_of_union_of_PrimitiveTypeLoader_or_RecordSchemaLoader_or_EnumSchemaLoader_or_ArraySchemaLoader_or_MapSchemaLoader_or_UnionSchemaLoader_or_strtype,
     False,
     True,
     2,
@@ -5415,6 +7028,10 @@ def save(
 )
 Array_nameLoader = _EnumLoader(("array",), "Array_name")
 typedsl_Array_nameLoader_2 = _TypeDSLLoader(Array_nameLoader, 2, "v1.1")
+Map_nameLoader = _EnumLoader(("map",), "Map_name")
+typedsl_Map_nameLoader_2 = _TypeDSLLoader(Map_nameLoader, 2, "v1.1")
+Union_nameLoader = _EnumLoader(("union",), "Union_name")
+typedsl_Union_nameLoader_2 = _TypeDSLLoader(Union_nameLoader, 2, "v1.1")
 union_of_None_type_or_booltype = _UnionLoader(
     (
         None_type,
@@ -5474,24 +7091,26 @@ def save(
 )
 Documentation_nameLoader = _EnumLoader(("documentation",), "Documentation_name")
 typedsl_Documentation_nameLoader_2 = _TypeDSLLoader(Documentation_nameLoader, 2, "v1.1")
-union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader = (
-    _UnionLoader(
-        (
-            SaladRecordSchemaLoader,
-            SaladEnumSchemaLoader,
-            DocumentationLoader,
-        )
+union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader = _UnionLoader(
+    (
+        SaladRecordSchemaLoader,
+        SaladEnumSchemaLoader,
+        SaladMapSchemaLoader,
+        SaladUnionSchemaLoader,
+        DocumentationLoader,
     )
 )
-array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader = _ArrayLoader(
-    union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader
+array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader = _ArrayLoader(
+    union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader
 )
-union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader_or_array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader = _UnionLoader(
+union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader_or_array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader = _UnionLoader(
     (
         SaladRecordSchemaLoader,
         SaladEnumSchemaLoader,
+        SaladMapSchemaLoader,
+        SaladUnionSchemaLoader,
         DocumentationLoader,
-        array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader,
+        array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader,
     )
 )
 
@@ -5506,7 +7125,7 @@ def load_document(
     if loadingOptions is None:
         loadingOptions = LoadingOptions()
     result, metadata = _document_load(
-        union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader_or_array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader,
+        union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader_or_array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader,
         doc,
         baseuri,
         loadingOptions,
@@ -5525,7 +7144,7 @@ def load_document_with_metadata(
     if loadingOptions is None:
         loadingOptions = LoadingOptions(fileuri=baseuri)
     return _document_load(
-        union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader_or_array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader,
+        union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader_or_array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader,
         doc,
         baseuri,
         loadingOptions,
@@ -5546,7 +7165,7 @@ def load_document_by_string(
         loadingOptions = LoadingOptions(fileuri=uri)
 
     result, metadata = _document_load(
-        union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader_or_array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader,
+        union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader_or_array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader,
         result,
         uri,
         loadingOptions,
@@ -5569,7 +7188,7 @@ def load_document_by_yaml(
         loadingOptions = LoadingOptions(fileuri=uri)
 
     result, metadata = _document_load(
-        union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader_or_array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_DocumentationLoader,
+        union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader_or_array_of_union_of_SaladRecordSchemaLoader_or_SaladEnumSchemaLoader_or_SaladMapSchemaLoader_or_SaladUnionSchemaLoader_or_DocumentationLoader,
         yaml,
         uri,
         loadingOptions,
diff --git a/schema_salad/metaschema/metaschema.yml b/schema_salad/metaschema/metaschema.yml
index de0b09315..f696e0aed 100644
--- a/schema_salad/metaschema/metaschema.yml
+++ b/schema_salad/metaschema/metaschema.yml
@@ -274,6 +274,7 @@ $graph:
       doc: |
         If true, indicates that the type is a valid at the document root.  At
         least one type in a schema must be tagged with `documentRoot: true`.
+      jsonldPredicate: sld:documentRoot
 
 
 - name: SaladRecordField
@@ -356,6 +357,31 @@ $graph:
         Indicates that this enum inherits symbols from a base enum.
 
 
+- name: SaladMapSchema
+  docParent: "#Schema"
+  type: record
+  extends: [NamedType, MapSchema, SchemaDefinedType]
+  documentRoot: true
+  doc: |
+    Define a map type.
+
+
+- name: SaladUnionSchema
+  docParent: "#Schema"
+  type: record
+  extends: [NamedType, UnionSchema, DocType]
+  documentRoot: true
+  doc: |
+    Define a union type.
+  fields:
+    - name: documentRoot
+      type: boolean?
+      doc: |
+        If true, indicates that the type is a valid at the document root.  At
+        least one type in a schema must be tagged with `documentRoot: true`.
+      jsonldPredicate: sld:documentRoot
+
+
 - name: Documentation
   type: record
   docParent: "#Schema"
diff --git a/schema_salad/metaschema/metaschema_base.yml b/schema_salad/metaschema/metaschema_base.yml
index 96ae8d8cd..3bdf63903 100644
--- a/schema_salad/metaschema/metaschema_base.yml
+++ b/schema_salad/metaschema/metaschema_base.yml
@@ -77,6 +77,8 @@ $graph:
         - RecordSchema
         - EnumSchema
         - ArraySchema
+        - MapSchema
+        - UnionSchema
         - string
         - type: array
           items:
@@ -84,6 +86,8 @@ $graph:
             - RecordSchema
             - EnumSchema
             - ArraySchema
+            - MapSchema
+            - UnionSchema
             - string
       jsonldPredicate:
         _id: sld:type
@@ -170,6 +174,8 @@ $graph:
         - RecordSchema
         - EnumSchema
         - ArraySchema
+        - MapSchema
+        - UnionSchema
         - string
         - type: array
           items:
@@ -177,9 +183,91 @@ $graph:
             - RecordSchema
             - EnumSchema
             - ArraySchema
+            - MapSchema
+            - UnionSchema
             - string
       jsonldPredicate:
         _id: "sld:items"
         _type: "@vocab"
         refScope: 2
       doc: "Defines the type of the array elements."
+
+
+- name: MapSchema
+  type: record
+  fields:
+    type:
+      doc: "Must be `map`"
+      type:
+        type: enum
+        name: Map_name
+        symbols:
+          - "sld:map"
+      jsonldPredicate:
+        _id: "sld:type"
+        _type: "@vocab"
+        typeDSL: true
+        refScope: 2
+    values:
+      type:
+        - PrimitiveType
+        - RecordSchema
+        - EnumSchema
+        - ArraySchema
+        - MapSchema
+        - UnionSchema
+        - string
+        - type: array
+          items:
+            - PrimitiveType
+            - RecordSchema
+            - EnumSchema
+            - ArraySchema
+            - MapSchema
+            - UnionSchema
+            - string
+      jsonldPredicate:
+        _id: "sld:values"
+        _type: "@vocab"
+        refScope: 2
+      doc: "Defines the type of the map elements."
+
+
+- name: UnionSchema
+  type: record
+  fields:
+    type:
+      doc: "Must be `union`"
+      type:
+        type: enum
+        name: Union_name
+        symbols:
+          - "sld:union"
+      jsonldPredicate:
+        _id: "sld:type"
+        _type: "@vocab"
+        typeDSL: true
+        refScope: 2
+    names:
+      type:
+        - PrimitiveType
+        - RecordSchema
+        - EnumSchema
+        - ArraySchema
+        - MapSchema
+        - UnionSchema
+        - string
+        - type: array
+          items:
+            - PrimitiveType
+            - RecordSchema
+            - EnumSchema
+            - ArraySchema
+            - MapSchema
+            - UnionSchema
+            - string
+      jsonldPredicate:
+        _id: "sld:names"
+        _type: "@vocab"
+        refScope: 2
+      doc: "Defines the type of the union elements."
diff --git a/schema_salad/metaschema/salad.md b/schema_salad/metaschema/salad.md
index ad9daf3cd..7c53c9972 100644
--- a/schema_salad/metaschema/salad.md
+++ b/schema_salad/metaschema/salad.md
@@ -10,6 +10,7 @@ Contributors:
 * The developers of JSON-LD
 * Nebojša Tijanić <nebojsa.tijanic@sbgenomics.com>, Seven Bridges Genomics
 * Michael R. Crusoe, ELIXIR-DE
+* Iacopo Colonnelli, University of Torino
 
 # Abstract
 
@@ -94,6 +95,16 @@ ease the development of extensions to CWL v1.2. The only change is that
 inherited records can narrow the types of fields if those fields are re-specified
 with a matching jsonldPredicate.
 
+## Introduction to v1.3
+
+This is the fifth version of the Schema Salad specification. It was created to
+enhance code generation by representing CWL data types as specific Python objects
+(instead of  relying on the generic `Any` type). The following changes have been made:
+
+* Support for the Avro `map` schema
+* Add named versions of the `map` and `union` Avro types
+* Support for nested named `union` type definitions
+
 ## References to Other Specifications
 
 **Javascript Object Notation (JSON)**: http://json.org
diff --git a/schema_salad/python_codegen.py b/schema_salad/python_codegen.py
index 19cc69e7e..7e006dbdc 100644
--- a/schema_salad/python_codegen.py
+++ b/schema_salad/python_codegen.py
@@ -1,17 +1,7 @@
 """Python code generator for a given schema salad definition."""
 import textwrap
 from io import StringIO
-from typing import (
-    IO,
-    Any,
-    Dict,
-    List,
-    MutableMapping,
-    MutableSequence,
-    Optional,
-    Set,
-    Union,
-)
+from typing import Any, Dict, IO, List, MutableMapping, MutableSequence, Optional, Set, Union
 
 try:
     import black
@@ -21,7 +11,7 @@
 from importlib_resources import files
 
 from . import schema
-from .codegen_base import CodeGenBase, TypeDef
+from .codegen_base import CodeGenBase, LazyInitDef, TypeDef
 from .exceptions import SchemaException
 from .schema import shortname
 
@@ -365,9 +355,7 @@ def end_class(self, classname: str, field_names: List[str]) -> None:
             )
         )
 
-        safe_init_fields = [
-            self.safe_name(f) for f in field_names if f != "class"
-        ]  # type: List[str]
+        safe_init_fields: List[str] = [self.safe_name(f) for f in field_names if f != "class"]
 
         safe_inits = [f + "=" + f for f in safe_init_fields]
 
@@ -390,12 +378,16 @@ def end_class(self, classname: str, field_names: List[str]) -> None:
 
         self.out.write("\n\n")
 
-    def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -> TypeDef:
+    def type_loader(
+        self,
+        type_declaration: Union[List[Any], Dict[str, Any], str],
+        container: Optional[str] = None,
+        no_link_check: Optional[bool] = None,
+    ) -> TypeDef:
         """Parse the given type declaration and declare its components."""
+        sub_names: List[str]
         if isinstance(type_declaration, MutableSequence):
-            sub_names: List[str] = list(
-                dict.fromkeys([self.type_loader(i).name for i in type_declaration])
-            )
+            sub_names = list(dict.fromkeys([self.type_loader(i).name for i in type_declaration]))
             return self.declare_type(
                 TypeDef(
                     "union_of_{}".format("_or_".join(sub_names)),
@@ -408,7 +400,37 @@ def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -
                 "https://w3id.org/cwl/salad#array",
             ):
                 i = self.type_loader(type_declaration["items"])
-                return self.declare_type(TypeDef(f"array_of_{i.name}", f"_ArrayLoader({i.name})"))
+                return self.declare_type(
+                    TypeDef(
+                        f"array_of_{i.name}",
+                        f"_ArrayLoader({i.name})",
+                    )
+                )
+            if type_declaration["type"] in (
+                "map",
+                "https://w3id.org/cwl/salad#map",
+            ):
+                i = self.type_loader(type_declaration["values"])
+                name = (
+                    self.safe_name(type_declaration["name"]) if "name" in type_declaration else None
+                )
+                anon_type = self.declare_type(
+                    TypeDef(
+                        f"map_of_{i.name}",
+                        "_MapLoader({}, {}, {}, {})".format(
+                            i.name,
+                            f"'{name}'",
+                            f"'{container}'" if container is not None else None,
+                            no_link_check,
+                        ),
+                    )
+                )
+                if "name" in type_declaration:
+                    return self.declare_type(
+                        TypeDef(self.safe_name(type_declaration["name"]) + "Loader", anon_type.name)
+                    )
+                else:
+                    return anon_type
             if type_declaration["type"] in ("enum", "https://w3id.org/cwl/salad#enum"):
                 for sym in type_declaration["symbols"]:
                     self.add_vocab(shortname(sym), sym)
@@ -441,12 +463,35 @@ def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -
                 return self.declare_type(
                     TypeDef(
                         self.safe_name(type_declaration["name"]) + "Loader",
-                        "_RecordLoader({})".format(
+                        "_RecordLoader({}, {}, {})".format(
                             self.safe_name(type_declaration["name"]),
+                            f"'{container}'" if container is not None else None,
+                            no_link_check,
                         ),
                         abstract=type_declaration.get("abstract", False),
                     )
                 )
+
+            if type_declaration["type"] in (
+                "union",
+                "https://w3id.org/cwl/salad#union",
+            ):
+                # Declare the named loader to handle recursive union definitions
+                loader_name = self.safe_name(type_declaration["name"]) + "Loader"
+                loader_type = TypeDef(loader_name, "_UnionLoader((), '{}')".format(loader_name))
+                self.declare_type(loader_type)
+                # Parse inner types
+                sub_names = list(
+                    dict.fromkeys([self.type_loader(i).name for i in type_declaration["names"]])
+                )
+                # Register lazy initialization for the loader
+                self.add_lazy_init(
+                    LazyInitDef(
+                        loader_name,
+                        "{}.add_loaders(({},))".format(loader_name, ", ".join(sub_names)),
+                    )
+                )
+                return loader_type
             raise SchemaException("wft {}".format(type_declaration["type"]))
 
         if type_declaration in prims:
@@ -530,11 +575,23 @@ def declare_field(
             baseurivar = "baseuri"
 
         self.out.write(
-            """{spc}        try:
-{spc}            if _doc.get("{fieldname}") is None:
+            f"""{spc}        try:
+"""
+        )
+
+        if not optional:
+            self.out.write(
+                """{spc}            if _doc.get("{fieldname}") is None:
 {spc}                raise ValidationException("missing required field `{fieldname}`", None, [])
 
-{spc}            {safename} = load_field(
+""".format(
+                    fieldname=shortname(name),
+                    spc=spc,
+                )
+            )
+
+        self.out.write(
+            """{spc}            {safename} = load_field(
 {spc}                _doc.get("{fieldname}"),
 {spc}                {fieldtype},
 {spc}                {baseurivar},
@@ -640,7 +697,7 @@ def uri_loader(
         scoped_id: bool,
         vocab_term: bool,
         ref_scope: Optional[int],
-        no_link_check: Optional[bool],
+        no_link_check: Optional[bool] = None,
     ) -> TypeDef:
         """Construct the TypeDef for the given URI loader."""
         return self.declare_type(
@@ -679,12 +736,13 @@ def secondaryfilesdsl_loader(self, inner: TypeDef) -> TypeDef:
         return self.declare_type(
             TypeDef(
                 f"secondaryfilesdsl_{inner.name}",
-                f"_SecondaryDSLLoader({inner.name})",
+                f"_UnionLoader((_SecondaryDSLLoader({inner.name}), {inner.name},))",
             )
         )
 
     def epilogue(self, root_loader: TypeDef) -> None:
         """Trigger to generate the epilouge code."""
+
         self.out.write("_vocab = {\n")
         for k in sorted(self.vocab.keys()):
             self.out.write(f'    "{k}": "{self.vocab[k]}",\n')  # noqa: B907
@@ -700,6 +758,11 @@ def epilogue(self, root_loader: TypeDef) -> None:
                 self.out.write(fmt(f"{collected_type.name} = {collected_type.init}\n", 0))
         self.out.write("\n")
 
+        if self.lazy_inits:
+            for lazy_init in self.lazy_inits.values():
+                self.out.write(fmt(f"{lazy_init.init}\n", 0))
+            self.out.write("\n")
+
         self.out.write(
             """
 def load_document(
diff --git a/schema_salad/python_codegen_support.py b/schema_salad/python_codegen_support.py
index d086031f0..fc39288cc 100644
--- a/schema_salad/python_codegen_support.py
+++ b/schema_salad/python_codegen_support.py
@@ -8,6 +8,7 @@
 import xml.sax  # nosec
 from abc import ABC, abstractmethod
 from io import StringIO
+from itertools import chain
 from typing import (
     Any,
     Dict,
@@ -56,6 +57,8 @@ class LoadingOptions:
     cache: CacheType
     imports: List[str]
     includes: List[str]
+    no_link_check: Optional[bool]
+    container: Optional[str]
 
     def __init__(
         self,
@@ -70,7 +73,8 @@ def __init__(
         idx: Optional[IdxType] = None,
         imports: Optional[List[str]] = None,
         includes: Optional[List[str]] = None,
-        no_link_check: bool = False,
+        no_link_check: Optional[bool] = None,
+        container: Optional[str] = None,
     ) -> None:
         """Create a LoadingOptions object."""
         self.original_doc = original_doc
@@ -115,7 +119,15 @@ def __init__(
         else:
             self.includes = copyfrom.includes if copyfrom is not None else []
 
-        self.no_link_check = no_link_check
+        if no_link_check is not None:
+            self.no_link_check = no_link_check
+        else:
+            self.no_link_check = copyfrom.no_link_check if copyfrom is not None else False
+
+        if container is not None:
+            self.container = container
+        else:
+            self.container = copyfrom.container if copyfrom is not None else None
 
         if fetcher is not None:
             self.fetcher = fetcher
@@ -138,10 +150,10 @@ def __init__(
         self.vocab = _vocab
         self.rvocab = _rvocab
 
-        if namespaces is not None:
+        if self.namespaces is not None:
             self.vocab = self.vocab.copy()
             self.rvocab = self.rvocab.copy()
-            for k, v in namespaces.items():
+            for k, v in self.namespaces.items():
                 self.vocab[k] = v
                 self.rvocab[v] = k
 
@@ -204,8 +216,13 @@ def save(
         """Convert this object to a JSON/YAML friendly dictionary."""
 
 
-def load_field(val, fieldtype, baseuri, loadingOptions, lc=None):
-    # type: (Union[str, Dict[str, str]], _Loader, str, LoadingOptions, Optional[List[Any]]) -> Any
+def load_field(
+    val: Union[str, Dict[str, str]],
+    fieldtype: "_Loader",
+    baseuri: str,
+    loadingOptions: LoadingOptions,
+    lc: Optional[List[Any]] = None,
+) -> Any:
     """Load field."""
     if isinstance(val, MutableMapping):
         if "$import" in val:
@@ -337,14 +354,13 @@ def save_with_metadata(
 
 
 def expand_url(
-    url,  # type: str
-    base_url,  # type: str
-    loadingOptions,  # type: LoadingOptions
-    scoped_id=False,  # type: bool
-    vocab_term=False,  # type: bool
-    scoped_ref=None,  # type: Optional[int]
-):
-    # type: (...) -> str
+    url: str,
+    base_url: str,
+    loadingOptions: LoadingOptions,
+    scoped_id: bool = False,
+    vocab_term: bool = False,
+    scoped_ref: Optional[int] = None,
+) -> str:
     if url in ("@id", "@type"):
         return url
 
@@ -405,58 +421,80 @@ def expand_url(
 
 
 class _Loader:
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         pass
 
 
 class _AnyLoader(_Loader):
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if doc is not None:
             return doc
         raise ValidationException("Expected non-null")
 
 
 class _PrimitiveLoader(_Loader):
-    def __init__(self, tp):
-        # type: (Union[type, Tuple[Type[str], Type[str]]]) -> None
+    def __init__(self, tp: Union[type, Tuple[Type[str], Type[str]]]) -> None:
         self.tp = tp
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if not isinstance(doc, self.tp):
             raise ValidationException(
                 "Expected a {} but got {}".format(self.tp, doc.__class__.__name__)
             )
         return doc
 
-    def __repr__(self):  # type: () -> str
+    def __repr__(self) -> str:
         return str(self.tp)
 
 
 class _ArrayLoader(_Loader):
-    def __init__(self, items):
-        # type: (_Loader) -> None
+    def __init__(self, items: _Loader) -> None:
         self.items = items
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if not isinstance(doc, MutableSequence):
             raise ValidationException(
                 f"Value is a {convert_typing(extract_type(type(doc)))}, "
                 f"but valid type for this field is an array."
             )
-        r = []  # type: List[Any]
-        errors = []  # type: List[SchemaSaladException]
-        fields = []  # type: List[str]
-
+        r: List[Any] = []
+        errors: List[SchemaSaladException] = []
+        fields: List[str] = []
         for i in range(0, len(doc)):
             try:
                 lf = load_field(
                     doc[i], _UnionLoader(([self, self.items])), baseuri, loadingOptions, lc=lc
                 )
-                if isinstance(lf, MutableSequence):
+                flatten = loadingOptions.container != "@list"
+                if flatten and isinstance(lf, MutableSequence):
                     r.extend(lf)
                 else:
                     r.append(lf)
@@ -483,32 +521,86 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
             raise ValidationException("", None, errors)
         return r
 
-    def __repr__(self):  # type: () -> str
+    def __repr__(self) -> str:
         return f"array<{self.items}>"
 
 
+class _MapLoader(_Loader):
+    def __init__(
+        self,
+        values: _Loader,
+        name: Optional[str] = None,
+        container: Optional[str] = None,
+        no_link_check: Optional[bool] = None,
+    ) -> None:
+        self.values = values
+        self.name = name
+        self.container = container
+        self.no_link_check = no_link_check
+
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
+        if not isinstance(doc, MutableMapping):
+            raise ValidationException(f"Expected a map, was {type(doc)}")
+        if self.container is not None or self.no_link_check is not None:
+            loadingOptions = LoadingOptions(
+                copyfrom=loadingOptions, container=self.container, no_link_check=self.no_link_check
+            )
+        r: Dict[str, Any] = {}
+        errors: List[SchemaSaladException] = []
+        for k, v in doc.items():
+            try:
+                lf = load_field(v, self.values, baseuri, loadingOptions, lc)
+                r[k] = lf
+            except ValidationException as e:
+                errors.append(e.with_sourceline(SourceLine(doc, k, str)))
+        if errors:
+            raise ValidationException("", None, errors)
+        return r
+
+    def __repr__(self) -> str:
+        return self.name if self.name is not None else f"map<string, {self.values}>"
+
+
 class _EnumLoader(_Loader):
     def __init__(self, symbols: Sequence[str], name: str) -> None:
         self.symbols = symbols
         self.name = name
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if doc in self.symbols:
             return doc
         raise ValidationException(f"Expected one of {self.symbols}")
 
-    def __repr__(self):  # type: () -> str
+    def __repr__(self) -> str:
         return self.name
 
 
 class _SecondaryDSLLoader(_Loader):
-    def __init__(self, inner):
-        # type: (_Loader) -> None
+    def __init__(self, inner: _Loader) -> None:
         self.inner = inner
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         r: List[Dict[str, Any]] = []
         if isinstance(doc, MutableSequence):
             for d in doc:
@@ -570,20 +662,36 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
 
 
 class _RecordLoader(_Loader):
-    def __init__(self, classtype):
-        # type: (Type[Saveable]) -> None
+    def __init__(
+        self,
+        classtype: Type[Saveable],
+        container: Optional[str] = None,
+        no_link_check: Optional[bool] = None,
+    ) -> None:
         self.classtype = classtype
+        self.container = container
+        self.no_link_check = no_link_check
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if not isinstance(doc, MutableMapping):
             raise ValidationException(
                 f"Value is a {convert_typing(extract_type(type(doc)))}, "
                 f"but valid type for this field is an object."
             )
+        if self.container is not None or self.no_link_check is not None:
+            loadingOptions = LoadingOptions(
+                copyfrom=loadingOptions, container=self.container, no_link_check=self.no_link_check
+            )
         return self.classtype.fromDoc(doc, baseuri, loadingOptions, docRoot=docRoot)
 
-    def __repr__(self):  # type: () -> str
+    def __repr__(self) -> str:
         return str(self.classtype.__name__)
 
 
@@ -591,8 +699,14 @@ class _ExpressionLoader(_Loader):
     def __init__(self, items: Type[str]) -> None:
         self.items = items
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if not isinstance(doc, str):
             raise ValidationException(
                 f"Value is a {convert_typing(extract_type(type(doc)))}, "
@@ -602,11 +716,21 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
 
 
 class _UnionLoader(_Loader):
-    def __init__(self, alternates: Sequence[_Loader]) -> None:
+    def __init__(self, alternates: Sequence[_Loader], name: Optional[str] = None) -> None:
         self.alternates = alternates
+        self.name = name
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def add_loaders(self, loaders: Sequence[_Loader]) -> None:
+        self.alternates = tuple(loader for loader in chain(self.alternates, loaders))
+
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         errors = []
 
         if lc is None:
@@ -677,21 +801,33 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
                 )
         raise ValidationException("", None, errors, "*")
 
-    def __repr__(self):  # type: () -> str
-        return " | ".join(str(a) for a in self.alternates)
+    def __repr__(self) -> str:
+        return self.name if self.name is not None else " | ".join(str(a) for a in self.alternates)
 
 
 class _URILoader(_Loader):
-    def __init__(self, inner, scoped_id, vocab_term, scoped_ref, no_link_check):
-        # type: (_Loader, bool, bool, Union[int, None], Union[bool, None]) -> None
+    def __init__(
+        self,
+        inner: _Loader,
+        scoped_id: bool,
+        vocab_term: bool,
+        scoped_ref: Optional[int],
+        no_link_check: Optional[bool],
+    ) -> None:
         self.inner = inner
         self.scoped_id = scoped_id
         self.vocab_term = vocab_term
         self.scoped_ref = scoped_ref
         self.no_link_check = no_link_check
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if self.no_link_check is not None:
             loadingOptions = LoadingOptions(
                 copyfrom=loadingOptions, no_link_check=self.no_link_check
@@ -738,19 +874,17 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
 
 
 class _TypeDSLLoader(_Loader):
-    def __init__(self, inner, refScope, salad_version):
-        # type: (_Loader, Union[int, None], str) -> None
+    def __init__(self, inner: _Loader, refScope: Optional[int], salad_version: str) -> None:
         self.inner = inner
         self.refScope = refScope
         self.salad_version = salad_version
 
     def resolve(
         self,
-        doc,  # type: str
-        baseuri,  # type: str
-        loadingOptions,  # type: LoadingOptions
-    ):
-        # type: (...) -> Union[List[Union[Dict[str, Any], str]], Dict[str, Any], str]
+        doc: str,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+    ) -> Union[List[Union[Dict[str, Any], str]], Dict[str, Any], str]:
         doc_ = doc
         optional = False
         if doc_.endswith("?"):
@@ -759,7 +893,7 @@ def resolve(
 
         if doc_.endswith("[]"):
             salad_versions = [int(v) for v in self.salad_version[1:].split(".")]
-            items = ""  # type: Union[List[Union[Dict[str, Any], str]], Dict[str, Any], str]
+            items: Union[List[Union[Dict[str, Any], str]], Dict[str, Any], str] = ""
             rest = doc_[0:-2]
             if salad_versions < [1, 3]:
                 if rest.endswith("[]"):
@@ -771,7 +905,7 @@ def resolve(
                 items = self.resolve(rest, baseuri, loadingOptions)
                 if isinstance(items, str):
                     items = expand_url(items, baseuri, loadingOptions, False, True, self.refScope)
-            expanded = {"type": "array", "items": items}  # type: Union[Dict[str, Any], str]
+            expanded: Union[Dict[str, Any], str] = {"type": "array", "items": items}
         else:
             expanded = expand_url(doc_, baseuri, loadingOptions, False, True, self.refScope)
 
@@ -780,10 +914,16 @@ def resolve(
         else:
             return expanded
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if isinstance(doc, MutableSequence):
-            r = []  # type: List[Any]
+            r: List[Any] = []
             for d in doc:
                 if isinstance(d, str):
                     resolved = self.resolve(d, baseuri, loadingOptions)
@@ -804,16 +944,21 @@ def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
 
 
 class _IdMapLoader(_Loader):
-    def __init__(self, inner, mapSubject, mapPredicate):
-        # type: (_Loader, str, Union[str, None]) -> None
+    def __init__(self, inner: _Loader, mapSubject: str, mapPredicate: Optional[str]) -> None:
         self.inner = inner
         self.mapSubject = mapSubject
         self.mapPredicate = mapPredicate
 
-    def load(self, doc, baseuri, loadingOptions, docRoot=None, lc=None):
-        # type: (Any, str, LoadingOptions, Optional[str], Optional[List[Any]]) -> Any
+    def load(
+        self,
+        doc: Any,
+        baseuri: str,
+        loadingOptions: LoadingOptions,
+        docRoot: Optional[str] = None,
+        lc: Optional[List[Any]] = None,
+    ) -> Any:
         if isinstance(doc, MutableMapping):
-            r = []  # type: List[Any]
+            r: List[Any] = []
             for k in sorted(doc.keys()):
                 val = doc[k]
                 if isinstance(val, CommentedMap):
@@ -938,7 +1083,8 @@ def _document_load_by_url(
     return loadingOptions.idx[url]
 
 
-def file_uri(path, split_frag=False):  # type: (str, bool) -> str
+def file_uri(path: str, split_frag: bool = False) -> str:
+    """Transform a file path into a URL with file scheme."""
     if path.startswith("file://"):
         return path
     if split_frag:
diff --git a/schema_salad/schema.py b/schema_salad/schema.py
index 5f1c79219..db0d8e18b 100644
--- a/schema_salad/schema.py
+++ b/schema_salad/schema.py
@@ -102,6 +102,8 @@ def get_metaschema() -> Tuple[Names, List[Dict[str, str]], Loader]:
             "EnumSchema": saladp + "EnumSchema",
             "Enum_symbol": saladp + "EnumSchema/type/Enum_symbol",
             "JsonldPredicate": saladp + "JsonldPredicate",
+            "MapSchema": saladp + "MapSchema",
+            "Map_symbol": saladp + "MapSchema/type/Map_symbol",
             "NamedType": saladp + "NamedType",
             "PrimitiveType": saladp + "PrimitiveType",
             "RecordField": saladp + "RecordField",
@@ -112,6 +114,8 @@ def get_metaschema() -> Tuple[Names, List[Dict[str, str]], Loader]:
             "SaladRecordSchema": saladp + "SaladRecordSchema",
             "SchemaDefinedType": saladp + "SchemaDefinedType",
             "SpecializeDef": saladp + "SpecializeDef",
+            "UnionSchema": saladp + "UnionSchema",
+            "Union_symbol": saladp + "UnionSchema/type/Union_symbol",
             "_container": saladp + "JsonldPredicate/_container",
             "_id": {"@id": saladp + "_id", "@type": "@id", "identity": True},
             "_type": saladp + "JsonldPredicate/_type",
@@ -141,9 +145,11 @@ def get_metaschema() -> Tuple[Names, List[Dict[str, str]], Loader]:
             "items": {"@id": saladp + "items", "@type": "@vocab", "refScope": 2},
             "jsonldPredicate": "sld:jsonldPredicate",
             "long": "http://www.w3.org/2001/XMLSchema#long",
+            "map": saladp + "map",
             "mapPredicate": saladp + "JsonldPredicate/mapPredicate",
             "mapSubject": saladp + "JsonldPredicate/mapSubject",
             "name": "@id",
+            "names": {"@id": saladp + "names", "@type": "@vocab", "refScope": 2},
             "noLinkCheck": saladp + "JsonldPredicate/noLinkCheck",
             "null": saladp + "null",
             "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
@@ -176,6 +182,8 @@ def get_metaschema() -> Tuple[Names, List[Dict[str, str]], Loader]:
                 "typeDSL": True,
             },
             "typeDSL": saladp + "JsonldPredicate/typeDSL",
+            "union": saladp + "union",
+            "values": {"@id": saladp + "values", "@type": "@vocab", "refScope": 2},
             "xsd": "http://www.w3.org/2001/XMLSchema#",
         },
         salad_version="v1.3",
@@ -223,7 +231,7 @@ def add_namespaces(metadata: Mapping[str, Any], namespaces: MutableMapping[str,
 
 def collect_namespaces(metadata: Mapping[str, Any]) -> Dict[str, str]:
     """Walk through the metadata object, collecting namespace declarations."""
-    namespaces = {}  # type: Dict[str, str]
+    namespaces: Dict[str, str] = {}
     if "$import_metadata" in metadata:
         for value in metadata["$import_metadata"].values():
             add_namespaces(collect_namespaces(value), namespaces)
@@ -447,7 +455,7 @@ def replace_type(
     """Go through and replace types in the 'spec' mapping."""
     if isinstance(items, MutableMapping):
         # recursively check these fields for types to replace
-        if items.get("type") in ("record", "enum") and items.get("name"):
+        if items.get("type") in ("record", "enum", "map", "union") and items.get("name"):
             if items["name"] in found:
                 return items["name"]
             found.add(items["name"])
@@ -458,7 +466,7 @@ def replace_type(
         items = copy.copy(items)
         if not items.get("name"):
             items["name"] = get_anon_name(items)
-        for name in ("type", "items", "fields"):
+        for name in ("type", "items", "fields", "values"):
             if name in items:
                 items[name] = replace_type(
                     items[name],
@@ -539,15 +547,20 @@ def make_valid_avro(
         if "type" in avro and avro["type"] in (
             saladp + "record",
             saladp + "enum",
+            saladp + "map",
+            saladp + "union",
             "record",
             "enum",
+            "map",
+            "union",
         ):
             if (hasattr(avro, "get") and avro.get("abstract")) or ("abstract" in avro):
                 return avro
-            if avro["name"] in found:
-                return cast(str, avro["name"])
-            found.add(avro["name"])
-        for field in ("type", "items", "values", "fields"):
+            if "name" in avro:
+                if avro["name"] in found:
+                    return cast(str, avro["name"])
+                found.add(avro["name"])
+        for field in ("type", "items", "names", "values", "fields"):
             if field in avro:
                 avro[field] = make_valid_avro(
                     avro[field],
@@ -593,19 +606,19 @@ def deepcopy_strip(item: Any) -> Any:
 def extend_and_specialize(items: List[Dict[str, Any]], loader: Loader) -> List[Dict[str, Any]]:
     """Apply 'extend' and 'specialize' to fully materialize derived record types."""
     items2 = deepcopy_strip(items)
-    types = {i["name"]: i for i in items2}  # type: Dict[str, Any]
+    types: Dict[str, Any] = {i["name"]: i for i in items2}
     types.update({k[len(saladp) :]: v for k, v in types.items() if k.startswith(saladp)})
     results = []
 
     for stype in items2:
         if "extends" in stype:
-            specs = {}  # type: Dict[str, str]
+            specs: Dict[str, str] = {}
             if "specialize" in stype:
                 for spec in aslist(stype["specialize"]):
                     specs[spec["specializeFrom"]] = spec["specializeTo"]
 
-            exfields = []  # type: List[Any]
-            exsym = []  # type: List[str]
+            exfields: List[Any] = []
+            exsym: List[str] = []
             for ex in aslist(stype["extends"]):
                 if ex not in types:
                     raise ValidationException(
@@ -669,7 +682,7 @@ def extend_and_specialize(items: List[Dict[str, Any]], loader: Loader) -> List[D
 
                 stype["fields"] = combined_fields
 
-                fieldnames = set()  # type: Set[str]
+                fieldnames: Set[str] = set()
                 for field in stype["fields"]:
                     if field["name"] in fieldnames:
                         raise ValidationException(
@@ -689,7 +702,7 @@ def extend_and_specialize(items: List[Dict[str, Any]], loader: Loader) -> List[D
     for result in results:
         ex_types[result["name"]] = result
 
-    extended_by = {}  # type: Dict[str, str]
+    extended_by: Dict[str, str] = {}
     for result in results:
         if "extends" in result:
             for ex in aslist(result["extends"]):
@@ -706,6 +719,8 @@ def extend_and_specialize(items: List[Dict[str, Any]], loader: Loader) -> List[D
     for result in results:
         if "fields" in result:
             result["fields"] = replace_type(result["fields"], extended_by, loader, set())
+        elif "values" in result:
+            result["values"] = replace_type(result["values"], extended_by, loader, set())
 
     return results
 
@@ -717,7 +732,7 @@ def make_avro(
 ) -> List[Any]:
     j = extend_and_specialize(i, loader)
 
-    name_dict = {}  # type: Dict[str, Dict[str, Any]]
+    name_dict: Dict[str, Dict[str, Any]] = {}
     for entry in j:
         name_dict[entry["name"]] = entry
 
@@ -803,7 +818,7 @@ def print_fieldrefs(doc: List[Dict[str, Any]], loader: Loader, stream: IO[Any])
         if entry["type"] == "record":
             label = shortname(entry["name"])
             for field in entry.get("fields", []):
-                found = set()  # type: Set[str]
+                found: Set[str] = set()
                 field_name = shortname(field["name"])
                 replace_type(field["type"], {}, loader, found, find_embeds=False)
                 for each_type in found:
diff --git a/schema_salad/sourceline.py b/schema_salad/sourceline.py
index 70e0631b3..0515cd065 100644
--- a/schema_salad/sourceline.py
+++ b/schema_salad/sourceline.py
@@ -52,7 +52,7 @@ def reflow_all(text: str, maxline: Optional[int] = None) -> str:
         assert group is not None  # nosec
         maxno = max(maxno, len(group))
     maxno_text = maxline - maxno
-    msg = []  # type: List[str]
+    msg: List[str] = []
     for line in text.splitlines():
         g = lineno_re.match(line)
         if not g:
@@ -108,7 +108,7 @@ def strip_duplicated_lineno(text: str) -> str:
 
     Same as :py:meth:`strip_dup_lineno` but without reflow.
     """
-    pre = None  # type: Optional[str]
+    pre: Optional[str] = None
     msg = []
     for line in text.splitlines():
         g = lineno_re.match(line)
@@ -130,7 +130,7 @@ def strip_duplicated_lineno(text: str) -> str:
 def strip_dup_lineno(text: str, maxline: Optional[int] = None) -> str:
     if maxline is None:
         maxline = int(os.environ.get("COLUMNS", "100"))
-    pre = None  # type: Optional[str]
+    pre: Optional[str] = None
     msg = []
     maxno = 0
     for line in text.splitlines():
diff --git a/schema_salad/typescript/util/Internal.ts b/schema_salad/typescript/util/Internal.ts
index c805b41db..43b0a4660 100644
--- a/schema_salad/typescript/util/Internal.ts
+++ b/schema_salad/typescript/util/Internal.ts
@@ -8,6 +8,7 @@ export * from './loaders/ExpressionLoader'
 export * from './Fetcher'
 export * from './loaders/IdMapLoader'
 export * from './loaders/Loader'
+export * from './loaders/MapLoader'
 export * from './LoadingOptions'
 export * from './loaders/AnyLoader'
 export * from './loaders/PrimitiveLoader'
diff --git a/schema_salad/typescript/util/LoaderInstances.ts b/schema_salad/typescript/util/LoaderInstances.ts
index ca75ed9a2..8f4b30b47 100644
--- a/schema_salad/typescript/util/LoaderInstances.ts
+++ b/schema_salad/typescript/util/LoaderInstances.ts
@@ -8,6 +8,7 @@ import {
   _ArrayLoader,
   _EnumLoader,
   _IdMapLoader,
+  _MapLoader,
   _TypeDSLLoader,
   _SecondaryDSLLoader,
   TypeGuards,
diff --git a/schema_salad/typescript/util/LoadingOptions.ts b/schema_salad/typescript/util/LoadingOptions.ts
index ba3dabf70..9b877b7ab 100644
--- a/schema_salad/typescript/util/LoadingOptions.ts
+++ b/schema_salad/typescript/util/LoadingOptions.ts
@@ -6,17 +6,19 @@ export class LoadingOptions {
   fileUri?: string
   namespaces?: Dictionary<string>
   noLinkCheck?: boolean
+  container?: string
   schemas?: Dictionary<string>
   copyFrom?: LoadingOptions
   originalDoc: any
   vocab: Dictionary<string>
   rvocab: Dictionary<string>
 
-  constructor ({ fileUri, namespaces, noLinkCheck, schemas, originalDoc, copyFrom, fetcher }: {fileUri?: string, namespaces?: Dictionary<string>, noLinkCheck?: boolean, schemas?: Dictionary<string>, originalDoc?: any, copyFrom?: LoadingOptions, fetcher?: Fetcher}) {
+  constructor ({ fileUri, namespaces, noLinkCheck, container, schemas, originalDoc, copyFrom, fetcher}: {fileUri?: string, namespaces?: Dictionary<string>, noLinkCheck?: boolean, container?: string, schemas?: Dictionary<string>, originalDoc?: any, copyFrom?: LoadingOptions, fetcher?: Fetcher}) {
     this.idx = {}
     this.fileUri = fileUri
     this.namespaces = namespaces
     this.noLinkCheck = noLinkCheck
+    this.container = container
     this.schemas = schemas
     this.originalDoc = originalDoc
 
@@ -34,6 +36,9 @@ export class LoadingOptions {
       if (noLinkCheck === undefined) {
         this.noLinkCheck = copyFrom.noLinkCheck
       }
+      if (container === undefined) {
+        this.container = copyFrom.container
+      }
       if (schemas === undefined) {
         this.schemas = copyFrom.schemas
       }
diff --git a/schema_salad/typescript/util/loaders/ArrayLoader.ts b/schema_salad/typescript/util/loaders/ArrayLoader.ts
index eea30c54b..50b34a840 100644
--- a/schema_salad/typescript/util/loaders/ArrayLoader.ts
+++ b/schema_salad/typescript/util/loaders/ArrayLoader.ts
@@ -16,7 +16,8 @@ export class _ArrayLoader implements Loader {
     for (var val of doc) {
       try {
         const lf = await loadField(val, new _UnionLoader([this, ...this.items]), baseuri, loadingOptions)
-        if (Array.isArray(lf)) {
+        const flatten: boolean = loadingOptions.container != "@list"
+        if (flatten && Array.isArray(lf)) {
           r = r.concat(lf)
         } else {
           r.push(lf)
diff --git a/schema_salad/typescript/util/loaders/MapLoader.ts b/schema_salad/typescript/util/loaders/MapLoader.ts
new file mode 100644
index 000000000..454f9e9bd
--- /dev/null
+++ b/schema_salad/typescript/util/loaders/MapLoader.ts
@@ -0,0 +1,40 @@
+import {Loader, loadField, LoadingOptions, _UnionLoader, ValidationException, TypeGuards, Dictionary} from '../Internal'
+
+export class _MapLoader implements Loader {
+  values: Loader[]
+  container?: string
+  noLinkCheck?: boolean
+
+  constructor (values: Loader[], container?: string, noLinkCheck?: boolean) {
+    this.values = values
+    this.container = container
+    this.noLinkCheck = noLinkCheck
+  }
+
+  async load (doc: any, baseuri: string, loadingOptions: LoadingOptions, docRoot?: string): Promise<any> {
+    if (!TypeGuards.isDictionary(doc)) {
+      throw new ValidationException('Expected a dict')
+    }
+    if (this.container !== undefined || this.noLinkCheck !== undefined) {
+      loadingOptions = new LoadingOptions({ copyFrom: loadingOptions, container: this.container, noLinkCheck: this.noLinkCheck })
+    }
+    let r : Dictionary = {}
+    const errors: ValidationException[] = []
+    for (const key in doc) {
+      try {
+        r[key] = await loadField(doc[key], new _UnionLoader([this, ...this.values]), baseuri, loadingOptions)
+      } catch (e) {
+        if (e instanceof ValidationException) {
+          errors.push(e)
+        } else {
+          throw e
+        }
+      }
+    }
+    
+    if (errors.length > 0) {
+      throw new ValidationException('', errors)
+    }
+    return r
+  }
+}
diff --git a/schema_salad/typescript/util/loaders/RecordLoader.ts b/schema_salad/typescript/util/loaders/RecordLoader.ts
index a9920f030..ecedba6c0 100644
--- a/schema_salad/typescript/util/loaders/RecordLoader.ts
+++ b/schema_salad/typescript/util/loaders/RecordLoader.ts
@@ -2,14 +2,22 @@ import { Saveable, Loader, LoadingOptions, TypeGuards, ValidationException } fro
 
 export class _RecordLoader implements Loader {
   creatorFunc: (doc: any, baseuri: string, loadingOptions: LoadingOptions, docRoot?: string) => Promise<Saveable>
-  constructor (createrFunc: (doc: any, baseuri: string, loadingOptions: LoadingOptions, docRoot?: string) => Promise<Saveable>) {
+  container?: string
+  noLinkCheck?: boolean
+
+  constructor (createrFunc: (doc: any, baseuri: string, loadingOptions: LoadingOptions, docRoot?: string) => Promise<Saveable>, container?: string, noLinkCheck?: boolean) {
     this.creatorFunc = createrFunc
+    this.container = container
+    this.noLinkCheck = noLinkCheck
   }
 
   async load (doc: any, baseuri: string, loadingOptions: LoadingOptions, docRoot?: string): Promise<Saveable> {
     if (!TypeGuards.isDictionary(doc)) {
       throw new ValidationException('Expected a dict')
     }
+    if (this.container !== undefined || this.noLinkCheck !== undefined) {
+      loadingOptions = new LoadingOptions({ copyFrom: loadingOptions, container: this.container, noLinkCheck: this.noLinkCheck })
+    }
     return await this.creatorFunc(doc, baseuri, loadingOptions, docRoot)
   }
 }
diff --git a/schema_salad/typescript/util/loaders/UnionLoader.ts b/schema_salad/typescript/util/loaders/UnionLoader.ts
index 1a1fb4c3f..03f549527 100644
--- a/schema_salad/typescript/util/loaders/UnionLoader.ts
+++ b/schema_salad/typescript/util/loaders/UnionLoader.ts
@@ -7,6 +7,10 @@ export class _UnionLoader implements Loader {
     this.alternates = alternates
   }
 
+  addLoaders(loaders: Loader[]) {
+    this.alternates.concat(loaders);
+  }
+
   async load (doc: any, baseuri: string, loadingOptions: LoadingOptions, docRoot?: string): Promise<Saveable> {
     const errors: ValidationException[] = []
     for (const t of this.alternates) {
diff --git a/schema_salad/typescript_codegen.py b/schema_salad/typescript_codegen.py
index 9b5ca18dd..eb9835bf0 100644
--- a/schema_salad/typescript_codegen.py
+++ b/schema_salad/typescript_codegen.py
@@ -18,7 +18,7 @@
 from importlib_resources import files
 
 from . import _logger, schema
-from .codegen_base import CodeGenBase, TypeDef
+from .codegen_base import CodeGenBase, LazyInitDef, TypeDef
 from .exceptions import SchemaException
 from .java_codegen import _ensure_directory_and_write, _safe_makedirs
 from .schema import shortname
@@ -362,7 +362,12 @@ def end_class(self, classname: str, field_names: List[str]) -> None:
 """
             )
 
-    def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -> TypeDef:
+    def type_loader(
+        self,
+        type_declaration: Union[List[Any], Dict[str, Any], str],
+        container: Optional[str] = None,
+        no_link_check: Optional[bool] = None,
+    ) -> TypeDef:
         """Parse the given type declaration and declare its components."""
         if isinstance(type_declaration, MutableSequence):
             sub_types = [self.type_loader(i) for i in type_declaration]
@@ -390,6 +395,22 @@ def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -
                         instance_type=f"Array<{i.instance_type}>",
                     )
                 )
+            if type_declaration["type"] in (
+                "map",
+                "https://w3id.org/cwl/salad#map",
+            ):
+                i = self.type_loader(type_declaration["values"])
+                return self.declare_type(
+                    TypeDef(
+                        f"mapOf{i.name}",
+                        "new _MapLoader([{}], {}, {})".format(
+                            i.name,
+                            f"'{container}'" if container is not None else None,
+                            self.to_typescript(no_link_check),
+                        ),
+                        instance_type=f"Dictionary<{i.instance_type}>",
+                    )
+                )
             if type_declaration["type"] in ("enum", "https://w3id.org/cwl/salad#enum"):
                 return self.type_loader_enum(type_declaration)
 
@@ -400,13 +421,40 @@ def type_loader(self, type_declaration: Union[List[Any], Dict[str, Any], str]) -
                 return self.declare_type(
                     TypeDef(
                         self.safe_name(type_declaration["name"]) + "Loader",
-                        "new _RecordLoader({}.fromDoc)".format(
+                        "new _RecordLoader({}.fromDoc, {}, {})".format(
                             self.safe_name(type_declaration["name"]),
+                            f"'{container}'" if container is not None else None,
+                            self.to_typescript(no_link_check),
                         ),
                         instance_type="Internal." + self.safe_name(type_declaration["name"]),
                         abstract=type_declaration.get("abstract", False),
                     )
                 )
+            if type_declaration["type"] in (
+                "union",
+                "https://w3id.org/cwl/salad#union",
+            ):
+                # Declare the named loader to handle recursive union definitions
+                loader_name = self.safe_name(type_declaration["name"]) + "Loader"
+                loader_type = TypeDef(
+                    loader_name,
+                    "new _UnionLoader([])",
+                    instance_type="any",
+                )
+                self.declare_type(loader_type)
+                # Parse inner types
+                sub_types = [self.type_loader(i) for i in type_declaration["names"]]
+
+                # Register lazy initialization for the loader
+                self.add_lazy_init(
+                    LazyInitDef(
+                        loader_name,
+                        "{}.addLoaders([{}]);".format(
+                            loader_name, ", ".join(s.name for s in sub_types)
+                        ),
+                    )
+                )
+                return loader_type
             raise SchemaException("wft {}".format(type_declaration["type"]))
 
         if type_declaration in prims:
@@ -670,7 +718,7 @@ def uri_loader(
         scoped_id: bool,
         vocab_term: bool,
         ref_scope: Optional[int],
-        no_link_check: Optional[bool],
+        no_link_check: Optional[bool] = None,
     ) -> TypeDef:
         """Construct the TypeDef for the given URI loader."""
         instance_type = inner.instance_type or "any"
@@ -768,6 +816,11 @@ def expand_resource_template_to(resource: str, path: Path) -> None:
         sorted_modules = sorted(self.modules)
         internal_module_exports = "\n".join(f"export * from '../{f}'" for f in sorted_modules)
 
+        if self.lazy_inits:
+            loader_instances += "\n"
+            for lazy_init in self.lazy_inits.values():
+                loader_instances += f"{lazy_init.init}\n"
+
         example_tests = ""
         if self.examples:
             _safe_makedirs(self.test_resources_dir)
diff --git a/schema_salad/utils.py b/schema_salad/utils.py
index 1312178dd..bcc4b7a36 100644
--- a/schema_salad/utils.py
+++ b/schema_salad/utils.py
@@ -40,7 +40,8 @@
 AttachmentsType = Callable[[Union[CommentedMap, CommentedSeq]], bool]
 
 
-def add_dictlist(di, key, val):  # type: (Dict[Any, Any], Any, Any) -> None
+def add_dictlist(di: Dict[Any, Any], key: Any, val: Any) -> None:
+    """Manage element insertion in dicts of lists."""
     if key not in di:
         di[key] = []
     di[key].append(val)
@@ -57,8 +58,8 @@ def aslist(thing: Any) -> MutableSequence[Any]:
     return [thing]
 
 
-def flatten(thing, ltypes=(list, tuple)):
-    # type: (Any, Any) -> Any
+def flatten(thing: Any, ltypes: Any = (list, tuple)) -> Any:
+    """Flatten lists recursively."""
     # http://rightfootin.blogspot.com/2006/09/more-on-python-flatten.html
     if thing is None:
         return []
@@ -79,13 +80,13 @@ def flatten(thing, ltypes=(list, tuple)):
     return ltype(lst)
 
 
-# Check if we are on windows OS
-def onWindows():
-    # type: () -> (bool)
+def onWindows() -> bool:
+    """Check if Python is running on Windows OS."""
     return os.name == "nt"
 
 
-def convert_to_dict(j4):  # type: (Any) -> Any
+def convert_to_dict(j4: Any) -> Any:
+    """Convert generic Mapping objects to dicts recursively."""
     if isinstance(j4, Mapping):
         return {k: convert_to_dict(v) for k, v in j4.items()}
     if isinstance(j4, MutableSequence):
@@ -99,9 +100,9 @@ def json_dump(obj: Any, fp: IO[str], **kwargs: Any) -> None:
 
 
 def json_dumps(
-    obj,  # type: Any
-    **kwargs,  # type: Any
-):  # type: (...) -> str
+    obj: Any,
+    **kwargs: Any,
+) -> str:
     """Force use of unicode."""
     return json.dumps(convert_to_dict(obj), **kwargs)
 
diff --git a/schema_salad/validate.py b/schema_salad/validate.py
index 0f748fc76..87d2863ff 100644
--- a/schema_salad/validate.py
+++ b/schema_salad/validate.py
@@ -61,6 +61,8 @@ def avro_shortname(name: str) -> str:
     saladp + "enum": "enum",
     saladp + "array": "array",
     saladp + "record": "record",
+    saladp + "map": "map",
+    saladp + "union": "union",
 }
 
 
@@ -86,7 +88,8 @@ def avro_type_name(url: str) -> str:
     return ".".join(joined)
 
 
-def friendly(v):  # type: (Any) -> Any
+def friendly(v: Any) -> Any:
+    """Format an Avro schema into a pretty-printed representation."""
     if isinstance(v, avro.schema.NamedSchema):
         return avro_shortname(v.name)
     if isinstance(v, avro.schema.ArraySchema):
@@ -98,7 +101,8 @@ def friendly(v):  # type: (Any) -> Any
     return avro_shortname(v)
 
 
-def vpformat(datum):  # type: (Any) -> str
+def vpformat(datum: Any) -> str:
+    """Truncate a pretty-printed representation of a Python object to 160 characters."""
     a = pprint.pformat(datum)
     if len(a) > 160:
         a = a[0:160] + "[...]"
@@ -107,17 +111,16 @@ def vpformat(datum):  # type: (Any) -> str
 
 def validate_ex(
     expected_schema: Schema,
-    datum,  # type: Any
-    identifiers=None,  # type: Optional[List[str]]
-    strict=False,  # type: bool
-    foreign_properties=None,  # type: Optional[Set[str]]
-    raise_ex=True,  # type: bool
-    strict_foreign_properties=False,  # type: bool
-    logger=_logger,  # type: logging.Logger
-    skip_foreign_properties=False,  # type: bool
-    vocab=None,  # type: Optional[Mapping[str, str]]
-):
-    # type: (...) -> bool
+    datum: Any,
+    identifiers: Optional[List[str]] = None,
+    strict: bool = False,
+    foreign_properties: Optional[Set[str]] = None,
+    raise_ex: bool = True,
+    strict_foreign_properties: bool = False,
+    logger: logging.Logger = _logger,
+    skip_foreign_properties: bool = False,
+    vocab: Optional[Mapping[str, str]] = None,
+) -> bool:
     """Determine if a python datum is an instance of a schema."""
     debug = _logger.isEnabledFor(logging.DEBUG)
     if not identifiers:


Test patch:
diff --git a/schema_salad/tests/cpp_tests/01_single_record.h b/schema_salad/tests/cpp_tests/01_single_record.h
index 8a79cb080..c2d1ac4b0 100644
--- a/schema_salad/tests/cpp_tests/01_single_record.h
+++ b/schema_salad/tests/cpp_tests/01_single_record.h
@@ -107,6 +107,8 @@ template <typename T> struct IsConstant : std::false_type {};
 template <typename T>
 auto toYaml(std::vector<T> const& v) -> YAML::Node;
 template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node;
+template <typename T>
 auto toYaml(T const& t) -> YAML::Node;
 template <typename ...Args>
 auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
@@ -115,6 +117,8 @@ auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
 template <typename T>
 void fromYaml(YAML::Node const& n, std::vector<T>& v);
 template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v);
+template <typename T>
 void fromYaml(YAML::Node const& n, T& t);
 template <typename ...Args>
 void fromYaml(YAML::Node const& n, std::variant<Args...>& t);
@@ -162,6 +166,17 @@ struct DetectAndExtractFromYaml<std::vector<T>> {
     }
 };
 
+template <typename T>
+struct DetectAndExtractFromYaml<std::map<std::string, T>> {
+    auto operator()(YAML::Node const& n) const -> std::optional<std::map<std::string, T>> {
+        if (!n.IsDefined()) return std::nullopt;
+        if (!n.IsMap()) return std::nullopt;
+        auto res = std::map<std::string, T>{};
+        fromYaml(n, res);
+        return res;
+    }
+};
+
 template <typename T>
 class heap_object {
     std::unique_ptr<T> data = std::make_unique<T>();
@@ -268,6 +283,15 @@ auto toYaml(std::vector<T> const& v) -> YAML::Node {
     return n;
 }
 
+template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node {
+    auto n = YAML::Node(YAML::NodeType::Map);
+    for (auto const& [key, value] : v) {
+        n[key] = toYaml(value);
+    }
+    return n;
+}
+
 template <typename T>
 auto toYaml(T const& t) -> YAML::Node {
     if constexpr (std::is_enum_v<T>) {
@@ -293,6 +317,15 @@ void fromYaml(YAML::Node const& n, std::vector<T>& v){
     }
 }
 
+template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v){
+    if (!n.IsMap()) return;
+    for (auto e : n) {
+        auto key = e.first.as<std::string>();
+        fromYaml(e.second, v[key]);
+    }
+}
+
 template <typename T>
 void fromYaml(YAML::Node const& n, T& t){
     if constexpr (std::is_enum_v<T>) {
diff --git a/schema_salad/tests/cpp_tests/02_two_records.h b/schema_salad/tests/cpp_tests/02_two_records.h
index 7b8a11c16..d014b3b1d 100644
--- a/schema_salad/tests/cpp_tests/02_two_records.h
+++ b/schema_salad/tests/cpp_tests/02_two_records.h
@@ -107,6 +107,8 @@ template <typename T> struct IsConstant : std::false_type {};
 template <typename T>
 auto toYaml(std::vector<T> const& v) -> YAML::Node;
 template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node;
+template <typename T>
 auto toYaml(T const& t) -> YAML::Node;
 template <typename ...Args>
 auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
@@ -115,6 +117,8 @@ auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
 template <typename T>
 void fromYaml(YAML::Node const& n, std::vector<T>& v);
 template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v);
+template <typename T>
 void fromYaml(YAML::Node const& n, T& t);
 template <typename ...Args>
 void fromYaml(YAML::Node const& n, std::variant<Args...>& t);
@@ -162,6 +166,17 @@ struct DetectAndExtractFromYaml<std::vector<T>> {
     }
 };
 
+template <typename T>
+struct DetectAndExtractFromYaml<std::map<std::string, T>> {
+    auto operator()(YAML::Node const& n) const -> std::optional<std::map<std::string, T>> {
+        if (!n.IsDefined()) return std::nullopt;
+        if (!n.IsMap()) return std::nullopt;
+        auto res = std::map<std::string, T>{};
+        fromYaml(n, res);
+        return res;
+    }
+};
+
 template <typename T>
 class heap_object {
     std::unique_ptr<T> data = std::make_unique<T>();
@@ -305,6 +320,15 @@ auto toYaml(std::vector<T> const& v) -> YAML::Node {
     return n;
 }
 
+template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node {
+    auto n = YAML::Node(YAML::NodeType::Map);
+    for (auto const& [key, value] : v) {
+        n[key] = toYaml(value);
+    }
+    return n;
+}
+
 template <typename T>
 auto toYaml(T const& t) -> YAML::Node {
     if constexpr (std::is_enum_v<T>) {
@@ -330,6 +354,15 @@ void fromYaml(YAML::Node const& n, std::vector<T>& v){
     }
 }
 
+template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v){
+    if (!n.IsMap()) return;
+    for (auto e : n) {
+        auto key = e.first.as<std::string>();
+        fromYaml(e.second, v[key]);
+    }
+}
+
 template <typename T>
 void fromYaml(YAML::Node const& n, T& t){
     if constexpr (std::is_enum_v<T>) {
diff --git a/schema_salad/tests/cpp_tests/03_simple_inheritance.h b/schema_salad/tests/cpp_tests/03_simple_inheritance.h
index d023df30a..d17f82e3e 100644
--- a/schema_salad/tests/cpp_tests/03_simple_inheritance.h
+++ b/schema_salad/tests/cpp_tests/03_simple_inheritance.h
@@ -107,6 +107,8 @@ template <typename T> struct IsConstant : std::false_type {};
 template <typename T>
 auto toYaml(std::vector<T> const& v) -> YAML::Node;
 template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node;
+template <typename T>
 auto toYaml(T const& t) -> YAML::Node;
 template <typename ...Args>
 auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
@@ -115,6 +117,8 @@ auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
 template <typename T>
 void fromYaml(YAML::Node const& n, std::vector<T>& v);
 template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v);
+template <typename T>
 void fromYaml(YAML::Node const& n, T& t);
 template <typename ...Args>
 void fromYaml(YAML::Node const& n, std::variant<Args...>& t);
@@ -162,6 +166,17 @@ struct DetectAndExtractFromYaml<std::vector<T>> {
     }
 };
 
+template <typename T>
+struct DetectAndExtractFromYaml<std::map<std::string, T>> {
+    auto operator()(YAML::Node const& n) const -> std::optional<std::map<std::string, T>> {
+        if (!n.IsDefined()) return std::nullopt;
+        if (!n.IsMap()) return std::nullopt;
+        auto res = std::map<std::string, T>{};
+        fromYaml(n, res);
+        return res;
+    }
+};
+
 template <typename T>
 class heap_object {
     std::unique_ptr<T> data = std::make_unique<T>();
@@ -308,6 +323,15 @@ auto toYaml(std::vector<T> const& v) -> YAML::Node {
     return n;
 }
 
+template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node {
+    auto n = YAML::Node(YAML::NodeType::Map);
+    for (auto const& [key, value] : v) {
+        n[key] = toYaml(value);
+    }
+    return n;
+}
+
 template <typename T>
 auto toYaml(T const& t) -> YAML::Node {
     if constexpr (std::is_enum_v<T>) {
@@ -333,6 +357,15 @@ void fromYaml(YAML::Node const& n, std::vector<T>& v){
     }
 }
 
+template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v){
+    if (!n.IsMap()) return;
+    for (auto e : n) {
+        auto key = e.first.as<std::string>();
+        fromYaml(e.second, v[key]);
+    }
+}
+
 template <typename T>
 void fromYaml(YAML::Node const& n, T& t){
     if constexpr (std::is_enum_v<T>) {
diff --git a/schema_salad/tests/cpp_tests/04_abstract_inheritance.h b/schema_salad/tests/cpp_tests/04_abstract_inheritance.h
index 7b3b82414..03689d1a1 100644
--- a/schema_salad/tests/cpp_tests/04_abstract_inheritance.h
+++ b/schema_salad/tests/cpp_tests/04_abstract_inheritance.h
@@ -107,6 +107,8 @@ template <typename T> struct IsConstant : std::false_type {};
 template <typename T>
 auto toYaml(std::vector<T> const& v) -> YAML::Node;
 template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node;
+template <typename T>
 auto toYaml(T const& t) -> YAML::Node;
 template <typename ...Args>
 auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
@@ -115,6 +117,8 @@ auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
 template <typename T>
 void fromYaml(YAML::Node const& n, std::vector<T>& v);
 template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v);
+template <typename T>
 void fromYaml(YAML::Node const& n, T& t);
 template <typename ...Args>
 void fromYaml(YAML::Node const& n, std::variant<Args...>& t);
@@ -162,6 +166,17 @@ struct DetectAndExtractFromYaml<std::vector<T>> {
     }
 };
 
+template <typename T>
+struct DetectAndExtractFromYaml<std::map<std::string, T>> {
+    auto operator()(YAML::Node const& n) const -> std::optional<std::map<std::string, T>> {
+        if (!n.IsDefined()) return std::nullopt;
+        if (!n.IsMap()) return std::nullopt;
+        auto res = std::map<std::string, T>{};
+        fromYaml(n, res);
+        return res;
+    }
+};
+
 template <typename T>
 class heap_object {
     std::unique_ptr<T> data = std::make_unique<T>();
@@ -292,6 +307,15 @@ auto toYaml(std::vector<T> const& v) -> YAML::Node {
     return n;
 }
 
+template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node {
+    auto n = YAML::Node(YAML::NodeType::Map);
+    for (auto const& [key, value] : v) {
+        n[key] = toYaml(value);
+    }
+    return n;
+}
+
 template <typename T>
 auto toYaml(T const& t) -> YAML::Node {
     if constexpr (std::is_enum_v<T>) {
@@ -317,6 +341,15 @@ void fromYaml(YAML::Node const& n, std::vector<T>& v){
     }
 }
 
+template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v){
+    if (!n.IsMap()) return;
+    for (auto e : n) {
+        auto key = e.first.as<std::string>();
+        fromYaml(e.second, v[key]);
+    }
+}
+
 template <typename T>
 void fromYaml(YAML::Node const& n, T& t){
     if constexpr (std::is_enum_v<T>) {
diff --git a/schema_salad/tests/cpp_tests/05_specialization.h b/schema_salad/tests/cpp_tests/05_specialization.h
index a83e14ff3..5db2c0c12 100644
--- a/schema_salad/tests/cpp_tests/05_specialization.h
+++ b/schema_salad/tests/cpp_tests/05_specialization.h
@@ -107,6 +107,8 @@ template <typename T> struct IsConstant : std::false_type {};
 template <typename T>
 auto toYaml(std::vector<T> const& v) -> YAML::Node;
 template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node;
+template <typename T>
 auto toYaml(T const& t) -> YAML::Node;
 template <typename ...Args>
 auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
@@ -115,6 +117,8 @@ auto toYaml(std::variant<Args...> const& t) -> YAML::Node;
 template <typename T>
 void fromYaml(YAML::Node const& n, std::vector<T>& v);
 template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v);
+template <typename T>
 void fromYaml(YAML::Node const& n, T& t);
 template <typename ...Args>
 void fromYaml(YAML::Node const& n, std::variant<Args...>& t);
@@ -162,6 +166,17 @@ struct DetectAndExtractFromYaml<std::vector<T>> {
     }
 };
 
+template <typename T>
+struct DetectAndExtractFromYaml<std::map<std::string, T>> {
+    auto operator()(YAML::Node const& n) const -> std::optional<std::map<std::string, T>> {
+        if (!n.IsDefined()) return std::nullopt;
+        if (!n.IsMap()) return std::nullopt;
+        auto res = std::map<std::string, T>{};
+        fromYaml(n, res);
+        return res;
+    }
+};
+
 template <typename T>
 class heap_object {
     std::unique_ptr<T> data = std::make_unique<T>();
@@ -370,6 +385,15 @@ auto toYaml(std::vector<T> const& v) -> YAML::Node {
     return n;
 }
 
+template <typename T>
+auto toYaml(std::map<std::string, T> const& v) -> YAML::Node {
+    auto n = YAML::Node(YAML::NodeType::Map);
+    for (auto const& [key, value] : v) {
+        n[key] = toYaml(value);
+    }
+    return n;
+}
+
 template <typename T>
 auto toYaml(T const& t) -> YAML::Node {
     if constexpr (std::is_enum_v<T>) {
@@ -395,6 +419,15 @@ void fromYaml(YAML::Node const& n, std::vector<T>& v){
     }
 }
 
+template <typename T>
+void fromYaml(YAML::Node const& n, std::map<std::string, T>& v){
+    if (!n.IsMap()) return;
+    for (auto e : n) {
+        auto key = e.first.as<std::string>();
+        fromYaml(e.second, v[key]);
+    }
+}
+
 template <typename T>
 void fromYaml(YAML::Node const& n, T& t){
     if constexpr (std::is_enum_v<T>) {
diff --git a/schema_salad/tests/cwl-pre.yml b/schema_salad/tests/cwl-pre.yml
index aae5f4f8a..ccd65e4a1 100644
--- a/schema_salad/tests/cwl-pre.yml
+++ b/schema_salad/tests/cwl-pre.yml
@@ -1,2414 +1,2795 @@
 [
-    {
-        "name": "https://w3id.org/cwl/cwl#Common Workflow Language, v1.0",
-        "type": "documentation",
-        "doc": "\n"
-    },
-    {
-        "type": "documentation",
-        "name": "https://w3id.org/cwl/salad#Schema",
-        "doc": "# Schema\n"
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/salad#Documented",
-        "abstract": true,
-        "docParent": "https://w3id.org/cwl/salad#Schema",
-        "fields": [
+  {
+    "name": "https://w3id.org/cwl/cwl#Common Workflow Language, v1.0",
+    "type": "documentation",
+    "doc": "\n"
+  },
+  {
+    "type": "documentation",
+    "name": "https://w3id.org/cwl/salad#Schema",
+    "doc": "# Schema\n"
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/salad#Documented",
+    "abstract": true,
+    "docParent": "https://w3id.org/cwl/salad#Schema",
+    "fields": [
+      {
+        "doc": "A documentation string for this object, or an array of strings which should be concatenated.",
+        "jsonldPredicate": "rdfs:comment",
+        "name": "https://w3id.org/cwl/salad#Documented/doc",
+        "type": [
+          "null",
+          "string",
           {
-            "doc": "A documentation string for this object, or an array of strings which should be concatenated.",
-            "jsonldPredicate": "rdfs:comment",
-            "name": "https://w3id.org/cwl/salad#Documented/doc",
-            "type": [
-              "null",
-              "string",
-              {
-                "items": "string",
-                "type": "array"
-              }
-            ]
+            "items": "string",
+            "type": "array"
           }
         ]
-    },
-    {
-        "name": "https://w3id.org/cwl/salad#PrimitiveType",
-        "type": "enum",
-        "symbols": [
-            "https://w3id.org/cwl/salad#null",
-            "http://www.w3.org/2001/XMLSchema#boolean",
-            "http://www.w3.org/2001/XMLSchema#int",
-            "http://www.w3.org/2001/XMLSchema#long",
-            "http://www.w3.org/2001/XMLSchema#float",
-            "http://www.w3.org/2001/XMLSchema#double",
-            "http://www.w3.org/2001/XMLSchema#string"
-        ],
-        "doc": [
-            "Salad data types are based on Avro schema declarations.  Refer to the\n[Avro schema declaration documentation](https://avro.apache.org/docs/current/spec.html#schemas) for\ndetailed information.\n",
-            "null: no value",
-            "boolean: a binary value",
-            "int: 32-bit signed integer",
-            "long: 64-bit signed integer",
-            "float: single precision (32-bit) IEEE 754 floating-point number",
-            "double: double precision (64-bit) IEEE 754 floating-point number",
-            "string: Unicode character sequence"
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/salad#Any",
-        "type": "enum",
-        "symbols": [
-            "https://w3id.org/cwl/salad#Any"
-        ],
-        "doc": "The **Any** type validates for any non-null value.\n",
-        "docAfter": "https://w3id.org/cwl/salad#PrimitiveType"
-    },
-    {
-        "name": "https://w3id.org/cwl/salad#RecordField",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/salad#Documented",
-        "doc": "A field of a record.",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/salad#RecordField/name",
-                "type": "string",
-                "jsonldPredicate": "@id",
-                "doc": "The name of the field\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/salad#RecordField/type",
-                "type": [
-                    "PrimitiveType",
-                    "RecordSchema",
-                    "EnumSchema",
-                    "ArraySchema",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": [
-                            "PrimitiveType",
-                            "RecordSchema",
-                            "EnumSchema",
-                            "ArraySchema",
-                            "string"
-                        ]
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/salad#type",
-                    "_type": "@vocab",
-                    "typeDSL": true,
-                    "refScope": 2
-                },
-                "doc": "The field type\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/salad#RecordSchema",
-        "type": "record",
-        "fields": [
-            {
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "RecordField"
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/salad#fields",
-                    "mapSubject": "name",
-                    "mapPredicate": "type"
-                },
-                "doc": "Defines the fields of the record.",
-                "name": "https://w3id.org/cwl/salad#RecordSchema/fields"
-            },
-            {
-                "doc": "Must be `record`",
-                "type": {
-                    "type": "enum",
-                    "name": "https://w3id.org/cwl/salad#RecordSchema/type/Record_name",
-                    "symbols": [
-                        "https://w3id.org/cwl/salad#record"
-                    ]
-                },
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/salad#type",
-                    "_type": "@vocab",
-                    "typeDSL": true,
-                    "refScope": 2
-                },
-                "name": "https://w3id.org/cwl/salad#RecordSchema/type"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/salad#EnumSchema",
-        "type": "record",
-        "doc": "Define an enumerated type.\n",
-        "fields": [
-            {
-                "jsonldPredicate": "@id",
-                "name": "https://w3id.org/cwl/salad#EnumSchema/name",
-                "type": [
-                  "null",
-                  "string"
-                ]
-            },
-            {
-                "type": {
-                    "type": "array",
-                    "items": "string"
-                },
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/salad#symbols",
-                    "_type": "@id",
-                    "identity": true
-                },
-                "doc": "Defines the set of valid symbols.",
-                "name": "https://w3id.org/cwl/salad#EnumSchema/symbols"
-            },
-            {
-                "doc": "Must be `enum`",
-                "type": {
-                    "type": "enum",
-                    "name": "https://w3id.org/cwl/salad#EnumSchema/type/Enum_name",
-                    "symbols": [
-                        "https://w3id.org/cwl/salad#enum"
-                    ]
-                },
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/salad#type",
-                    "_type": "@vocab",
-                    "typeDSL": true,
-                    "refScope": 2
-                },
-                "name": "https://w3id.org/cwl/salad#EnumSchema/type"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/salad#ArraySchema",
-        "type": "record",
-        "fields": [
-            {
-                "type": [
-                    "PrimitiveType",
-                    "RecordSchema",
-                    "EnumSchema",
-                    "ArraySchema",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": [
-                            "PrimitiveType",
-                            "RecordSchema",
-                            "EnumSchema",
-                            "ArraySchema",
-                            "string"
-                        ]
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/salad#items",
-                    "_type": "@vocab",
-                    "refScope": 2
-                },
-                "doc": "Defines the type of the array elements.",
-                "name": "https://w3id.org/cwl/salad#ArraySchema/items"
-            },
-            {
-                "doc": "Must be `array`",
-                "type": {
-                    "type": "enum",
-                    "name": "https://w3id.org/cwl/salad#ArraySchema/type/Array_name",
-                    "symbols": [
-                        "https://w3id.org/cwl/salad#array"
-                    ]
-                },
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/salad#type",
-                    "_type": "@vocab",
-                    "typeDSL": true,
-                    "refScope": 2
-                },
-                "name": "https://w3id.org/cwl/salad#ArraySchema/type"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#BaseTypesDoc",
-        "type": "documentation",
-        "doc": "## Base types\n",
-        "docChild": [
-            "https://w3id.org/cwl/cwl#CWLType",
-            "https://w3id.org/cwl/cwl#Process"
-        ]
-    },
-    {
-        "type": "enum",
-        "name": "https://w3id.org/cwl/cwl#CWLVersion",
-        "doc": "Version symbols for published CWL document versions.",
-        "symbols": [
-            "https://w3id.org/cwl/cwl#draft-2",
-            "https://w3id.org/cwl/cwl#draft-3.dev1",
-            "https://w3id.org/cwl/cwl#draft-3.dev2",
-            "https://w3id.org/cwl/cwl#draft-3.dev3",
-            "https://w3id.org/cwl/cwl#draft-3.dev4",
-            "https://w3id.org/cwl/cwl#draft-3.dev5",
-            "https://w3id.org/cwl/cwl#draft-3",
-            "https://w3id.org/cwl/cwl#draft-4.dev1",
-            "https://w3id.org/cwl/cwl#draft-4.dev2",
-            "https://w3id.org/cwl/cwl#draft-4.dev3",
-            "https://w3id.org/cwl/cwl#v1.0.dev4",
-            "https://w3id.org/cwl/cwl#v1.0"
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#CWLType",
-        "type": "enum",
-        "extends": "https://w3id.org/cwl/salad#PrimitiveType",
-        "symbols": [
-            "https://w3id.org/cwl/cwl#File",
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/salad#PrimitiveType",
+    "type": "enum",
+    "symbols": [
+      "https://w3id.org/cwl/salad#null",
+      "http://www.w3.org/2001/XMLSchema#boolean",
+      "http://www.w3.org/2001/XMLSchema#int",
+      "http://www.w3.org/2001/XMLSchema#long",
+      "http://www.w3.org/2001/XMLSchema#float",
+      "http://www.w3.org/2001/XMLSchema#double",
+      "http://www.w3.org/2001/XMLSchema#string"
+    ],
+    "doc": [
+      "Names of salad data types (based on Avro schema declarations).\n\nRefer to the [Avro schema declaration documentation](https://avro.apache.org/docs/current/spec.html#schemas) for\ndetailed information.\n",
+      "null: no value",
+      "boolean: a binary value",
+      "int: 32-bit signed integer",
+      "long: 64-bit signed integer",
+      "float: single precision (32-bit) IEEE 754 floating-point number",
+      "double: double precision (64-bit) IEEE 754 floating-point number",
+      "string: Unicode character sequence"
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/salad#Any",
+    "type": "enum",
+    "symbols": [
+      "https://w3id.org/cwl/salad#Any"
+    ],
+    "doc": "The **Any** type validates for any non-null value.\n",
+    "docAfter": "https://w3id.org/cwl/salad#PrimitiveType"
+  },
+  {
+    "name": "https://w3id.org/cwl/salad#RecordField",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/salad#Documented",
+    "doc": "A field of a record.",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/salad#RecordField/name",
+        "type": "string",
+        "jsonldPredicate": "@id",
+        "doc": "The name of the field\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/salad#RecordField/type",
+        "type": [
+          "PrimitiveType",
+          "RecordSchema",
+          "EnumSchema",
+          "ArraySchema",
+          "MapSchema",
+          "UnionSchema",
+          "string",
+          {
+            "type": "array",
+            "items": [
+              "PrimitiveType",
+              "RecordSchema",
+              "EnumSchema",
+              "ArraySchema",
+              "MapSchema",
+              "UnionSchema",
+              "string"
+            ]
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#type",
+          "_type": "@vocab",
+          "typeDSL": true,
+          "refScope": 2
+        },
+        "doc": "The field type\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/salad#RecordSchema",
+    "type": "record",
+    "fields": [
+      {
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": "RecordField"
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#fields",
+          "mapSubject": "name",
+          "mapPredicate": "type"
+        },
+        "doc": "Defines the fields of the record.",
+        "name": "https://w3id.org/cwl/salad#RecordSchema/fields"
+      },
+      {
+        "doc": "Must be `record`",
+        "type": {
+          "type": "enum",
+          "name": "https://w3id.org/cwl/salad#RecordSchema/type/Record_name",
+          "symbols": [
+            "https://w3id.org/cwl/salad#record"
+          ]
+        },
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#type",
+          "_type": "@vocab",
+          "typeDSL": true,
+          "refScope": 2
+        },
+        "name": "https://w3id.org/cwl/salad#RecordSchema/type"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/salad#EnumSchema",
+    "type": "record",
+    "doc": "Define an enumerated type.\n",
+    "fields": [
+      {
+        "jsonldPredicate": "@id",
+        "name": "https://w3id.org/cwl/salad#EnumSchema/name",
+        "type": [
+          "null",
+          "string"
+        ]
+      },
+      {
+        "type": {
+          "type": "array",
+          "items": "string"
+        },
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#symbols",
+          "_type": "@id",
+          "identity": true
+        },
+        "doc": "Defines the set of valid symbols.",
+        "name": "https://w3id.org/cwl/salad#EnumSchema/symbols"
+      },
+      {
+        "doc": "Must be `enum`",
+        "type": {
+          "type": "enum",
+          "name": "https://w3id.org/cwl/salad#EnumSchema/type/Enum_name",
+          "symbols": [
+            "https://w3id.org/cwl/salad#enum"
+          ]
+        },
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#type",
+          "_type": "@vocab",
+          "typeDSL": true,
+          "refScope": 2
+        },
+        "name": "https://w3id.org/cwl/salad#EnumSchema/type"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/salad#ArraySchema",
+    "type": "record",
+    "fields": [
+      {
+        "type": [
+          "PrimitiveType",
+          "RecordSchema",
+          "EnumSchema",
+          "ArraySchema",
+          "MapSchema",
+          "UnionSchema",
+          "string",
+          {
+            "type": "array",
+            "items": [
+              "PrimitiveType",
+              "RecordSchema",
+              "EnumSchema",
+              "ArraySchema",
+              "MapSchema",
+              "UnionSchema",
+              "string"
+            ]
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#items",
+          "_type": "@vocab",
+          "refScope": 2
+        },
+        "doc": "Defines the type of the array elements.",
+        "name": "https://w3id.org/cwl/salad#ArraySchema/items"
+      },
+      {
+        "doc": "Must be `array`",
+        "type": {
+          "type": "enum",
+          "name": "https://w3id.org/cwl/salad#ArraySchema/type/Array_name",
+          "symbols": [
+            "https://w3id.org/cwl/salad#array"
+          ]
+        },
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#type",
+          "_type": "@vocab",
+          "typeDSL": true,
+          "refScope": 2
+        },
+        "name": "https://w3id.org/cwl/salad#ArraySchema/type"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/salad#MapSchema",
+    "type": "record",
+    "fields": [
+      {
+        "doc": "Must be `map`",
+        "type": {
+          "type": "enum",
+          "name": "https://w3id.org/cwl/salad#MapSchema/type/Map_name",
+          "symbols": [
+            "https://w3id.org/cwl/salad#map"
+          ]
+        },
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#type",
+          "_type": "@vocab",
+          "typeDSL": true,
+          "refScope": 2
+        },
+        "name": "https://w3id.org/cwl/salad#MapSchema/type"
+      },
+      {
+        "type": [
+          "PrimitiveType",
+          "RecordSchema",
+          "EnumSchema",
+          "ArraySchema",
+          "MapSchema",
+          "UnionSchema",
+          "string",
+          {
+            "type": "array",
+            "items": [
+              "PrimitiveType",
+              "RecordSchema",
+              "EnumSchema",
+              "ArraySchema",
+              "MapSchema",
+              "UnionSchema",
+              "string"
+            ]
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#values",
+          "_type": "@vocab",
+          "refScope": 2
+        },
+        "doc": "Defines the type of the map elements.",
+        "name": "https://w3id.org/cwl/salad#MapSchema/values"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/salad#UnionSchema",
+    "type": "record",
+    "fields": [
+      {
+        "type": [
+          "PrimitiveType",
+          "RecordSchema",
+          "EnumSchema",
+          "ArraySchema",
+          "MapSchema",
+          "UnionSchema",
+          "string",
+          {
+            "type": "array",
+            "items": [
+              "PrimitiveType",
+              "RecordSchema",
+              "EnumSchema",
+              "ArraySchema",
+              "MapSchema",
+              "UnionSchema",
+              "string"
+            ]
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#names",
+          "_type": "@vocab",
+          "refScope": 2
+        },
+        "doc": "Defines the type of the union elements.",
+        "name": "https://w3id.org/cwl/salad#UnionSchema/names"
+      },
+      {
+        "doc": "Must be `union`",
+        "type": {
+          "type": "enum",
+          "name": "https://w3id.org/cwl/salad#UnionSchema/type/Union_name",
+          "symbols": [
+            "https://w3id.org/cwl/salad#union"
+          ]
+        },
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#type",
+          "_type": "@vocab",
+          "typeDSL": true,
+          "refScope": 2
+        },
+        "name": "https://w3id.org/cwl/salad#UnionSchema/type"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CWLType",
+    "type": "enum",
+    "extends": "https://w3id.org/cwl/salad#PrimitiveType",
+    "symbols": [
+      "https://w3id.org/cwl/cwl#File",
+      "https://w3id.org/cwl/cwl#Directory"
+    ],
+    "doc": [
+      "Extends primitive types with the concept of a file and directory as a builtin type.",
+      "File: A File object",
+      "Directory: A Directory object"
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CWLArraySchema",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/salad#ArraySchema",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#CWLArraySchema/items",
+        "type": [
+          "PrimitiveType",
+          "https://w3id.org/cwl/cwl#CWLRecordSchema",
+          "EnumSchema",
+          "https://w3id.org/cwl/cwl#CWLArraySchema",
+          "string",
+          {
+            "items": [
+              "PrimitiveType",
+              "https://w3id.org/cwl/cwl#CWLRecordSchema",
+              "EnumSchema",
+              "https://w3id.org/cwl/cwl#CWLArraySchema",
+              "string"
+            ],
+            "type": "array"
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#items",
+          "_type": "@vocab",
+          "refScope": 2
+        },
+        "doc": "Defines the type of the array elements."
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CWLRecordField",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/salad#RecordField",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#CWLRecordField/type",
+        "type": [
+          "PrimitiveType",
+          "https://w3id.org/cwl/cwl#CWLRecordSchema",
+          "EnumSchema",
+          "https://w3id.org/cwl/cwl#CWLArraySchema",
+          "string",
+          {
+            "items": [
+              "PrimitiveType",
+              "https://w3id.org/cwl/cwl#CWLRecordSchema",
+              "EnumSchema",
+              "https://w3id.org/cwl/cwl#CWLArraySchema",
+              "string"
+            ],
+            "type": "array"
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#type",
+          "_type": "@vocab",
+          "refScope": 2,
+          "typeDSL": true
+        },
+        "doc": "The field type\n"
+      }
+    ]
+  },
+  {
+    "extends": "https://w3id.org/cwl/salad#RecordSchema",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#CWLRecordSchema/fields",
+        "type": [
+          "null",
+          {
+            "items": "https://w3id.org/cwl/cwl#CWLRecordField",
+            "type": "array"
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#fields",
+          "mapPredicate": "type",
+          "mapSubject": "name"
+        },
+        "doc": "Defines the fields of the record."
+      }
+    ],
+    "name": "https://w3id.org/cwl/cwl#CWLRecordSchema",
+    "type": "record"
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#File",
+    "type": "record",
+    "docParent": "https://w3id.org/cwl/cwl#CWLType",
+    "doc": "Represents a file (or group of files when `secondaryFiles` is provided) that\nwill be accessible by tools using standard POSIX file system call API such as\nopen(2) and read(2).\n\nFiles are represented as objects with `class` of `File`.  File objects have\na number of properties that provide metadata about the file.\n\nThe `location` property of a File is a URI that uniquely identifies the\nfile.  Implementations must support the file:// URI scheme and may support\nother schemes such as http://.  The value of `location` may also be a\nrelative reference, in which case it must be resolved relative to the URI\nof the document it appears in.  Alternately to `location`, implementations\nmust also accept the `path` property on File, which must be a filesystem\npath available on the same host as the CWL runner (for inputs) or the\nruntime environment of a command line tool execution (for command line tool\noutputs).\n\nIf no `location` or `path` is specified, a file object must specify\n`contents` with the UTF-8 text content of the file.  This is a \"file\nliteral\".  File literals do not correspond to external resources, but are\ncreated on disk with `contents` with when needed for a executing a tool.\nWhere appropriate, expressions can return file literals to define new files\non a runtime.  The maximum size of `contents` is 64 kilobytes.\n\nThe `basename` property defines the filename on disk where the file is\nstaged.  This may differ from the resource name.  If not provided,\n`basename` must be computed from the last path part of `location` and made\navailable to expressions.\n\nThe `secondaryFiles` property is a list of File or Directory objects that\nmust be staged in the same directory as the primary file.  It is an error\nfor file names to be duplicated in `secondaryFiles`.\n\nThe `size` property is the size in bytes of the File.  It must be computed\nfrom the resource and made available to expressions.  The `checksum` field\ncontains a cryptographic hash of the file content for use it verifying file\ncontents.  Implementations may, at user option, enable or disable\ncomputation of the `checksum` field for performance or other reasons.\nHowever, the ability to compute output checksums is required to pass the\nCWL conformance test suite.\n\nWhen executing a CommandLineTool, the files and secondary files may be\nstaged to an arbitrary directory, but must use the value of `basename` for\nthe filename.  The `path` property must be file path in the context of the\ntool execution runtime (local to the compute node, or within the executing\ncontainer).  All computed properties should be available to expressions.\nFile literals also must be staged and `path` must be set.\n\nWhen collecting CommandLineTool outputs, `glob` matching returns file paths\n(with the `path` property) and the derived properties. This can all be\nmodified by `outputEval`.  Alternately, if the file `cwl.output.json` is\npresent in the output, `outputBinding` is ignored.\n\nFile objects in the output must provide either a `location` URI or a `path`\nproperty in the context of the tool execution runtime (local to the compute\nnode, or within the executing container).\n\nWhen evaluating an ExpressionTool, file objects must be referenced via\n`location` (the expression tool does not have access to files on disk so\n`path` is meaningless) or as file literals.  It is legal to return a file\nobject with an existing `location` but a different `basename`.  The\n`loadContents` field of ExpressionTool inputs behaves the same as on\nCommandLineTool inputs, however it is not meaningful on the outputs.\n\nAn ExpressionTool may forward file references from input to output by using\nthe same value for `location`.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#File/class",
+        "type": {
+          "type": "enum",
+          "name": "https://w3id.org/cwl/cwl#File/class/File_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#File"
+          ]
+        },
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        },
+        "doc": "Must be `File` to indicate this object describes a file."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/location",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "An IRI that identifies the file resource.  This may be a relative\nreference, in which case it must be resolved using the base IRI of the\ndocument.  The location may refer to a local or remote resource; the\nimplementation must use the IRI to retrieve file content.  If an\nimplementation is unable to retrieve the file content stored at a\nremote resource (due to unsupported protocol, access denied, or other\nissue) it must signal an error.\n\nIf the `location` field is not provided, the `contents` field must be\nprovided.  The implementation must assign a unique identifier for\nthe `location` field.\n\nIf the `path` field is provided but the `location` field is not, an\nimplementation may assign the value of the `path` field to `location`,\nthen follow the rules above.\n",
+        "jsonldPredicate": {
+          "_id": "@id",
+          "_type": "@id"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/path",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "The local host path where the File is available when a CommandLineTool is\nexecuted.  This field must be set by the implementation.  The final\npath component must match the value of `basename`.  This field\nmust not be used in any other context.  The command line tool being\nexecuted must be able to to access the file at `path` using the POSIX\n`open(2)` syscall.\n\nAs a special case, if the `path` field is provided but the `location`\nfield is not, an implementation may assign the value of the `path`\nfield to `location`, and remove the `path` field.\n\nIf the `path` contains [POSIX shell metacharacters](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02)\n(`|`,`&`, `;`, `<`, `>`, `(`,`)`, `$`,`` ` ``, `\\`, `\"`, `'`,\n`<space>`, `<tab>`, and `<newline>`) or characters\n[not allowed](http://www.iana.org/assignments/idna-tables-6.3.0/idna-tables-6.3.0.xhtml)\nfor [Internationalized Domain Names for Applications](https://tools.ietf.org/html/rfc6452)\nthen implementations may terminate the process with a\n`permanentFailure`.\n",
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#path",
+          "_type": "@id"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/basename",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "The base name of the file, that is, the name of the file without any\nleading directory path.  The base name must not contain a slash `/`.\n\nIf not provided, the implementation must set this field based on the\n`location` field by taking the final path component after parsing\n`location` as an IRI.  If `basename` is provided, it is not required to\nmatch the value from `location`.\n\nWhen this file is made available to a CommandLineTool, it must be named\nwith `basename`, i.e. the final component of the `path` field must match\n`basename`.\n",
+        "jsonldPredicate": "cwl:basename"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/dirname",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "The name of the directory containing file, that is, the path leading up\nto the final slash in the path such that `dirname + '/' + basename ==\npath`.\n\nThe implementation must set this field based on the value of `path`\nprior to evaluating parameter references or expressions in a\nCommandLineTool document.  This field must not be used in any other\ncontext.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/nameroot",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "The basename root such that `nameroot + nameext == basename`, and\n`nameext` is empty or begins with a period and contains at most one\nperiod.  For the purposess of path splitting leading periods on the\nbasename are ignored; a basename of `.cshrc` will have a nameroot of\n`.cshrc`.\n\nThe implementation must set this field automatically based on the value\nof `basename` prior to evaluating parameter references or expressions.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/nameext",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "The basename extension such that `nameroot + nameext == basename`, and\n`nameext` is empty or begins with a period and contains at most one\nperiod.  Leading periods on the basename are ignored; a basename of\n`.cshrc` will have an empty `nameext`.\n\nThe implementation must set this field automatically based on the value\nof `basename` prior to evaluating parameter references or expressions.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/checksum",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "Optional hash code for validating file integrity.  Currently must be in the form\n\"sha1$ + hexadecimal string\" using the SHA-1 algorithm.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/size",
+        "type": [
+          "null",
+          "int",
+          "long"
+        ],
+        "doc": "Optional file size"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/secondaryFiles",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": [
+              "https://w3id.org/cwl/cwl#File",
+              "https://w3id.org/cwl/cwl#Directory"
+            ]
+          }
+        ],
+        "jsonldPredicate": "cwl:secondaryFiles",
+        "doc": "A list of additional files or directories that are associated with the\nprimary file and must be transferred alongside the primary file.\nExamples include indexes of the primary file, or external references\nwhich must be included when loading primary document.  A file object\nlisted in `secondaryFiles` may itself include `secondaryFiles` for\nwhich the same rules apply.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/format",
+        "type": [
+          "null",
+          "string"
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#format",
+          "_type": "@id",
+          "identity": true,
+          "noLinkCheck": true
+        },
+        "doc": "The format of the file: this must be an IRI of a concept node that\nrepresents the file format, preferrably defined within an ontology.\nIf no ontology is available, file formats may be tested by exact match.\n\nReasoning about format compatability must be done by checking that an\ninput file format is the same, `owl:equivalentClass` or\n`rdfs:subClassOf` the format required by the input parameter.\n`owl:equivalentClass` is transitive with `rdfs:subClassOf`, e.g. if\n`<B> owl:equivalentClass <C>` and `<B> owl:subclassOf <A>` then infer\n`<C> owl:subclassOf <A>`.\n\nFile format ontologies may be provided in the \"$schemas\" metadata at the\nroot of the document.  If no ontologies are specified in `$schemas`, the\nruntime may perform exact file format matches.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#File/contents",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "File contents literal.  Maximum of 64 KiB.\n\nIf neither `location` nor `path` is provided, `contents` must be\nnon-null.  The implementation must assign a unique identifier for the\n`location` field.  When the file is staged as input to CommandLineTool,\nthe value of `contents` must be written to a file.\n\nIf `loadContents` of `inputBinding` or `outputBinding` is true and\n`location` is valid, the implementation must read up to the first 64\nKiB of text from the file and place it in the \"contents\" field.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#Directory",
+    "type": "record",
+    "docAfter": "https://w3id.org/cwl/cwl#File",
+    "doc": "Represents a directory to present to a command line tool.\n\nDirectories are represented as objects with `class` of `Directory`.  Directory objects have\na number of properties that provide metadata about the directory.\n\nThe `location` property of a Directory is a URI that uniquely identifies\nthe directory.  Implementations must support the file:// URI scheme and may\nsupport other schemes such as http://.  Alternately to `location`,\nimplementations must also accept the `path` property on Directory, which\nmust be a filesystem path available on the same host as the CWL runner (for\ninputs) or the runtime environment of a command line tool execution (for\ncommand line tool outputs).\n\nA Directory object may have a `listing` field.  This is a list of File and\nDirectory objects that are contained in the Directory.  For each entry in\n`listing`, the `basename` property defines the name of the File or\nSubdirectory when staged to disk.  If `listing` is not provided, the\nimplementation must have some way of fetching the Directory listing at\nruntime based on the `location` field.\n\nIf a Directory does not have `location`, it is a Directory literal.  A\nDirectory literal must provide `listing`.  Directory literals must be\ncreated on disk at runtime as needed.\n\nThe resources in a Directory literal do not need to have any implied\nrelationship in their `location`.  For example, a Directory listing may\ncontain two files located on different hosts.  It is the responsibility of\nthe runtime to ensure that those files are staged to disk appropriately.\nSecondary files associated with files in `listing` must also be staged to\nthe same Directory.\n\nWhen executing a CommandLineTool, Directories must be recursively staged\nfirst and have local values of `path` assigend.\n\nDirectory objects in CommandLineTool output must provide either a\n`location` URI or a `path` property in the context of the tool execution\nruntime (local to the compute node, or within the executing container).\n\nAn ExpressionTool may forward file references from input to output by using\nthe same value for `location`.\n\nName conflicts (the same `basename` appearing multiple times in `listing`\nor in any entry in `secondaryFiles` in the listing) is a fatal error.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#Directory/class",
+        "type": {
+          "type": "enum",
+          "name": "https://w3id.org/cwl/cwl#Directory/class/Directory_class",
+          "symbols": [
             "https://w3id.org/cwl/cwl#Directory"
+          ]
+        },
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        },
+        "doc": "Must be `Directory` to indicate this object describes a Directory."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Directory/location",
+        "type": [
+          "null",
+          "string"
         ],
-        "doc": [
-            "Extends primitive types with the concept of a file and directory as a builtin type.",
-            "File: A File object",
-            "Directory: A Directory object"
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#File",
-        "type": "record",
-        "docParent": "https://w3id.org/cwl/cwl#CWLType",
-        "doc": "Represents a file (or group of files if `secondaryFiles` is specified) that\nmust be accessible by tools using standard POSIX file system call API such as\nopen(2) and read(2).\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#File/class",
-                "type": {
-                    "type": "enum",
-                    "name": "https://w3id.org/cwl/cwl#File/class/File_class",
-                    "symbols": [
-                        "https://w3id.org/cwl/cwl#File"
-                    ]
-                },
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                },
-                "doc": "Must be `File` to indicate this object describes a file."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/location",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "An IRI that identifies the file resource.  This may be a relative\nreference, in which case it must be resolved using the base IRI of the\ndocument.  The location may refer to a local or remote resource; the\nimplementation must use the IRI to retrieve file content.  If an\nimplementation is unable to retrieve the file content stored at a\nremote resource (due to unsupported protocol, access denied, or other\nissue) it must signal an error.\n\nIf the `location` field is not provided, the `contents` field must be\nprovided.  The implementation must assign a unique identifier for\nthe `location` field.\n\nIf the `path` field is provided but the `location` field is not, an\nimplementation may assign the value of the `path` field to `location`,\nthen follow the rules above.\n",
-                "jsonldPredicate": {
-                    "_id": "@id",
-                    "_type": "@id"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/path",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "The local host path where the File is available when a CommandLineTool is\nexecuted.  This field must be set by the implementation.  The final\npath component must match the value of `basename`.  This field\nmust not be used in any other context.  The command line tool being\nexecuted must be able to to access the file at `path` using the POSIX\n`open(2)` syscall.\n\nAs a special case, if the `path` field is provided but the `location`\nfield is not, an implementation may assign the value of the `path`\nfield to `location`, and remove the `path` field.\n\nIf the `path` contains [POSIX shell metacharacters](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02)\n(`|`,`&`, `;`, `<`, `>`, `(`,`)`, `$`,`` ` ``, `\\`, `\"`, `'`,\n`<space>`, `<tab>`, and `<newline>`) or characters\n[not allowed](http://www.iana.org/assignments/idna-tables-6.3.0/idna-tables-6.3.0.xhtml)\nfor [Internationalized Domain Names for Applications](https://tools.ietf.org/html/rfc6452)\nthen implementations may terminate the process with a\n`permanentFailure`.\n",
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#path",
-                    "_type": "@id"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/basename",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "The base name of the file, that is, the name of the file without any\nleading directory path.  The base name must not contain a slash `/`.\n\nIf not provided, the implementation must set this field based on the\n`location` field by taking the final path component after parsing\n`location` as an IRI.  If `basename` is provided, it is not required to\nmatch the value from `location`.\n\nWhen this file is made available to a CommandLineTool, it must be named\nwith `basename`, i.e. the final component of the `path` field must match\n`basename`.\n",
-                "jsonldPredicate": "cwl:basename"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/dirname",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "The name of the directory containing file, that is, the path leading up\nto the final slash in the path such that `dirname + '/' + basename ==\npath`.\n\nThe implementation must set this field based on the value of `path`\nprior to evaluating parameter references or expressions in a\nCommandLineTool document.  This field must not be used in any other\ncontext.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/nameroot",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "The basename root such that `nameroot + nameext == basename`, and\n`nameext` is empty or begins with a period and contains at most one\nperiod.  For the purposess of path splitting leading periods on the\nbasename are ignored; a basename of `.cshrc` will have a nameroot of\n`.cshrc`.\n\nThe implementation must set this field automatically based on the value\nof `basename` prior to evaluating parameter references or expressions.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/nameext",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "The basename extension such that `nameroot + nameext == basename`, and\n`nameext` is empty or begins with a period and contains at most one\nperiod.  Leading periods on the basename are ignored; a basename of\n`.cshrc` will have an empty `nameext`.\n\nThe implementation must set this field automatically based on the value\nof `basename` prior to evaluating parameter references or expressions.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/checksum",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "Optional hash code for validating file integrity.  Currently must be in the form\n\"sha1$ + hexadecimal string\" using the SHA-1 algorithm.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/size",
-                "type": [
-                    "null",
-                    "long"
-                ],
-                "doc": "Optional file size"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/secondaryFiles",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": [
-                            "https://w3id.org/cwl/cwl#File",
-                            "https://w3id.org/cwl/cwl#Directory"
-                        ]
-                    }
-                ],
-                "jsonldPredicate": "cwl:secondaryFiles",
-                "doc": "A list of additional files that are associated with the primary file\nand must be transferred alongside the primary file.  Examples include\nindexes of the primary file, or external references which must be\nincluded when loading primary document.  A file object listed in\n`secondaryFiles` may itself include `secondaryFiles` for which the same\nrules apply.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/format",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#format",
-                    "_type": "@id",
-                    "identity": true
-                },
-                "doc": "The format of the file: this must be an IRI of a concept node that\nrepresents the file format, preferrably defined within an ontology.\nIf no ontology is available, file formats may be tested by exact match.\n\nReasoning about format compatability must be done by checking that an\ninput file format is the same, `owl:equivalentClass` or\n`rdfs:subClassOf` the format required by the input parameter.\n`owl:equivalentClass` is transitive with `rdfs:subClassOf`, e.g. if\n`<B> owl:equivalentClass <C>` and `<B> owl:subclassOf <A>` then infer\n`<C> owl:subclassOf <A>`.\n\nFile format ontologies may be provided in the \"$schema\" metadata at the\nroot of the document.  If no ontologies are specified in `$schema`, the\nruntime may perform exact file format matches.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#File/contents",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "File contents literal.  Maximum of 64 KiB.\n\nIf neither `location` nor `path` is provided, `contents` must be\nnon-null.  The implementation must assign a unique identifier for the\n`location` field.  When the file is staged as input to CommandLineTool,\nthe value of `contents` must be written to a file.\n\nIf `loadContents` of `inputBinding` or `outputBinding` is true and\n`location` is valid, the implementation must read up to the first 64\nKiB of text from the file and place it in the \"contents\" field.\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#Directory",
-        "type": "record",
-        "docAfter": "https://w3id.org/cwl/cwl#File",
-        "doc": "Represents a directory to present to a command line tool.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#Directory/class",
-                "type": {
-                    "type": "enum",
-                    "name": "https://w3id.org/cwl/cwl#Directory/class/Directory_class",
-                    "symbols": [
-                        "https://w3id.org/cwl/cwl#Directory"
-                    ]
-                },
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                },
-                "doc": "Must be `Directory` to indicate this object describes a Directory."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Directory/location",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "An IRI that identifies the directory resource.  This may be a relative\nreference, in which case it must be resolved using the base IRI of the\ndocument.  The location may refer to a local or remote resource.  If\nthe `listing` field is not set, the implementation must use the\nlocation IRI to retrieve directory listing.  If an implementation is\nunable to retrieve the directory listing stored at a remote resource (due to\nunsupported protocol, access denied, or other issue) it must signal an\nerror.\n\nIf the `location` field is not provided, the `listing` field must be\nprovided.  The implementation must assign a unique identifier for\nthe `location` field.\n\nIf the `path` field is provided but the `location` field is not, an\nimplementation may assign the value of the `path` field to `location`,\nthen follow the rules above.\n",
-                "jsonldPredicate": {
-                    "_id": "@id",
-                    "_type": "@id"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Directory/path",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "The local path where the Directory is made available prior to executing a\nCommandLineTool.  This must be set by the implementation.  This field\nmust not be used in any other context.  The command line tool being\nexecuted must be able to to access the directory at `path` using the POSIX\n`opendir(2)` syscall.\n\nIf the `path` contains [POSIX shell metacharacters](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02)\n(`|`,`&`, `;`, `<`, `>`, `(`,`)`, `$`,`` ` ``, `\\`, `\"`, `'`,\n`<space>`, `<tab>`, and `<newline>`) or characters\n[not allowed](http://www.iana.org/assignments/idna-tables-6.3.0/idna-tables-6.3.0.xhtml)\nfor [Internationalized Domain Names for Applications](https://tools.ietf.org/html/rfc6452)\nthen implementations may terminate the process with a\n`permanentFailure`.\n",
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#path",
-                    "_type": "@id"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Directory/basename",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "The base name of the directory, that is, the name of the file without any\nleading directory path.  The base name must not contain a slash `/`.\n\nIf not provided, the implementation must set this field based on the\n`location` field by taking the final path component after parsing\n`location` as an IRI.  If `basename` is provided, it is not required to\nmatch the value from `location`.\n\nWhen this file is made available to a CommandLineTool, it must be named\nwith `basename`, i.e. the final component of the `path` field must match\n`basename`.\n",
-                "jsonldPredicate": "cwl:basename"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Directory/listing",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": [
-                            "https://w3id.org/cwl/cwl#File",
-                            "https://w3id.org/cwl/cwl#Directory"
-                        ]
-                    }
-                ],
-                "doc": "List of files or subdirectories contained in this directory.  The name\nof each file or subdirectory is determined by the `basename` field of\neach `File` or `Directory` object.  It is an error if a `File` shares a\n`basename` with any other entry in `listing`.  If two or more\n`Directory` object share the same `basename`, this must be treated as\nequivalent to a single subdirectory with the listings recursively\nmerged.\n",
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#listing"
-                }
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#SchemaBase",
-        "type": "record",
-        "abstract": true,
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#SchemaBase/label",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "jsonldPredicate": "rdfs:label",
-                "doc": "A short, human-readable label of this object."
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#Parameter",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#SchemaBase",
-        "abstract": true,
-        "doc": "Define an input or output parameter to a process.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#Parameter/secondaryFiles",
-                "type": [
-                    "null",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression",
-                    {
-                        "type": "array",
-                        "items": [
-                            "string",
-                            "https://w3id.org/cwl/cwl#Expression"
-                        ]
-                    }
-                ],
-                "jsonldPredicate": "cwl:secondaryFiles",
-                "doc": "Only valid when `type: File` or is an array of `items: File`.\n\nDescribes files that must be included alongside the primary file(s).\n\nIf the value is an expression, the value of `self` in the expression\nmust be the primary input or output File to which this binding applies.\n\nIf the value is a string, it specifies that the following pattern\nshould be applied to the primary file:\n\n  1. If string begins with one or more caret `^` characters, for each\n    caret, remove the last file extension from the path (the last\n    period `.` and all following characters).  If there are no file\n    extensions, the path is unchanged.\n  2. Append the remainder of the string to the end of the file path.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Parameter/format",
-                "type": [
-                    "null",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": "string"
-                    },
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#format",
-                    "_type": "@id",
-                    "identity": true
-                },
-                "doc": "Only valid when `type: File` or is an array of `items: File`.\n\nFor input parameters, this must be one or more IRIs of concept nodes\nthat represents file formats which are allowed as input to this\nparameter, preferrably defined within an ontology.  If no ontology is\navailable, file formats may be tested by exact match.\n\nFor output parameters, this is the file format that will be assigned to\nthe output parameter.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Parameter/streamable",
-                "type": [
-                    "null",
-                    "boolean"
-                ],
-                "doc": "Only valid when `type: File` or is an array of `items: File`.\n\nA value of `true` indicates that the file is read or written\nsequentially without seeking.  An implementation may use this flag to\nindicate whether it is valid to stream file contents using a named\npipe.  Default: `false`.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Parameter/doc",
-                "type": [
-                    "null",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": "string"
-                    }
-                ],
-                "doc": "A documentation string for this type, or an array of strings which should be concatenated.",
-                "jsonldPredicate": "rdfs:comment"
-            }
-        ]
-    },
-    {
-        "type": "enum",
-        "name": "https://w3id.org/cwl/cwl#Expression",
-        "doc": "'Expression' is not a real type.  It indicates that a field must allow\nruntime parameter references.  If [InlineJavascriptRequirement](#InlineJavascriptRequirement)\nis declared and supported by the platform, the field must also allow\nJavascript expressions.\n",
-        "symbols": [
-            "https://w3id.org/cwl/cwl#ExpressionPlaceholder"
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#InputBinding",
-        "type": "record",
-        "abstract": true,
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#InputBinding/loadContents",
-                "type": [
-                    "null",
-                    "boolean"
-                ],
-                "jsonldPredicate": "cwl:loadContents",
-                "doc": "Only valid when `type: File` or is an array of `items: File`.\n\nRead up to the first 64 KiB of text from the file and place it in the\n\"contents\" field of the file object for use by expressions.\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#OutputBinding",
-        "type": "record",
-        "abstract": true
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#InputSchema",
-        "extends": "https://w3id.org/cwl/cwl#SchemaBase",
-        "type": "record",
-        "abstract": true
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#OutputSchema",
-        "extends": "https://w3id.org/cwl/cwl#SchemaBase",
-        "type": "record",
-        "abstract": true
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#InputRecordField",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/salad#RecordField",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#RecordSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#InputRecordSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#EnumSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#InputEnumSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#ArraySchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#InputArraySchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#PrimitiveType",
-                "specializeTo": "https://w3id.org/cwl/cwl#CWLType"
-            }
-        ],
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#InputRecordField/inputBinding",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#InputBinding"
-                ],
-                "jsonldPredicate": "cwl:inputBinding"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#InputRecordField/label",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "jsonldPredicate": "rdfs:label",
-                "doc": "A short, human-readable label of this process object."
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#InputRecordSchema",
-        "type": "record",
-        "extends": [
-            "https://w3id.org/cwl/salad#RecordSchema",
-            "https://w3id.org/cwl/cwl#InputSchema"
-        ],
-        "fields": [
+        "doc": "An IRI that identifies the directory resource.  This may be a relative\nreference, in which case it must be resolved using the base IRI of the\ndocument.  The location may refer to a local or remote resource.  If\nthe `listing` field is not set, the implementation must use the\nlocation IRI to retrieve directory listing.  If an implementation is\nunable to retrieve the directory listing stored at a remote resource (due to\nunsupported protocol, access denied, or other issue) it must signal an\nerror.\n\nIf the `location` field is not provided, the `listing` field must be\nprovided.  The implementation must assign a unique identifier for\nthe `location` field.\n\nIf the `path` field is provided but the `location` field is not, an\nimplementation may assign the value of the `path` field to `location`,\nthen follow the rules above.\n",
+        "jsonldPredicate": {
+          "_id": "@id",
+          "_type": "@id"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Directory/path",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "The local path where the Directory is made available prior to executing a\nCommandLineTool.  This must be set by the implementation.  This field\nmust not be used in any other context.  The command line tool being\nexecuted must be able to to access the directory at `path` using the POSIX\n`opendir(2)` syscall.\n\nIf the `path` contains [POSIX shell metacharacters](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02)\n(`|`,`&`, `;`, `<`, `>`, `(`,`)`, `$`,`` ` ``, `\\`, `\"`, `'`,\n`<space>`, `<tab>`, and `<newline>`) or characters\n[not allowed](http://www.iana.org/assignments/idna-tables-6.3.0/idna-tables-6.3.0.xhtml)\nfor [Internationalized Domain Names for Applications](https://tools.ietf.org/html/rfc6452)\nthen implementations may terminate the process with a\n`permanentFailure`.\n",
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#path",
+          "_type": "@id"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Directory/basename",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "The base name of the directory, that is, the name of the file without any\nleading directory path.  The base name must not contain a slash `/`.\n\nIf not provided, the implementation must set this field based on the\n`location` field by taking the final path component after parsing\n`location` as an IRI.  If `basename` is provided, it is not required to\nmatch the value from `location`.\n\nWhen this file is made available to a CommandLineTool, it must be named\nwith `basename`, i.e. the final component of the `path` field must match\n`basename`.\n",
+        "jsonldPredicate": "cwl:basename"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Directory/listing",
+        "type": [
+          "null",
           {
-            "jsonldPredicate": "@id",
-            "name": "https://w3id.org/cwl/cwl#InputRecordSchema/name",
-            "type": [
-              "null",
+            "type": "array",
+            "items": [
+              "https://w3id.org/cwl/cwl#File",
+              "https://w3id.org/cwl/cwl#Directory"
+            ]
+          }
+        ],
+        "doc": "List of files or subdirectories contained in this directory.  The name\nof each file or subdirectory is determined by the `basename` field of\neach `File` or `Directory` object.  It is an error if a `File` shares a\n`basename` with any other entry in `listing`.  If two or more\n`Directory` object share the same `basename`, this must be treated as\nequivalent to a single subdirectory with the listings recursively\nmerged.\n",
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#listing"
+        }
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CWLObjectType",
+    "type": "union",
+    "names": [
+      "boolean",
+      "int",
+      "long",
+      "float",
+      "double",
+      "string",
+      "https://w3id.org/cwl/cwl#File",
+      "https://w3id.org/cwl/cwl#Directory",
+      {
+        "items": [
+          "null",
+          "https://w3id.org/cwl/cwl#CWLObjectType"
+        ],
+        "type": "array"
+      },
+      {
+        "type": "map",
+        "values": [
+          "null",
+          "https://w3id.org/cwl/cwl#CWLObjectType"
+        ]
+      }
+    ],
+    "doc": "Generic type representing a valid CWL object. It is used to represent\n`default` values passed to CWL `InputParameter` and `WorkflowStepInput`\nrecord fields.\n"
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CWLInputFile",
+    "type": "map",
+    "values": [
+      "null",
+      "https://w3id.org/cwl/cwl#CWLObjectType"
+    ],
+    "doc": "Type representing a valid CWL input file as a `map<string, CWLObjectType>`.\n",
+    "jsonldPredicate": {
+      "_id": "https://w3id.org/cwl/cwl#inputfile",
+      "_container": "@list",
+      "noLinkCheck": true
+    }
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#BaseTypesDoc",
+    "type": "documentation",
+    "doc": "## Base types\n",
+    "docChild": [
+      "https://w3id.org/cwl/cwl#CWLType",
+      "https://w3id.org/cwl/cwl#Process"
+    ]
+  },
+  {
+    "type": "enum",
+    "name": "https://w3id.org/cwl/cwl#CWLVersion",
+    "doc": "Version symbols for published CWL document versions.",
+    "symbols": [
+      "https://w3id.org/cwl/cwl#draft-2",
+      "https://w3id.org/cwl/cwl#draft-3.dev1",
+      "https://w3id.org/cwl/cwl#draft-3.dev2",
+      "https://w3id.org/cwl/cwl#draft-3.dev3",
+      "https://w3id.org/cwl/cwl#draft-3.dev4",
+      "https://w3id.org/cwl/cwl#draft-3.dev5",
+      "https://w3id.org/cwl/cwl#draft-3",
+      "https://w3id.org/cwl/cwl#draft-4.dev1",
+      "https://w3id.org/cwl/cwl#draft-4.dev2",
+      "https://w3id.org/cwl/cwl#draft-4.dev3",
+      "https://w3id.org/cwl/cwl#v1.0.dev4",
+      "https://w3id.org/cwl/cwl#v1.0"
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#SchemaBase",
+    "type": "record",
+    "abstract": true,
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#SchemaBase/label",
+        "type": [
+          "null",
+          "string"
+        ],
+        "jsonldPredicate": "rdfs:label",
+        "doc": "A short, human-readable label of this object."
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#Parameter",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#SchemaBase",
+    "abstract": true,
+    "doc": "Define an input or output parameter to a process.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#Parameter/secondaryFiles",
+        "type": [
+          "null",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression",
+          {
+            "type": "array",
+            "items": [
+              "string",
+              "https://w3id.org/cwl/cwl#Expression"
+            ]
+          }
+        ],
+        "jsonldPredicate": "cwl:secondaryFiles",
+        "doc": "Only valid when `type: File` or is an array of `items: File`.\n\nProvides a pattern or expression specifying files or directories that\nmust be included alongside the primary file.  All listed secondary\nfiles must be present.  An implementation may fail workflow execution\nif an expected secondary file does not exist.\n\nIf the value is an expression, the value of `self` in the expression\nmust be the primary input or output File object to which this binding\napplies.  The `basename`, `nameroot` and `nameext` fields must be\npresent in `self`.  For `CommandLineTool` outputs the `path` field must\nalso be present.  The expression must return a filename string relative\nto the path to the primary File, a File or Directory object with either\n`path` or `location` and `basename` fields set, or an array consisting\nof strings or File or Directory objects.  It is legal to reference an\nunchanged File or Directory object taken from input as a secondaryFile.\n\nTo work on non-filename-preserving storage systems, portable tool\ndescriptions should avoid constructing new values from `location`, but\nshould construct relative references using `basename` or `nameroot`\ninstead.\n\nIf a value in `secondaryFiles` is a string that is not an expression,\nit specifies that the following pattern should be applied to the path\nof the primary file to yield a filename relative to the primary File:\n\n  1. If string begins with one or more caret `^` characters, for each\n    caret, remove the last file extension from the path (the last\n    period `.` and all following characters).  If there are no file\n    extensions, the path is unchanged.\n  2. Append the remainder of the string to the end of the file path.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Parameter/streamable",
+        "type": [
+          "null",
+          "boolean"
+        ],
+        "default": false,
+        "doc": "Only valid when `type: File` or is an array of `items: File`.\n\nA value of `true` indicates that the file is read or written\nsequentially without seeking.  An implementation may use this flag to\nindicate whether it is valid to stream file contents using a named\npipe.  Default: `false`.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Parameter/doc",
+        "type": [
+          "null",
+          "string",
+          {
+            "type": "array",
+            "items": "string"
+          }
+        ],
+        "doc": "A documentation string for this type, or an array of strings which should be concatenated.",
+        "jsonldPredicate": "rdfs:comment"
+      }
+    ]
+  },
+  {
+    "type": "enum",
+    "name": "https://w3id.org/cwl/cwl#Expression",
+    "doc": "'Expression' is not a real type.  It indicates that a field must allow\nruntime parameter references.  If [InlineJavascriptRequirement](#InlineJavascriptRequirement)\nis declared and supported by the platform, the field must also allow\nJavascript expressions.\n",
+    "symbols": [
+      "https://w3id.org/cwl/cwl#ExpressionPlaceholder"
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#InputBinding",
+    "type": "record",
+    "abstract": true,
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#InputBinding/loadContents",
+        "type": [
+          "null",
+          "boolean"
+        ],
+        "jsonldPredicate": "cwl:loadContents",
+        "doc": "Only valid when `type: File` or is an array of `items: File`.\n\nRead up to the first 64 KiB of text from the file and place it in the\n\"contents\" field of the file object for use by expressions.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#OutputBinding",
+    "type": "record",
+    "abstract": true
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#InputSchema",
+    "extends": "https://w3id.org/cwl/cwl#SchemaBase",
+    "type": "record",
+    "abstract": true
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#OutputSchema",
+    "extends": "https://w3id.org/cwl/cwl#SchemaBase",
+    "type": "record",
+    "abstract": true
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#InputRecordField",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#CWLRecordField",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#CWLRecordSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#InputRecordSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/salad#EnumSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#InputEnumSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#CWLArraySchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#InputArraySchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/salad#PrimitiveType",
+        "specializeTo": "https://w3id.org/cwl/cwl#CWLType"
+      }
+    ],
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#InputRecordField/inputBinding",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#InputBinding"
+        ],
+        "jsonldPredicate": "cwl:inputBinding"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#InputRecordField/label",
+        "type": [
+          "null",
+          "string"
+        ],
+        "jsonldPredicate": "rdfs:label",
+        "doc": "A short, human-readable label of this process object."
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#InputRecordSchema",
+    "type": "record",
+    "extends": [
+      "https://w3id.org/cwl/cwl#CWLRecordSchema",
+      "https://w3id.org/cwl/cwl#InputSchema"
+    ],
+    "fields": [
+      {
+        "jsonldPredicate": "@id",
+        "name": "https://w3id.org/cwl/cwl#InputRecordSchema/name",
+        "type": [
+          "null",
+          "string"
+        ]
+      }
+    ],
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#CWLRecordField",
+        "specializeTo": "https://w3id.org/cwl/cwl#InputRecordField"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#InputEnumSchema",
+    "type": "record",
+    "extends": [
+      "https://w3id.org/cwl/salad#EnumSchema",
+      "https://w3id.org/cwl/cwl#InputSchema"
+    ],
+    "fields": [
+      {
+        "jsonldPredicate": "@id",
+        "name": "https://w3id.org/cwl/cwl#InputEnumSchema/name",
+        "type": [
+          "null",
+          "string"
+        ]
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#InputEnumSchema/inputBinding",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#InputBinding"
+        ],
+        "jsonldPredicate": "cwl:inputBinding"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#InputArraySchema",
+    "type": "record",
+    "extends": [
+      "https://w3id.org/cwl/cwl#CWLArraySchema",
+      "https://w3id.org/cwl/cwl#InputSchema"
+    ],
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#CWLRecordSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#InputRecordSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/salad#EnumSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#InputEnumSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#CWLArraySchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#InputArraySchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/salad#PrimitiveType",
+        "specializeTo": "https://w3id.org/cwl/cwl#CWLType"
+      }
+    ],
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#InputArraySchema/inputBinding",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#InputBinding"
+        ],
+        "jsonldPredicate": "cwl:inputBinding"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#OutputRecordField",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#CWLRecordField",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#CWLRecordSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#OutputRecordSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/salad#EnumSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#OutputEnumSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#CWLArraySchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#OutputArraySchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/salad#PrimitiveType",
+        "specializeTo": "https://w3id.org/cwl/cwl#CWLType"
+      }
+    ],
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#OutputRecordField/outputBinding",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#OutputBinding"
+        ],
+        "jsonldPredicate": "cwl:outputBinding"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#OutputRecordSchema",
+    "type": "record",
+    "extends": [
+      "https://w3id.org/cwl/cwl#CWLRecordSchema",
+      "https://w3id.org/cwl/cwl#OutputSchema"
+    ],
+    "docParent": "https://w3id.org/cwl/cwl#OutputParameter",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#CWLRecordField",
+        "specializeTo": "https://w3id.org/cwl/cwl#OutputRecordField"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#OutputEnumSchema",
+    "type": "record",
+    "extends": [
+      "https://w3id.org/cwl/salad#EnumSchema",
+      "https://w3id.org/cwl/cwl#OutputSchema"
+    ],
+    "docParent": "https://w3id.org/cwl/cwl#OutputParameter",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#OutputEnumSchema/outputBinding",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#OutputBinding"
+        ],
+        "jsonldPredicate": "cwl:outputBinding"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#OutputArraySchema",
+    "type": "record",
+    "extends": [
+      "https://w3id.org/cwl/cwl#CWLArraySchema",
+      "https://w3id.org/cwl/cwl#OutputSchema"
+    ],
+    "docParent": "https://w3id.org/cwl/cwl#OutputParameter",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#CWLRecordSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#OutputRecordSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/salad#EnumSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#OutputEnumSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#CWLArraySchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#OutputArraySchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/salad#PrimitiveType",
+        "specializeTo": "https://w3id.org/cwl/cwl#CWLType"
+      }
+    ],
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#OutputArraySchema/outputBinding",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#OutputBinding"
+        ],
+        "jsonldPredicate": "cwl:outputBinding"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#InputParameter",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#Parameter",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#InputParameter/id",
+        "type": "string",
+        "jsonldPredicate": "@id",
+        "doc": "The unique identifier for this parameter object."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#InputParameter/format",
+        "type": [
+          "null",
+          "string",
+          {
+            "type": "array",
+            "items": "string"
+          },
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#format",
+          "_type": "@id",
+          "identity": true,
+          "noLinkCheck": true
+        },
+        "doc": "Only valid when `type: File` or is an array of `items: File`.\n\nThis must be one or more IRIs of concept nodes\nthat represents file formats which are allowed as input to this\nparameter, preferrably defined within an ontology.  If no ontology is\navailable, file formats may be tested by exact match.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#InputParameter/inputBinding",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#InputBinding"
+        ],
+        "jsonldPredicate": "cwl:inputBinding",
+        "doc": "Describes how to handle the inputs of a process and convert them\ninto a concrete form for execution, such as command line parameters.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#InputParameter/default",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#CWLObjectType"
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#default",
+          "_container": "@list",
+          "noLinkCheck": true
+        },
+        "doc": "The default value to use for this parameter if the parameter is missing\nfrom the input object, or if the value of the parameter in the input\nobject is `null`.  Default values are applied before evaluating expressions\n(e.g. dependent `valueFrom` fields).\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#InputParameter/type",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#CWLType",
+          "https://w3id.org/cwl/cwl#InputRecordSchema",
+          "https://w3id.org/cwl/cwl#InputEnumSchema",
+          "https://w3id.org/cwl/cwl#InputArraySchema",
+          "string",
+          {
+            "type": "array",
+            "items": [
+              "https://w3id.org/cwl/cwl#CWLType",
+              "https://w3id.org/cwl/cwl#InputRecordSchema",
+              "https://w3id.org/cwl/cwl#InputEnumSchema",
+              "https://w3id.org/cwl/cwl#InputArraySchema",
               "string"
             ]
           }
         ],
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#RecordField",
-                "specializeTo": "https://w3id.org/cwl/cwl#InputRecordField"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#InputEnumSchema",
-        "type": "record",
-        "extends": [
-            "https://w3id.org/cwl/salad#EnumSchema",
-            "https://w3id.org/cwl/cwl#InputSchema"
-        ],
-        "fields": [
-            {
-                "jsonldPredicate": "@id",
-                "name": "https://w3id.org/cwl/cwl#InputEnumSchema/name",
-                "type": [
-                  "null",
-                  "string"
-                ]
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#InputEnumSchema/inputBinding",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#InputBinding"
-                ],
-                "jsonldPredicate": "cwl:inputBinding"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#InputArraySchema",
-        "type": "record",
-        "extends": [
-            "https://w3id.org/cwl/salad#ArraySchema",
-            "https://w3id.org/cwl/cwl#InputSchema"
-        ],
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#RecordSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#InputRecordSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#EnumSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#InputEnumSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#ArraySchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#InputArraySchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#PrimitiveType",
-                "specializeTo": "https://w3id.org/cwl/cwl#CWLType"
-            }
-        ],
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#InputArraySchema/inputBinding",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#InputBinding"
-                ],
-                "jsonldPredicate": "cwl:inputBinding"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#OutputRecordField",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/salad#RecordField",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#RecordSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#OutputRecordSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#EnumSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#OutputEnumSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#ArraySchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#OutputArraySchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#PrimitiveType",
-                "specializeTo": "https://w3id.org/cwl/cwl#CWLType"
-            }
-        ],
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#OutputRecordField/outputBinding",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#OutputBinding"
-                ],
-                "jsonldPredicate": "cwl:outputBinding"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#OutputRecordSchema",
-        "type": "record",
-        "extends": [
-            "https://w3id.org/cwl/salad#RecordSchema",
-            "https://w3id.org/cwl/cwl#OutputSchema"
-        ],
-        "docParent": "https://w3id.org/cwl/cwl#OutputParameter",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#RecordField",
-                "specializeTo": "https://w3id.org/cwl/cwl#OutputRecordField"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#OutputEnumSchema",
-        "type": "record",
-        "extends": [
-            "https://w3id.org/cwl/salad#EnumSchema",
-            "https://w3id.org/cwl/cwl#OutputSchema"
-        ],
-        "docParent": "https://w3id.org/cwl/cwl#OutputParameter",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#OutputEnumSchema/outputBinding",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#OutputBinding"
-                ],
-                "jsonldPredicate": "cwl:outputBinding"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#OutputArraySchema",
-        "type": "record",
-        "extends": [
-            "https://w3id.org/cwl/salad#ArraySchema",
-            "https://w3id.org/cwl/cwl#OutputSchema"
-        ],
-        "docParent": "https://w3id.org/cwl/cwl#OutputParameter",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#RecordSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#OutputRecordSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#EnumSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#OutputEnumSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#ArraySchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#OutputArraySchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/salad#PrimitiveType",
-                "specializeTo": "https://w3id.org/cwl/cwl#CWLType"
-            }
-        ],
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#OutputArraySchema/outputBinding",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#OutputBinding"
-                ],
-                "jsonldPredicate": "cwl:outputBinding"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#InputParameter",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#Parameter",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#InputParameter/id",
-                "type": "string",
-                "jsonldPredicate": "@id",
-                "doc": "The unique identifier for this parameter object."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#InputParameter/inputBinding",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#InputBinding"
-                ],
-                "jsonldPredicate": "cwl:inputBinding",
-                "doc": "Describes how to handle the inputs of a process and convert them\ninto a concrete form for execution, such as command line parameters.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#InputParameter/default",
-                "type": [
-                    "null",
-                    "Any"
-                ],
-                "jsonldPredicate": "cwl:default",
-                "doc": "The default value for this parameter if not provided in the input\nobject.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#InputParameter/type",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#CWLType",
-                    "https://w3id.org/cwl/cwl#InputRecordSchema",
-                    "https://w3id.org/cwl/cwl#InputEnumSchema",
-                    "https://w3id.org/cwl/cwl#InputArraySchema",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": [
-                            "https://w3id.org/cwl/cwl#CWLType",
-                            "https://w3id.org/cwl/cwl#InputRecordSchema",
-                            "https://w3id.org/cwl/cwl#InputEnumSchema",
-                            "https://w3id.org/cwl/cwl#InputArraySchema",
-                            "string"
-                        ]
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/salad#type",
-                    "_type": "@vocab",
-                    "refScope": 2,
-                    "typeDSL": true
-                },
-                "doc": "Specify valid types of data that may be assigned to this parameter.\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#OutputParameter",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#Parameter",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#OutputParameter/id",
-                "type": "string",
-                "jsonldPredicate": "@id",
-                "doc": "The unique identifier for this parameter object."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#OutputParameter/outputBinding",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#OutputBinding"
-                ],
-                "jsonldPredicate": "cwl:outputBinding",
-                "doc": "Describes how to handle the outputs of a process.\n"
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "abstract": true,
-        "doc": "A process requirement declares a prerequisite that may or must be fulfilled\nbefore executing a process.  See [`Process.hints`](#process) and\n[`Process.requirements`](#process).\n\nProcess requirements are the primary mechanism for specifying extensions to\nthe CWL core specification.\n"
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#Process",
-        "abstract": true,
-        "doc": "\nThe base executable type in CWL is the `Process` object defined by the\ndocument.  Note that the `Process` object is abstract and cannot be\ndirectly executed.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#Process/id",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "jsonldPredicate": "@id",
-                "doc": "The unique identifier for this process object."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Process/inputs",
-                "type": {
-                    "type": "array",
-                    "items": "https://w3id.org/cwl/cwl#InputParameter"
-                },
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#inputs",
-                    "mapSubject": "id",
-                    "mapPredicate": "type"
-                },
-                "doc": "Defines the input parameters of the process.  The process is ready to\nrun when all required input parameters are associated with concrete\nvalues.  Input parameters include a schema for each parameter which is\nused to validate the input object.  It may also be used to build a user\ninterface for constructing the input object.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Process/outputs",
-                "type": {
-                    "type": "array",
-                    "items": "https://w3id.org/cwl/cwl#OutputParameter"
-                },
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#outputs",
-                    "mapSubject": "id",
-                    "mapPredicate": "type"
-                },
-                "doc": "Defines the parameters representing the output of the process.  May be\nused to generate and/or validate the output object.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Process/requirements",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "https://w3id.org/cwl/cwl#ProcessRequirement"
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#requirements",
-                    "mapSubject": "class"
-                },
-                "doc": "Declares requirements that apply to either the runtime environment or the\nworkflow engine that must be met in order to execute this process.  If\nan implementation cannot satisfy all requirements, or a requirement is\nlisted which is not recognized by the implementation, it is a fatal\nerror and the implementation must not attempt to run the process,\nunless overridden at user option.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Process/hints",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "Any"
-                    }
-                ],
-                "doc": "Declares hints applying to either the runtime environment or the\nworkflow engine that may be helpful in executing this process.  It is\nnot an error if an implementation cannot satisfy all hints, however\nthe implementation may report a warning.\n",
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#hints",
-                    "noLinkCheck": true,
-                    "mapSubject": "class"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Process/label",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "jsonldPredicate": "rdfs:label",
-                "doc": "A short, human-readable label of this process object."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Process/doc",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "jsonldPredicate": "rdfs:comment",
-                "doc": "A long, human-readable description of this process object."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Process/cwlVersion",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#CWLVersion"
-                ],
-                "doc": "CWL document version. Always required at the document root. Not\nrequired for a Process embedded inside another Process.\n",
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#cwlVersion",
-                    "_type": "@vocab"
-                }
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#InlineJavascriptRequirement",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "Indicates that the workflow platform must support inline Javascript expressions.\nIf this requirement is not present, the workflow platform must not perform expression\ninterpolatation.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#InlineJavascriptRequirement/class",
-                "type": "string",
-                "doc": "Always 'InlineJavascriptRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#InlineJavascriptRequirement/expressionLib",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "string"
-                    }
-                ],
-                "doc": "Additional code fragments that will also be inserted\nbefore executing the expression code.  Allows for function definitions that may\nbe called from CWL expressions.\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#SchemaDefRequirement",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "This field consists of an array of type definitions which must be used when\ninterpreting the `inputs` and `outputs` fields.  When a `type` field\ncontain a IRI, the implementation must check if the type is defined in\n`schemaDefs` and use that definition.  If the type is not found in\n`schemaDefs`, it is an error.  The entries in `schemaDefs` must be\nprocessed in the order listed such that later schema definitions may refer\nto earlier schema definitions.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#SchemaDefRequirement/class",
-                "type": "string",
-                "doc": "Always 'SchemaDefRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#SchemaDefRequirement/types",
-                "type": {
-                    "type": "array",
-                    "items": "https://w3id.org/cwl/cwl#InputSchema"
-                },
-                "doc": "The list of type definitions."
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#CommandLineToolDoc",
-        "type": "documentation",
-        "doc": [
-            "# Common Workflow Language (CWL) Command Line Tool Description, v1.0\n\nThis version:\n  * https://w3id.org/cwl/v1.0/\n\nCurrent version:\n  * https://w3id.org/cwl/\n",
-            "\n\n",
-            "\n",
-            "\n\n",
-            "# Abstract\n\nA Command Line Tool is a non-interactive executable program that reads\nsome input, performs a computation, and terminates after producing some\noutput.  Command line programs are a flexible unit of code sharing and\nreuse, unfortunately the syntax and input/output semantics among command\nline programs is extremely heterogeneous. A common layer for describing\nthe syntax and semantics of programs can reduce this incidental\ncomplexity by providing a consistent way to connect programs together.\nThis specification defines the Common Workflow Language (CWL) Command\nLine Tool Description, a vendor-neutral standard for describing the\nsyntax and input/output semantics of command line programs.\n",
-            "\n",
-            "## Introduction to v1.0\n\nThis specification represents the first full release from the CWL group.\nSince draft-3, version 1.0 introduces the following changes and additions:\n\n  * The [Directory](#Directory) type.\n  * Syntax simplifcations: denoted by the `map<>` syntax. Example: inputs\n    contains a list of items, each with an id. Now one can specify\n    a mapping of that identifier to the corresponding\n    `CommandInputParamater`.\n    ```\n    inputs:\n     - id: one\n       type: string\n       doc: First input parameter\n     - id: two\n       type: int\n       doc: Second input parameter\n    ```\n    can be\n    ```\n    inputs:\n     one:\n      type: string\n      doc: First input parameter\n     two:\n      type: int\n      doc: Second input parameter\n    ```\n  * [InitialWorkDirRequirement](#InitialWorkDirRequirement): list of\n    files and subdirectories to be present in the output directory prior\n    to execution.\n  * Shortcuts for specifying the standard [output](#stdout) and/or\n    [error](#stderr) streams as a (streamable) File output.\n  * [SoftwareRequirement](#SoftwareRequirement) for describing software\n    dependencies of a tool.\n  * The common `description` field has been renamed to `doc`.\n\n## Errata\n\nPost v1.0 release changes to the spec.\n\n  * 13 July 2016: Mark `baseCommand` as optional and update descriptive text.\n\n## Purpose\n\nStandalone programs are a flexible and interoperable form of code reuse.\nUnlike monolithic applications, applications and analysis workflows which\nare composed of multiple separate programs can be written in multiple\nlanguages and execute concurrently on multiple hosts.  However, POSIX\ndoes not dictate computer-readable grammar or semantics for program input\nand output, resulting in extremely heterogeneous command line grammar and\ninput/output semantics among program.  This is a particular problem in\ndistributed computing (multi-node compute clusters) and virtualized\nenvironments (such as Docker containers) where it is often necessary to\nprovision resources such as input files before executing the program.\n\nOften this gap is filled by hard coding program invocation and\nimplicitly assuming requirements will be met, or abstracting program\ninvocation with wrapper scripts or descriptor documents.  Unfortunately,\nwhere these approaches are application or platform specific it creates a\nsignificant barrier to reproducibility and portability, as methods\ndeveloped for one platform must be manually ported to be used on new\nplatforms.  Similarly it creates redundant work, as wrappers for popular\ntools must be rewritten for each application or platform in use.\n\nThe Common Workflow Language Command Line Tool Description is designed to\nprovide a common standard description of grammar and semantics for\ninvoking programs used in data-intensive fields such as Bioinformatics,\nChemistry, Physics, Astronomy, and Statistics.  This specification\ndefines a precise data and execution model for Command Line Tools that\ncan be implemented on a variety of computing platforms, ranging from a\nsingle workstation to cluster, grid, cloud, and high performance\ncomputing platforms.\n",
-            "\n",
-            "\n"
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#EnvironmentDef",
-        "doc": "Define an environment variable that will be set in the runtime environment\nby the workflow platform when executing the command line tool.  May be the\nresult of executing an expression, such as getting a parameter from input.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#EnvironmentDef/envName",
-                "type": "string",
-                "doc": "The environment variable name"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#EnvironmentDef/envValue",
-                "type": [
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "The environment variable value"
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#CommandLineBinding",
-        "extends": "https://w3id.org/cwl/cwl#InputBinding",
-        "doc": "\nWhen listed under `inputBinding` in the input schema, the term\n\"value\" refers to the the corresponding value in the input object.  For\nbinding objects listed in `CommandLineTool.arguments`, the term \"value\"\nrefers to the effective value after evaluating `valueFrom`.\n\nThe binding behavior when building the command line depends on the data\ntype of the value.  If there is a mismatch between the type described by\nthe input schema and the effective value, such as resulting from an\nexpression evaluation, an implementation must use the data type of the\neffective value.\n\n  - **string**: Add `prefix` and the string to the command line.\n\n  - **number**: Add `prefix` and decimal representation to command line.\n\n  - **boolean**: If true, add `prefix` to the command line.  If false, add\n      nothing.\n\n  - **File**: Add `prefix` and the value of\n    [`File.path`](#File) to the command line.\n\n  - **array**: If `itemSeparator` is specified, add `prefix` and the join\n      the array into a single string with `itemSeparator` separating the\n      items.  Otherwise first add `prefix`, then recursively process\n      individual elements.\n\n  - **object**: Add `prefix` only, and recursively add object fields for\n      which `inputBinding` is specified.\n\n  - **null**: Add nothing.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineBinding/position",
-                "type": [
-                    "null",
-                    "int"
-                ],
-                "doc": "The sorting key.  Default position is 0."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineBinding/prefix",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "Command line prefix to add before the value."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineBinding/separate",
-                "type": [
-                    "null",
-                    "boolean"
-                ],
-                "doc": "If true (default), then the prefix and value must be added as separate\ncommand line arguments; if false, prefix and value must be concatenated\ninto a single command line argument.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineBinding/itemSeparator",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "Join the array elements into a single string with the elements\nseparated by by `itemSeparator`.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineBinding/valueFrom",
-                "type": [
-                    "null",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "jsonldPredicate": "cwl:valueFrom",
-                "doc": "If `valueFrom` is a constant string value, use this as the value and\napply the binding rules above.\n\nIf `valueFrom` is an expression, evaluate the expression to yield the\nactual value to use to build the command line and apply the binding\nrules above.  If the inputBinding is associated with an input\nparameter, the value of `self` in the expression will be the value of the\ninput parameter.\n\nWhen a binding is part of the `CommandLineTool.arguments` field,\nthe `valueFrom` field is required.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineBinding/shellQuote",
-                "type": [
-                    "null",
-                    "boolean"
-                ],
-                "doc": "If `ShellCommandRequirement` is in the requirements for the current command,\nthis controls whether the value is quoted on the command line (default is true).\nUse `shellQuote: false` to inject metacharacters for operations such as pipes.\n"
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#CommandOutputBinding",
-        "extends": "https://w3id.org/cwl/cwl#OutputBinding",
-        "doc": "Describes how to generate an output parameter based on the files produced\nby a CommandLineTool.\n\nThe output parameter is generated by applying these operations in\nthe following order:\n\n  - glob\n  - loadContents\n  - outputEval\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandOutputBinding/glob",
-                "type": [
-                    "null",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression",
-                    {
-                        "type": "array",
-                        "items": "string"
-                    }
-                ],
-                "doc": "Find files relative to the output directory, using POSIX glob(3)\npathname matching.  If an array is provided, find files that match any\npattern in the array.  If an expression is provided, the expression must\nreturn a string or an array of strings, which will then be evaluated as\none or more glob patterns.  Must only match and return files which\nactually exist.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandOutputBinding/loadContents",
-                "type": [
-                    "null",
-                    "boolean"
-                ],
-                "jsonldPredicate": "cwl:loadContents",
-                "doc": "For each file matched in `glob`, read up to\nthe first 64 KiB of text from the file and place it in the `contents`\nfield of the file object for manipulation by `outputEval`.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandOutputBinding/outputEval",
-                "type": [
-                    "null",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "Evaluate an expression to generate the output value.  If `glob` was\nspecified, the value of `self` must be an array containing file objects\nthat were matched.  If no files were matched, `self` must be a zero\nlength array; if a single file was matched, the value of `self` is an\narray of a single element.  Additionally, if `loadContents` is `true`,\nthe File objects must include up to the first 64 KiB of file contents\nin the `contents` field.\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#CommandInputRecordField",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#InputRecordField",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputRecordSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputRecordSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputEnumSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputEnumSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputArraySchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputArraySchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputBinding",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandLineBinding"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#CommandInputRecordSchema",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#InputRecordSchema",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputRecordField",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputRecordField"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#CommandInputEnumSchema",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#InputEnumSchema",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputBinding",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandLineBinding"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#CommandInputArraySchema",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#InputArraySchema",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputRecordSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputRecordSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputEnumSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputEnumSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputArraySchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputArraySchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputBinding",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandLineBinding"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#CommandOutputRecordField",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#OutputRecordField",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputRecordSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputRecordSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputEnumSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputEnumSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputArraySchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputArraySchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputBinding",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputBinding"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#CommandOutputRecordSchema",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#OutputRecordSchema",
-        "fields": [
-            {
-                "jsonldPredicate": "@id",
-                "name": "https://w3id.org/cwl/cwl#CommandOutputRecordSchema/name",
-                "type": [
-                  "null",
-                  "string"
-                ]
-            }
-        ],
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputRecordField",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputRecordField"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#CommandOutputEnumSchema",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#OutputEnumSchema",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputRecordSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputRecordSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputEnumSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputEnumSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputArraySchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputArraySchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputBinding",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputBinding"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#CommandOutputArraySchema",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#OutputArraySchema",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputRecordSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputRecordSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputEnumSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputEnumSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputArraySchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputArraySchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputBinding",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputBinding"
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#CommandInputParameter",
-        "extends": "https://w3id.org/cwl/cwl#InputParameter",
-        "doc": "An input parameter for a CommandLineTool.",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputRecordSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputRecordSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputEnumSchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputEnumSchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputArraySchema",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputArraySchema"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputBinding",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandLineBinding"
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#CommandOutputParameter",
-        "extends": "https://w3id.org/cwl/cwl#OutputParameter",
-        "doc": "An output parameter for a CommandLineTool.",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputBinding",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputBinding"
-            }
-        ],
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandOutputParameter/type",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#CWLType",
-                    "https://w3id.org/cwl/cwl#stdout",
-                    "https://w3id.org/cwl/cwl#stderr",
-                    "https://w3id.org/cwl/cwl#CommandOutputRecordSchema",
-                    "https://w3id.org/cwl/cwl#CommandOutputEnumSchema",
-                    "https://w3id.org/cwl/cwl#CommandOutputArraySchema",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": [
-                            "https://w3id.org/cwl/cwl#CWLType",
-                            "https://w3id.org/cwl/cwl#CommandOutputRecordSchema",
-                            "https://w3id.org/cwl/cwl#CommandOutputEnumSchema",
-                            "https://w3id.org/cwl/cwl#CommandOutputArraySchema",
-                            "string"
-                        ]
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#CommandOutputParameter/type/type",
-                    "_type": "@vocab",
-                    "refScope": 2,
-                    "typeDSL": true
-                },
-                "doc": "Specify valid types of data that may be assigned to this parameter.\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#stdout",
-        "type": "enum",
-        "symbols": [
-            "https://w3id.org/cwl/cwl#stdout"
-        ],
-        "docParent": "https://w3id.org/cwl/cwl#CommandOutputParameter",
-        "doc": "Only valid as a `type` for a `CommandLineTool` output with no\n`outputBinding` set.\n\nThe following\n```\noutputs:\n   an_output_name:\n   type: stdout\n\nstdout: a_stdout_file\n```\nis equivalent to\n```\noutputs:\n  an_output_name:\n    type: File\n    streamable: true\n    outputBinding:\n      glob: a_stdout_file\n\nstdout: a_stdout_file\n```\n\nIf there is no `stdout` name provided, a random filename will be created.\nFor example, the following\n```\noutputs:\n  an_output_name:\n    type: stdout\n```\nis equivalent to\n```\noutputs:\n  an_output_name:\n    type: File\n    streamable: true\n    outputBinding:\n      glob: random_stdout_filenameABCDEFG\n\nstdout: random_stdout_filenameABCDEFG\n```\n"
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#stderr",
-        "type": "enum",
-        "symbols": [
-            "https://w3id.org/cwl/cwl#stderr"
-        ],
-        "docParent": "https://w3id.org/cwl/cwl#CommandOutputParameter",
-        "doc": "Only valid as a `type` for a `CommandLineTool` output with no\n`outputBinding` set.\n\nThe following\n```\noutputs:\n  an_output_name:\n  type: stderr\n\nstderr: a_stderr_file\n```\nis equivalent to\n```\noutputs:\n  an_output_name:\n    type: File\n    streamable: true\n    outputBinding:\n      glob: a_stderr_file\n\nstderr: a_stderr_file\n```\n\nIf there is no `stderr` name provided, a random filename will be created.\nFor example, the following\n```\noutputs:\n  an_output_name:\n    type: stderr\n```\nis equivalent to\n```\noutputs:\n  an_output_name:\n    type: File\n    streamable: true\n    outputBinding:\n      glob: random_stderr_filenameABCDEFG\n\nstderr: random_stderr_filenameABCDEFG\n```\n"
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#CommandLineTool",
-        "extends": "https://w3id.org/cwl/cwl#Process",
-        "documentRoot": true,
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#InputParameter",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandInputParameter"
-            },
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputParameter",
-                "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputParameter"
-            }
-        ],
-        "doc": "This defines the schema of the CWL Command Line Tool Description document.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineTool/class",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                },
-                "type": "string"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineTool/baseCommand",
-                "doc": "Specifies the program to execute.  If an array, the first element of\nthe array is the command to execute, and subsequent elements are\nmandatory command line arguments.  The elements in `baseCommand` must\nappear before any command line bindings from `inputBinding` or\n`arguments`.\n\nIf `baseCommand` is not provided or is an empty array, the first\nelement of the command line produced after processing `inputBinding` or\n`arguments` must be used as the program to execute.\n\nIf the program includes a path separator character it must\nbe an absolute path, otherwise it is an error.  If the program does not\ninclude a path separator, search the `$PATH` variable in the runtime\nenvironment of the workflow runner find the absolute path of the\nexecutable.\n",
-                "type": [
-                    "null",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": "string"
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#baseCommand",
-                    "_container": "@list"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineTool/arguments",
-                "doc": "Command line bindings which are not directly associated with input parameters.\n",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": [
-                            "string",
-                            "https://w3id.org/cwl/cwl#Expression",
-                            "https://w3id.org/cwl/cwl#CommandLineBinding"
-                        ]
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#arguments",
-                    "_container": "@list"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineTool/stdin",
-                "type": [
-                    "null",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "A path to a file whose contents must be piped into the command's\nstandard input stream.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineTool/stderr",
-                "type": [
-                    "null",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "jsonldPredicate": "https://w3id.org/cwl/cwl#stderr",
-                "doc": "Capture the command's standard error stream to a file written to\nthe designated output directory.\n\nIf `stderr` is a string, it specifies the file name to use.\n\nIf `stderr` is an expression, the expression is evaluated and must\nreturn a string with the file name to use to capture stderr.  If the\nreturn value is not a string, or the resulting path contains illegal\ncharacters (such as the path separator `/`) it is an error.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineTool/stdout",
-                "type": [
-                    "null",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "jsonldPredicate": "https://w3id.org/cwl/cwl#stdout",
-                "doc": "Capture the command's standard output stream to a file written to\nthe designated output directory.\n\nIf `stdout` is a string, it specifies the file name to use.\n\nIf `stdout` is an expression, the expression is evaluated and must\nreturn a string with the file name to use to capture stdout.  If the\nreturn value is not a string, or the resulting path contains illegal\ncharacters (such as the path separator `/`) it is an error.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineTool/successCodes",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "int"
-                    }
-                ],
-                "doc": "Exit codes that indicate the process completed successfully.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineTool/temporaryFailCodes",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "int"
-                    }
-                ],
-                "doc": "Exit codes that indicate the process failed due to a possibly\ntemporary condition, where executing the process with the same\nruntime environment and inputs may produce different results.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#CommandLineTool/permanentFailCodes",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "int"
-                    }
-                ],
-                "doc": "Exit codes that indicate the process failed due to a permanent logic error, where executing the process with the same runtime environment and same inputs is expected to always fail."
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#DockerRequirement",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "Indicates that a workflow component should be run in a\n[Docker](http://docker.com) container, and specifies how to fetch or build\nthe image.\n\nIf a CommandLineTool lists `DockerRequirement` under\n`hints` (or `requirements`), it may (or must) be run in the specified Docker\ncontainer.\n\nThe platform must first acquire or install the correct Docker image as\nspecified by `dockerPull`, `dockerImport`, `dockerLoad` or `dockerFile`.\n\nThe platform must execute the tool in the container using `docker run` with\nthe appropriate Docker image and tool command line.\n\nThe workflow platform may provide input files and the designated output\ndirectory through the use of volume bind mounts.  The platform may rewrite\nfile paths in the input object to correspond to the Docker bind mounted\nlocations.\n\nWhen running a tool contained in Docker, the workflow platform must not\nassume anything about the contents of the Docker container, such as the\npresence or absence of specific software, except to assume that the\ngenerated command line represents a valid command within the runtime\nenvironment of the container.\n\n## Interaction with other requirements\n\nIf [EnvVarRequirement](#EnvVarRequirement) is specified alongside a\nDockerRequirement, the environment variables must be provided to Docker\nusing `--env` or `--env-file` and interact with the container's preexisting\nenvironment as defined by Docker.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#DockerRequirement/class",
-                "type": "string",
-                "doc": "Always 'DockerRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerPull",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "Specify a Docker image to retrieve using `docker pull`."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerLoad",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "Specify a HTTP URL from which to download a Docker image using `docker load`."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerFile",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "Supply the contents of a Dockerfile which will be built using `docker build`."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerImport",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "Provide HTTP URL to download and gunzip a Docker images using `docker import."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerImageId",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "The image id that will be used for `docker run`.  May be a\nhuman-readable image name or the image identifier hash.  May be skipped\nif `dockerPull` is specified, in which case the `dockerPull` image id\nmust be used.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerOutputDirectory",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "doc": "Set the designated output directory to a specific location inside the\nDocker container.\n"
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#SoftwareRequirement",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "A list of software packages that should be configured in the environment of\nthe defined process.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#SoftwareRequirement/class",
-                "type": "string",
-                "doc": "Always 'SoftwareRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#SoftwareRequirement/packages",
-                "type": {
-                    "type": "array",
-                    "items": "https://w3id.org/cwl/cwl#SoftwarePackage"
-                },
-                "doc": "The list of software to be configured.",
-                "jsonldPredicate": {
-                    "mapSubject": "package",
-                    "mapPredicate": "specs"
-                }
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#SoftwarePackage",
-        "type": "record",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#SoftwarePackage/package",
-                "type": "string",
-                "doc": "The common name of the software to be configured."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#SoftwarePackage/version",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "string"
-                    }
-                ],
-                "doc": "The (optional) version of the software to configured."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#SoftwarePackage/specs",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "string"
-                    }
-                ],
-                "doc": "Must be one or more IRIs identifying resources for installing or\nenabling the software.  Implementations may provide resolvers which map\nwell-known software spec IRIs to some configuration action.\n\nFor example, an IRI `https://packages.debian.org/jessie/bowtie` could\nbe resolved with `apt-get install bowtie`.  An IRI\n`https://anaconda.org/bioconda/bowtie` could be resolved with `conda\ninstall -c bioconda bowtie`.\n\nTools may also provide IRIs to index entries such as\n[RRID](http://www.identifiers.org/rrid/), such as\n`http://identifiers.org/rrid/RRID:SCR_005476`\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#Dirent",
-        "type": "record",
-        "doc": "Define a file or subdirectory that must be placed in the designated output\ndirectory prior to executing the command line tool.  May be the result of\nexecuting an expression, such as building a configuration file from a\ntemplate.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#Dirent/entryname",
-                "type": [
-                    "null",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#entryname"
-                },
-                "doc": "The name of the file or subdirectory to create in the output directory.\nIf `entry` is a File or Directory, this overrides `basename`.  Optional.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Dirent/entry",
-                "type": [
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#entry"
-                },
-                "doc": "If the value is a string literal or an expression which evaluates to a\nstring, a new file must be created with the string as the file contents.\n\nIf the value is an expression that evaluates to a `File` object, this\nindicates the referenced file should be added to the designated output\ndirectory prior to executing the tool.\n\nIf the value is an expression that evaluates to a `Dirent` object, this\nindicates that the File or Directory in `entry` should be added to the\ndesignated output directory with the name in `entryname`.\n\nIf `writable` is false, the file may be made available using a bind\nmount or file system link to avoid unnecessary copying of the input\nfile.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Dirent/writable",
-                "type": [
-                    "null",
-                    "boolean"
-                ],
-                "doc": "If true, the file or directory must be writable by the tool.  Changes\nto the file or directory must be isolated and not visible by any other\nCommandLineTool process.  This may be implemented by making a copy of\nthe original file or directory.  Default false (files and directories\nread-only by default).\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#InitialWorkDirRequirement",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "Define a list of files and subdirectories that must be created by the workflow platform in the designated output directory prior to executing the command line tool.",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#InitialWorkDirRequirement/class",
-                "type": "string",
-                "doc": "InitialWorkDirRequirement",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#InitialWorkDirRequirement/listing",
-                "type": [
-                    {
-                        "type": "array",
-                        "items": [
-                            "https://w3id.org/cwl/cwl#File",
-                            "https://w3id.org/cwl/cwl#Directory",
-                            "https://w3id.org/cwl/cwl#Dirent",
-                            "string",
-                            "https://w3id.org/cwl/cwl#Expression"
-                        ]
-                    },
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#listing"
-                },
-                "doc": "The list of files or subdirectories that must be placed in the\ndesignated output directory prior to executing the command line tool.\n\nMay be an expression.  If so, the expression return value must validate\nas `{type: array, items: [File, Directory]}`.\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#EnvVarRequirement",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "Define a list of environment variables which will be set in the\nexecution environment of the tool.  See `EnvironmentDef` for details.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#EnvVarRequirement/class",
-                "type": "string",
-                "doc": "Always 'EnvVarRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#EnvVarRequirement/envDef",
-                "type": {
-                    "type": "array",
-                    "items": "https://w3id.org/cwl/cwl#EnvironmentDef"
-                },
-                "doc": "The list of environment variables.",
-                "jsonldPredicate": {
-                    "mapSubject": "envName",
-                    "mapPredicate": "envValue"
-                }
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#ShellCommandRequirement",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "Modify the behavior of CommandLineTool to generate a single string\ncontaining a shell command line.  Each item in the argument list must be\njoined into a string separated by single spaces and quoted to prevent\nintepretation by the shell, unless `CommandLineBinding` for that argument\ncontains `shellQuote: false`.  If `shellQuote: false` is specified, the\nargument is joined into the command string without quoting, which allows\nthe use of shell metacharacters such as `|` for pipes.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#ShellCommandRequirement/class",
-                "type": "string",
-                "doc": "Always 'ShellCommandRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#ResourceRequirement",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "Specify basic hardware resource requirements.\n\n\"min\" is the minimum amount of a resource that must be reserved to schedule\na job. If \"min\" cannot be satisfied, the job should not be run.\n\n\"max\" is the maximum amount of a resource that the job shall be permitted\nto use. If a node has sufficient resources, multiple jobs may be scheduled\non a single node provided each job's \"max\" resource requirements are\nmet. If a job attempts to exceed its \"max\" resource allocation, an\nimplementation may deny additional resources, which may result in job\nfailure.\n\nIf \"min\" is specified but \"max\" is not, then \"max\" == \"min\"\nIf \"max\" is specified by \"min\" is not, then \"min\" == \"max\".\n\nIt is an error if max < min.\n\nIt is an error if the value of any of these fields is negative.\n\nIf neither \"min\" nor \"max\" is specified for a resource, an implementation may provide a default.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#ResourceRequirement/class",
-                "type": "string",
-                "doc": "Always 'ResourceRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#ResourceRequirement/coresMin",
-                "type": [
-                    "null",
-                    "long",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "Minimum reserved number of CPU cores"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#ResourceRequirement/coresMax",
-                "type": [
-                    "null",
-                    "int",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "Maximum reserved number of CPU cores"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#ResourceRequirement/ramMin",
-                "type": [
-                    "null",
-                    "long",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "Minimum reserved RAM in mebibytes (2**20)"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#ResourceRequirement/ramMax",
-                "type": [
-                    "null",
-                    "long",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "Maximum reserved RAM in mebibytes (2**20)"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#ResourceRequirement/tmpdirMin",
-                "type": [
-                    "null",
-                    "long",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "Minimum reserved filesystem based storage for the designated temporary directory, in mebibytes (2**20)"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#ResourceRequirement/tmpdirMax",
-                "type": [
-                    "null",
-                    "long",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "Maximum reserved filesystem based storage for the designated temporary directory, in mebibytes (2**20)"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#ResourceRequirement/outdirMin",
-                "type": [
-                    "null",
-                    "long",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "Minimum reserved filesystem based storage for the designated output directory, in mebibytes (2**20)"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#ResourceRequirement/outdirMax",
-                "type": [
-                    "null",
-                    "long",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "Maximum reserved filesystem based storage for the designated output directory, in mebibytes (2**20)"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#WorkflowDoc",
-        "type": "documentation",
-        "doc": [
-            "# Common Workflow Language (CWL) Workflow Description, v1.0\n\nThis version:\n  * https://w3id.org/cwl/v1.0/\n\nCurrent version:\n  * https://w3id.org/cwl/\n",
-            "\n\n",
-            "\n",
-            "\n\n",
-            "# Abstract\n\nOne way to define a workflow is: an analysis task represented by a\ndirected graph describing a sequence of operations that transform an\ninput data set to output. This specification defines the Common Workflow\nLanguage (CWL) Workflow description, a vendor-neutral standard for\nrepresenting workflows intended to be portable across a variety of\ncomputing platforms.\n",
-            "\n",
-            "\n## Introduction to v1.0\n\nThis specification represents the first full release from the CWL group.\nSince draft-3, this draft introduces the following changes and additions:\n\n  * The `inputs` and `outputs` fields have been renamed `in` and `out`.\n  * Syntax simplifcations: denoted by the `map<>` syntax. Example: `in`\n    contains a list of items, each with an id. Now one can specify\n    a mapping of that identifier to the corresponding\n    `InputParameter`.\n    ```\n    in:\n     - id: one\n       type: string\n       doc: First input parameter\n     - id: two\n       type: int\n       doc: Second input parameter\n    ```\n    can be\n    ```\n    in:\n     one:\n      type: string\n      doc: First input parameter\n     two:\n      type: int\n      doc: Second input parameter\n    ```\n  * The common field `description` has been renamed to `doc`.\n\n## Purpose\n\nThe Common Workflow Language Command Line Tool Description express\nworkflows for data-intensive science, such as Bioinformatics, Chemistry,\nPhysics, and Astronomy.  This specification is intended to define a data\nand execution model for Workflows that can be implemented on top of a\nvariety of computing platforms, ranging from an individual workstation to\ncluster, grid, cloud, and high performance computing systems.\n",
-            "\n"
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#ExpressionToolOutputParameter",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#OutputParameter",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#ExpressionToolOutputParameter/type",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#CWLType",
-                    "https://w3id.org/cwl/cwl#OutputRecordSchema",
-                    "https://w3id.org/cwl/cwl#OutputEnumSchema",
-                    "https://w3id.org/cwl/cwl#OutputArraySchema",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": [
-                            "https://w3id.org/cwl/cwl#CWLType",
-                            "https://w3id.org/cwl/cwl#OutputRecordSchema",
-                            "https://w3id.org/cwl/cwl#OutputEnumSchema",
-                            "https://w3id.org/cwl/cwl#OutputArraySchema",
-                            "string"
-                        ]
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#ExpressionToolOutputParameter/type/type",
-                    "_type": "@vocab",
-                    "refScope": 2,
-                    "typeDSL": true
-                },
-                "doc": "Specify valid types of data that may be assigned to this parameter.\n"
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#ExpressionTool",
-        "extends": "https://w3id.org/cwl/cwl#Process",
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputParameter",
-                "specializeTo": "https://w3id.org/cwl/cwl#ExpressionToolOutputParameter"
-            }
-        ],
-        "documentRoot": true,
-        "doc": "Execute an expression as a Workflow step.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#ExpressionTool/class",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                },
-                "type": "string"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#ExpressionTool/expression",
-                "type": [
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "doc": "The expression to execute.  The expression must return a JSON object which\nmatches the output parameters of the ExpressionTool.\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#LinkMergeMethod",
-        "type": "enum",
-        "docParent": "https://w3id.org/cwl/cwl#WorkflowStepInput",
-        "doc": "The input link merge method, described in [WorkflowStepInput](#WorkflowStepInput).",
-        "symbols": [
-            "https://w3id.org/cwl/cwl#LinkMergeMethod/merge_nested",
-            "https://w3id.org/cwl/cwl#LinkMergeMethod/merge_flattened"
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#WorkflowOutputParameter",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#OutputParameter",
-        "docParent": "https://w3id.org/cwl/cwl#Workflow",
-        "doc": "Describe an output parameter of a workflow.  The parameter must be\nconnected to one or more parameters defined in the workflow that will\nprovide the value of the output parameter.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowOutputParameter/outputSource",
-                "doc": "Specifies one or more workflow parameters that supply the value of to\nthe output parameter.\n",
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#outputSource",
-                    "_type": "@id",
-                    "refScope": 0
-                },
-                "type": [
-                    "null",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": "string"
-                    }
-                ]
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowOutputParameter/linkMerge",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#LinkMergeMethod"
-                ],
-                "jsonldPredicate": "cwl:linkMerge",
-                "doc": "The method to use to merge multiple sources into a single array.\nIf not specified, the default method is \"merge_nested\".\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowOutputParameter/type",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#CWLType",
-                    "https://w3id.org/cwl/cwl#OutputRecordSchema",
-                    "https://w3id.org/cwl/cwl#OutputEnumSchema",
-                    "https://w3id.org/cwl/cwl#OutputArraySchema",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": [
-                            "https://w3id.org/cwl/cwl#CWLType",
-                            "https://w3id.org/cwl/cwl#OutputRecordSchema",
-                            "https://w3id.org/cwl/cwl#OutputEnumSchema",
-                            "https://w3id.org/cwl/cwl#OutputArraySchema",
-                            "string"
-                        ]
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#WorkflowOutputParameter/type/type",
-                    "_type": "@vocab",
-                    "refScope": 2,
-                    "typeDSL": true
-                },
-                "doc": "Specify valid types of data that may be assigned to this parameter.\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#Sink",
-        "type": "record",
-        "abstract": true,
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#Sink/source",
-                "doc": "Specifies one or more workflow parameters that will provide input to\nthe underlying step parameter.\n",
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#source",
-                    "_type": "@id",
-                    "refScope": 2
-                },
-                "type": [
-                    "null",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": "string"
-                    }
-                ]
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Sink/linkMerge",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#LinkMergeMethod"
-                ],
-                "jsonldPredicate": "cwl:linkMerge",
-                "doc": "The method to use to merge multiple inbound links into a single array.\nIf not specified, the default method is \"merge_nested\".\n"
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#WorkflowStepInput",
-        "extends": "https://w3id.org/cwl/cwl#Sink",
-        "docParent": "https://w3id.org/cwl/cwl#WorkflowStep",
-        "doc": "The input of a workflow step connects an upstream parameter (from the\nworkflow inputs, or the outputs of other workflows steps) with the input\nparameters of the underlying step.\n\n## Input object\n\nA WorkflowStepInput object must contain an `id` field in the form\n`#fieldname` or `#stepname.fieldname`.  When the `id` field contains a\nperiod `.` the field name consists of the characters following the final\nperiod.  This defines a field of the workflow step input object with the\nvalue of the `source` parameter(s).\n\n## Merging\n\nTo merge multiple inbound data links,\n[MultipleInputFeatureRequirement](#MultipleInputFeatureRequirement) must be specified\nin the workflow or workflow step requirements.\n\nIf the sink parameter is an array, or named in a [workflow\nscatter](#WorkflowStep) operation, there may be multiple inbound data links\nlisted in the `source` field.  The values from the input links are merged\ndepending on the method specified in the `linkMerge` field.  If not\nspecified, the default method is \"merge_nested\".\n\n* **merge_nested**\n\n  The input must be an array consisting of exactly one entry for each\n  input link.  If \"merge_nested\" is specified with a single link, the value\n  from the link must be wrapped in a single-item list.\n\n* **merge_flattened**\n\n  1. The source and sink parameters must be compatible types, or the source\n     type must be compatible with single element from the \"items\" type of\n     the destination array parameter.\n  2. Source parameters which are arrays are concatenated.\n     Source parameters which are single element types are appended as\n     single elements.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStepInput/id",
-                "type": "string",
-                "jsonldPredicate": "@id",
-                "doc": "A unique identifier for this workflow input parameter."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStepInput/default",
-                "type": [
-                    "null",
-                    "Any"
-                ],
-                "doc": "The default value for this parameter if there is no `source`\nfield.\n",
-                "jsonldPredicate": "cwl:default"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStepInput/valueFrom",
-                "type": [
-                    "null",
-                    "string",
-                    "https://w3id.org/cwl/cwl#Expression"
-                ],
-                "jsonldPredicate": "cwl:valueFrom",
-                "doc": "To use valueFrom, [StepInputExpressionRequirement](#StepInputExpressionRequirement) must\nbe specified in the workflow or workflow step requirements.\n\nIf `valueFrom` is a constant string value, use this as the value for\nthis input parameter.\n\nIf `valueFrom` is a parameter reference or expression, it must be\nevaluated to yield the actual value to be assiged to the input field.\n\nThe `self` value of in the parameter reference or expression must be\nthe value of the parameter(s) specified in the `source` field, or\nnull if there is no `source` field.\n\nThe value of `inputs` in the parameter reference or expression must be\nthe input object to the workflow step after assigning the `source`\nvalues and then scattering.  The order of evaluating `valueFrom` among\nstep input parameters is undefined and the result of evaluating\n`valueFrom` on a parameter must not be visible to evaluation of\n`valueFrom` on other parameters.\n"
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#WorkflowStepOutput",
-        "docParent": "https://w3id.org/cwl/cwl#WorkflowStep",
-        "doc": "Associate an output parameter of the underlying process with a workflow\nparameter.  The workflow parameter (given in the `id` field) be may be used\nas a `source` to connect with input parameters of other workflow steps, or\nwith an output parameter of the process.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStepOutput/id",
-                "type": "string",
-                "jsonldPredicate": "@id",
-                "doc": "A unique identifier for this workflow output parameter.  This is the\nidentifier to use in the `source` field of `WorkflowStepInput` to\nconnect the output value to downstream parameters.\n"
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#ScatterMethod",
-        "type": "enum",
-        "docParent": "https://w3id.org/cwl/cwl#WorkflowStep",
-        "doc": "The scatter method, as described in [workflow step scatter](#WorkflowStep).",
-        "symbols": [
-            "https://w3id.org/cwl/cwl#ScatterMethod/dotproduct",
-            "https://w3id.org/cwl/cwl#ScatterMethod/nested_crossproduct",
-            "https://w3id.org/cwl/cwl#ScatterMethod/flat_crossproduct"
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#WorkflowStep",
-        "type": "record",
-        "docParent": "https://w3id.org/cwl/cwl#Workflow",
-        "doc": "A workflow step is an executable element of a workflow.  It specifies the\nunderlying process implementation (such as `CommandLineTool` or another\n`Workflow`) in the `run` field and connects the input and output parameters\nof the underlying process to workflow parameters.\n\n# Scatter/gather\n\nTo use scatter/gather,\n[ScatterFeatureRequirement](#ScatterFeatureRequirement) must be specified\nin the workflow or workflow step requirements.\n\nA \"scatter\" operation specifies that the associated workflow step or\nsubworkflow should execute separately over a list of input elements.  Each\njob making up a scatter operation is independent and may be executed\nconcurrently.\n\nThe `scatter` field specifies one or more input parameters which will be\nscattered.  An input parameter may be listed more than once.  The declared\ntype of each input parameter is implicitly wrapped in an array for each\ntime it appears in the `scatter` field.  As a result, upstream parameters\nwhich are connected to scattered parameters may be arrays.\n\nAll output parameter types are also implicitly wrapped in arrays.  Each job\nin the scatter results in an entry in the output array.\n\nIf `scatter` declares more than one input parameter, `scatterMethod`\ndescribes how to decompose the input into a discrete set of jobs.\n\n  * **dotproduct** specifies that each of the input arrays are aligned and one\n      element taken from each array to construct each job.  It is an error\n      if all input arrays are not the same length.\n\n  * **nested_crossproduct** specifies the Cartesian product of the inputs,\n      producing a job for every combination of the scattered inputs.  The\n      output must be nested arrays for each level of scattering, in the\n      order that the input arrays are listed in the `scatter` field.\n\n  * **flat_crossproduct** specifies the Cartesian product of the inputs,\n      producing a job for every combination of the scattered inputs.  The\n      output arrays must be flattened to a single level, but otherwise listed in the\n      order that the input arrays are listed in the `scatter` field.\n\n# Subworkflows\n\nTo specify a nested workflow as part of a workflow step,\n[SubworkflowFeatureRequirement](#SubworkflowFeatureRequirement) must be\nspecified in the workflow or workflow step requirements.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStep/id",
-                "type": "string",
-                "jsonldPredicate": "@id",
-                "doc": "The unique identifier for this workflow step."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStep/in",
-                "type": {
-                    "type": "array",
-                    "items": "https://w3id.org/cwl/cwl#WorkflowStepInput"
-                },
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#in",
-                    "mapSubject": "id",
-                    "mapPredicate": "source"
-                },
-                "doc": "Defines the input parameters of the workflow step.  The process is ready to\nrun when all required input parameters are associated with concrete\nvalues.  Input parameters include a schema for each parameter which is\nused to validate the input object.  It may also be used build a user\ninterface for constructing the input object.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStep/out",
-                "type": [
-                    {
-                        "type": "array",
-                        "items": [
-                            "string",
-                            "https://w3id.org/cwl/cwl#WorkflowStepOutput"
-                        ]
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#out",
-                    "_type": "@id",
-                    "identity": true
-                },
-                "doc": "Defines the parameters representing the output of the process.  May be\nused to generate and/or validate the output object.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStep/requirements",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "https://w3id.org/cwl/cwl#ProcessRequirement"
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#requirements",
-                    "mapSubject": "class"
-                },
-                "doc": "Declares requirements that apply to either the runtime environment or the\nworkflow engine that must be met in order to execute this workflow step.  If\nan implementation cannot satisfy all requirements, or a requirement is\nlisted which is not recognized by the implementation, it is a fatal\nerror and the implementation must not attempt to run the process,\nunless overridden at user option.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStep/hints",
-                "type": [
-                    "null",
-                    {
-                        "type": "array",
-                        "items": "Any"
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#hints",
-                    "noLinkCheck": true,
-                    "mapSubject": "class"
-                },
-                "doc": "Declares hints applying to either the runtime environment or the\nworkflow engine that may be helpful in executing this workflow step.  It is\nnot an error if an implementation cannot satisfy all hints, however\nthe implementation may report a warning.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStep/label",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "jsonldPredicate": "rdfs:label",
-                "doc": "A short, human-readable label of this process object."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStep/doc",
-                "type": [
-                    "null",
-                    "string"
-                ],
-                "jsonldPredicate": "rdfs:comment",
-                "doc": "A long, human-readable description of this process object."
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStep/run",
-                "type": [
-                    "string",
-                    "https://w3id.org/cwl/cwl#Process"
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#run",
-                    "_type": "@id"
-                },
-                "doc": "Specifies the process to run.\n"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStep/scatter",
-                "type": [
-                    "null",
-                    "string",
-                    {
-                        "type": "array",
-                        "items": "string"
-                    }
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#scatter",
-                    "_type": "@id",
-                    "_container": "@list",
-                    "refScope": 0
-                }
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#WorkflowStep/scatterMethod",
-                "doc": "Required if `scatter` is an array of more than one element.\n",
-                "type": [
-                    "null",
-                    "https://w3id.org/cwl/cwl#ScatterMethod"
-                ],
-                "jsonldPredicate": {
-                    "_id": "https://w3id.org/cwl/cwl#scatterMethod",
-                    "_type": "@vocab"
-                }
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#Workflow",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#Process",
-        "documentRoot": true,
-        "specialize": [
-            {
-                "specializeFrom": "https://w3id.org/cwl/cwl#OutputParameter",
-                "specializeTo": "https://w3id.org/cwl/cwl#WorkflowOutputParameter"
-            }
-        ],
-        "doc": "A workflow describes a set of **steps** and the **dependencies** between\nthose steps.  When a step produces output that will be consumed by a\nsecond step, the first step is a dependency of the second step.\n\nWhen there is a dependency, the workflow engine must execute the preceeding\nstep and wait for it to successfully produce output before executing the\ndependent step.  If two steps are defined in the workflow graph that\nare not directly or indirectly dependent, these steps are **independent**,\nand may execute in any order or execute concurrently.  A workflow is\ncomplete when all steps have been executed.\n\nDependencies between parameters are expressed using the `source` field on\n[workflow step input parameters](#WorkflowStepInput) and [workflow output\nparameters](#WorkflowOutputParameter).\n\nThe `source` field expresses the dependency of one parameter on another\nsuch that when a value is associated with the parameter specified by\n`source`, that value is propagated to the destination parameter.  When all\ndata links inbound to a given step are fufilled, the step is ready to\nexecute.\n\n## Workflow success and failure\n\nA completed step must result in one of `success`, `temporaryFailure` or\n`permanentFailure` states.  An implementation may choose to retry a step\nexecution which resulted in `temporaryFailure`.  An implementation may\nchoose to either continue running other steps of a workflow, or terminate\nimmediately upon `permanentFailure`.\n\n* If any step of a workflow execution results in `permanentFailure`, then\nthe workflow status is `permanentFailure`.\n\n* If one or more steps result in `temporaryFailure` and all other steps\ncomplete `success` or are not executed, then the workflow status is\n`temporaryFailure`.\n\n* If all workflow steps are executed and complete with `success`, then the\nworkflow status is `success`.\n\n# Extensions\n\n[ScatterFeatureRequirement](#ScatterFeatureRequirement) and\n[SubworkflowFeatureRequirement](#SubworkflowFeatureRequirement) are\navailable as standard [extensions](#Extensions_and_Metadata) to core\nworkflow semantics.\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#Workflow/class",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                },
-                "type": "string"
-            },
-            {
-                "name": "https://w3id.org/cwl/cwl#Workflow/steps",
-                "doc": "The individual steps that make up the workflow.  Each step is executed when all of its\ninput data links are fufilled.  An implementation may choose to execute\nthe steps in a different order than listed and/or execute steps\nconcurrently, provided that dependencies between steps are met.\n",
-                "type": [
-                    {
-                        "type": "array",
-                        "items": "https://w3id.org/cwl/cwl#WorkflowStep"
-                    }
-                ],
-                "jsonldPredicate": {
-                    "mapSubject": "id"
-                }
-            }
-        ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#SubworkflowFeatureRequirement",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "Indicates that the workflow platform must support nested workflows in\nthe `run` field of [WorkflowStep](#WorkflowStep).\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#SubworkflowFeatureRequirement/class",
-                "type": "string",
-                "doc": "Always 'SubworkflowFeatureRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#ScatterFeatureRequirement",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "Indicates that the workflow platform must support the `scatter` and\n`scatterMethod` fields of [WorkflowStep](#WorkflowStep).\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#ScatterFeatureRequirement/class",
-                "type": "string",
-                "doc": "Always 'ScatterFeatureRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            }
-        ]
-    },
-    {
-        "name": "https://w3id.org/cwl/cwl#MultipleInputFeatureRequirement",
-        "type": "record",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "Indicates that the workflow platform must support multiple inbound data links\nlisted in the `source` field of [WorkflowStepInput](#WorkflowStepInput).\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#MultipleInputFeatureRequirement/class",
-                "type": "string",
-                "doc": "Always 'MultipleInputFeatureRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            }
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/salad#type",
+          "_type": "@vocab",
+          "refScope": 2,
+          "typeDSL": true
+        },
+        "doc": "Specify valid types of data that may be assigned to this parameter.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#OutputParameter",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#Parameter",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#OutputParameter/id",
+        "type": "string",
+        "jsonldPredicate": "@id",
+        "doc": "The unique identifier for this parameter object."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#OutputParameter/outputBinding",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#OutputBinding"
+        ],
+        "jsonldPredicate": "cwl:outputBinding",
+        "doc": "Describes how to handle the outputs of a process.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#OutputParameter/format",
+        "type": [
+          "null",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#format",
+          "_type": "@id",
+          "identity": true,
+          "noLinkCheck": true
+        },
+        "doc": "Only valid when `type: File` or is an array of `items: File`.\n\nThis is the file format that will be assigned to\nthe output parameter.\n"
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "abstract": true,
+    "doc": "A process requirement declares a prerequisite that may or must be fulfilled\nbefore executing a process.  See [`Process.hints`](#process) and\n[`Process.requirements`](#process).\n\nProcess requirements are the primary mechanism for specifying extensions to\nthe CWL core specification.\n"
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#Process",
+    "abstract": true,
+    "doc": "\nThe base executable type in CWL is the `Process` object defined by the\ndocument.  Note that the `Process` object is abstract and cannot be\ndirectly executed.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#Process/id",
+        "type": [
+          "null",
+          "string"
+        ],
+        "jsonldPredicate": "@id",
+        "doc": "The unique identifier for this process object."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Process/inputs",
+        "type": {
+          "type": "array",
+          "items": "https://w3id.org/cwl/cwl#InputParameter"
+        },
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#inputs",
+          "mapSubject": "id",
+          "mapPredicate": "type"
+        },
+        "doc": "Defines the input parameters of the process.  The process is ready to\nrun when all required input parameters are associated with concrete\nvalues.  Input parameters include a schema for each parameter which is\nused to validate the input object.  It may also be used to build a user\ninterface for constructing the input object.\n\nWhen accepting an input object, all input parameters must have a value.\nIf an input parameter is missing from the input object, it must be\nassigned a value of `null` (or the value of `default` for that\nparameter, if provided) for the purposes of validation and evaluation\nof expressions.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Process/outputs",
+        "type": {
+          "type": "array",
+          "items": "https://w3id.org/cwl/cwl#OutputParameter"
+        },
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#outputs",
+          "mapSubject": "id",
+          "mapPredicate": "type"
+        },
+        "doc": "Defines the parameters representing the output of the process.  May be\nused to generate and/or validate the output object.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Process/requirements",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": "https://w3id.org/cwl/cwl#ProcessRequirement"
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#requirements",
+          "mapSubject": "class"
+        },
+        "doc": "Declares requirements that apply to either the runtime environment or the\nworkflow engine that must be met in order to execute this process.  If\nan implementation cannot satisfy all requirements, or a requirement is\nlisted which is not recognized by the implementation, it is a fatal\nerror and the implementation must not attempt to run the process,\nunless overridden at user option.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Process/hints",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": [
+              "https://w3id.org/cwl/cwl#ProcessRequirement",
+              "Any"
+            ]
+          }
+        ],
+        "doc": "Declares hints applying to either the runtime environment or the\nworkflow engine that may be helpful in executing this process.  It is\nnot an error if an implementation cannot satisfy all hints, however\nthe implementation may report a warning.\n",
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#hints",
+          "noLinkCheck": true,
+          "mapSubject": "class"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Process/label",
+        "type": [
+          "null",
+          "string"
+        ],
+        "jsonldPredicate": "rdfs:label",
+        "doc": "A short, human-readable label of this process object."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Process/doc",
+        "type": [
+          "null",
+          "string"
+        ],
+        "jsonldPredicate": "rdfs:comment",
+        "doc": "A long, human-readable description of this process object."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Process/cwlVersion",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#CWLVersion"
+        ],
+        "doc": "CWL document version. Always required at the document root. Not\nrequired for a Process embedded inside another Process.\n",
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#cwlVersion",
+          "_type": "@vocab"
+        }
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#InlineJavascriptRequirement",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "Indicates that the workflow platform must support inline Javascript expressions.\nIf this requirement is not present, the workflow platform must not perform expression\ninterpolatation.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#InlineJavascriptRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#InlineJavascriptRequirement/class/InlineJavascriptRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#InlineJavascriptRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'InlineJavascriptRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#InlineJavascriptRequirement/expressionLib",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": "string"
+          }
+        ],
+        "doc": "Additional code fragments that will also be inserted\nbefore executing the expression code.  Allows for function definitions that may\nbe called from CWL expressions.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#SchemaDefRequirement",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "This field consists of an array of type definitions which must be used when\ninterpreting the `inputs` and `outputs` fields.  When a `type` field\ncontain a IRI, the implementation must check if the type is defined in\n`schemaDefs` and use that definition.  If the type is not found in\n`schemaDefs`, it is an error.  The entries in `schemaDefs` must be\nprocessed in the order listed such that later schema definitions may refer\nto earlier schema definitions.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#SchemaDefRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#SchemaDefRequirement/class/SchemaDefRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#SchemaDefRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'SchemaDefRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#SchemaDefRequirement/types",
+        "type": {
+          "type": "array",
+          "items": "https://w3id.org/cwl/cwl#InputSchema"
+        },
+        "doc": "The list of type definitions."
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CommandLineToolDoc",
+    "type": "documentation",
+    "doc": [
+      "# Common Workflow Language (CWL) Command Line Tool Description, v1.0.2\n\nThis version:\n  * https://w3id.org/cwl/v1.0/\n\nCurrent version:\n  * https://w3id.org/cwl/\n",
+      "\n\n",
+      "\n",
+      "\n\n",
+      "# Abstract\n\nA Command Line Tool is a non-interactive executable program that reads\nsome input, performs a computation, and terminates after producing some\noutput.  Command line programs are a flexible unit of code sharing and\nreuse, unfortunately the syntax and input/output semantics among command\nline programs is extremely heterogeneous. A common layer for describing\nthe syntax and semantics of programs can reduce this incidental\ncomplexity by providing a consistent way to connect programs together.\nThis specification defines the Common Workflow Language (CWL) Command\nLine Tool Description, a vendor-neutral standard for describing the\nsyntax and input/output semantics of command line programs.\n",
+      "\n",
+      "## Introduction to CWL Command Line Tool standard v1.0.2\n\nThis specification represents the third stable release from the CWL\ngroup.  Since the initial v1.0 release, v1.0.2 introduces the following\nupdates to the CWL Command Line Tool standard.  Documents should continue\nto use `cwlVersion: v1.0` and existing v1.0 documents remain valid,\nhowever CWL documents that relied on previously undefined or\nunderspecified behavior may have slightly different behavior in v1.0.2.\n\n  * 13 July 2016: Mark `baseCommand` as optional and update descriptive text.\n  * 14 November 2016: Clarify [SoftwareRequirement](#SoftwareRequirement)\n    `spec` fields.\n  * 12 March 2017:\n    * Mark `default` as not required for link checking.\n    * Add note that files in InitialWorkDir must have path in output directory.\n    * Add note that writable: true applies recursively.\n  * 23 July 2017: (v1.0.1)\n    * Add clarification about scattering over empty arrays.\n    * Clarify interpretation of `secondaryFiles` on inputs.\n    * Expanded discussion of semantics of `File` and `Directory` types\n    * Fixed typo \"EMACScript\" to \"ECMAScript\"\n    * Clarified application of input parameter default values when the input is `null` or undefined.\n    * Clarified valid types and meaning of the format field on inputs versus outputs\n    * Clarify that command line arguments must not interpreted as shell except when shellQuote: false\n    * Clarify behavior of `entryname`\n  * 10 August 2017: (v1.0.2)\n    * Clarify behavior resolving expressions in `secondaryFile`\n\nSince draft-3, v1.0 introduces the following changes and additions\nto the CWL Command Line Tool standard:\n\n  * The [Directory](#Directory) type.\n  * Syntax simplifcations: denoted by the `map<>` syntax. Example: inputs\n    contains a list of items, each with an id. Now one can specify\n    a mapping of that identifier to the corresponding\n    `CommandInputParameter`.\n    ```\n    inputs:\n     - id: one\n       type: string\n       doc: First input parameter\n     - id: two\n       type: int\n       doc: Second input parameter\n    ```\n    can be\n    ```\n    inputs:\n     one:\n      type: string\n      doc: First input parameter\n     two:\n      type: int\n      doc: Second input parameter\n    ```\n  * [InitialWorkDirRequirement](#InitialWorkDirRequirement): list of\n    files and subdirectories to be present in the output directory prior\n    to execution.\n  * Shortcuts for specifying the standard [output](#stdout) and/or\n    [error](#stderr) streams as a (streamable) File output.\n  * [SoftwareRequirement](#SoftwareRequirement) for describing software\n    dependencies of a tool.\n  * The common `description` field has been renamed to `doc`.\n\n\n## Purpose\n\nStandalone programs are a flexible and interoperable form of code reuse.\nUnlike monolithic applications, applications and analysis workflows which\nare composed of multiple separate programs can be written in multiple\nlanguages and execute concurrently on multiple hosts.  However, POSIX\ndoes not dictate computer-readable grammar or semantics for program input\nand output, resulting in extremely heterogeneous command line grammar and\ninput/output semantics among program.  This is a particular problem in\ndistributed computing (multi-node compute clusters) and virtualized\nenvironments (such as Docker containers) where it is often necessary to\nprovision resources such as input files before executing the program.\n\nOften this gap is filled by hard coding program invocation and\nimplicitly assuming requirements will be met, or abstracting program\ninvocation with wrapper scripts or descriptor documents.  Unfortunately,\nwhere these approaches are application or platform specific it creates a\nsignificant barrier to reproducibility and portability, as methods\ndeveloped for one platform must be manually ported to be used on new\nplatforms.  Similarly it creates redundant work, as wrappers for popular\ntools must be rewritten for each application or platform in use.\n\nThe Common Workflow Language Command Line Tool Description is designed to\nprovide a common standard description of grammar and semantics for\ninvoking programs used in data-intensive fields such as Bioinformatics,\nChemistry, Physics, Astronomy, and Statistics.  This specification\ndefines a precise data and execution model for Command Line Tools that\ncan be implemented on a variety of computing platforms, ranging from a\nsingle workstation to cluster, grid, cloud, and high performance\ncomputing platforms.\n",
+      "\n",
+      "\n"
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#EnvironmentDef",
+    "doc": "Define an environment variable that will be set in the runtime environment\nby the workflow platform when executing the command line tool.  May be the\nresult of executing an expression, such as getting a parameter from input.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#EnvironmentDef/envName",
+        "type": "string",
+        "doc": "The environment variable name"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#EnvironmentDef/envValue",
+        "type": [
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "The environment variable value"
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#CommandLineBinding",
+    "extends": "https://w3id.org/cwl/cwl#InputBinding",
+    "doc": "\nWhen listed under `inputBinding` in the input schema, the term\n\"value\" refers to the the corresponding value in the input object.  For\nbinding objects listed in `CommandLineTool.arguments`, the term \"value\"\nrefers to the effective value after evaluating `valueFrom`.\n\nThe binding behavior when building the command line depends on the data\ntype of the value.  If there is a mismatch between the type described by\nthe input schema and the effective value, such as resulting from an\nexpression evaluation, an implementation must use the data type of the\neffective value.\n\n  - **string**: Add `prefix` and the string to the command line.\n\n  - **number**: Add `prefix` and decimal representation to command line.\n\n  - **boolean**: If true, add `prefix` to the command line.  If false, add\n      nothing.\n\n  - **File**: Add `prefix` and the value of\n    [`File.path`](#File) to the command line.\n\n  - **Directory**: Add `prefix` and the value of\n    [`Directory.path`](#Directory) to the command line.\n\n  - **array**: If `itemSeparator` is specified, add `prefix` and the join\n      the array into a single string with `itemSeparator` separating the\n      items.  Otherwise first add `prefix`, then recursively process\n      individual elements.\n      If the array is empty, it does not add anything to command line.\n\n  - **object**: Add `prefix` only, and recursively add object fields for\n      which `inputBinding` is specified.\n\n  - **null**: Add nothing.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineBinding/position",
+        "type": [
+          "null",
+          "int"
+        ],
+        "default": 0,
+        "doc": "The sorting key.  Default position is 0."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineBinding/prefix",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "Command line prefix to add before the value."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineBinding/separate",
+        "type": [
+          "null",
+          "boolean"
+        ],
+        "default": true,
+        "doc": "If true (default), then the prefix and value must be added as separate\ncommand line arguments; if false, prefix and value must be concatenated\ninto a single command line argument.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineBinding/itemSeparator",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "Join the array elements into a single string with the elements\nseparated by by `itemSeparator`.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineBinding/valueFrom",
+        "type": [
+          "null",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "jsonldPredicate": "cwl:valueFrom",
+        "doc": "If `valueFrom` is a constant string value, use this as the value and\napply the binding rules above.\n\nIf `valueFrom` is an expression, evaluate the expression to yield the\nactual value to use to build the command line and apply the binding\nrules above.  If the inputBinding is associated with an input\nparameter, the value of `self` in the expression will be the value of\nthe input parameter.  Input parameter defaults (as specified by the\n`InputParameter.default` field) must be applied before evaluating the\nexpression.\n\nWhen a binding is part of the `CommandLineTool.arguments` field,\nthe `valueFrom` field is required.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineBinding/shellQuote",
+        "type": [
+          "null",
+          "boolean"
+        ],
+        "default": true,
+        "doc": "If `ShellCommandRequirement` is in the requirements for the current command,\nthis controls whether the value is quoted on the command line (default is true).\nUse `shellQuote: false` to inject metacharacters for operations such as pipes.\n\nIf `shellQuote` is true or not provided, the implementation must not\npermit interpretation of any shell metacharacters or directives.\n"
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#CommandOutputBinding",
+    "extends": "https://w3id.org/cwl/cwl#OutputBinding",
+    "doc": "Describes how to generate an output parameter based on the files produced\nby a CommandLineTool.\n\nThe output parameter value is generated by applying these operations in the\nfollowing order:\n\n  - glob\n  - loadContents\n  - outputEval\n  - secondaryFiles\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandOutputBinding/glob",
+        "type": [
+          "null",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression",
+          {
+            "type": "array",
+            "items": "string"
+          }
+        ],
+        "doc": "Find files relative to the output directory, using POSIX glob(3)\npathname matching.  If an array is provided, find files that match any\npattern in the array.  If an expression is provided, the expression must\nreturn a string or an array of strings, which will then be evaluated as\none or more glob patterns.  Must only match and return files which\nactually exist.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandOutputBinding/loadContents",
+        "type": [
+          "null",
+          "boolean"
+        ],
+        "jsonldPredicate": "cwl:loadContents",
+        "doc": "For each file matched in `glob`, read up to\nthe first 64 KiB of text from the file and place it in the `contents`\nfield of the file object for manipulation by `outputEval`.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandOutputBinding/outputEval",
+        "type": [
+          "null",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "Evaluate an expression to generate the output value.  If `glob` was\nspecified, the value of `self` must be an array containing file objects\nthat were matched.  If no files were matched, `self` must be a zero\nlength array; if a single file was matched, the value of `self` is an\narray of a single element.  Additionally, if `loadContents` is `true`,\nthe File objects must include up to the first 64 KiB of file contents\nin the `contents` field.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CommandInputRecordField",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#InputRecordField",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputRecordSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputRecordSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputEnumSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputEnumSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputArraySchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputArraySchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputBinding",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandLineBinding"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CommandInputRecordSchema",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#InputRecordSchema",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputRecordField",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputRecordField"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CommandInputEnumSchema",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#InputEnumSchema",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputBinding",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandLineBinding"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CommandInputArraySchema",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#InputArraySchema",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputRecordSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputRecordSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputEnumSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputEnumSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputArraySchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputArraySchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputBinding",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandLineBinding"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CommandOutputRecordField",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#OutputRecordField",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputRecordSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputRecordSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputEnumSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputEnumSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputArraySchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputArraySchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputBinding",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputBinding"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CommandOutputRecordSchema",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#OutputRecordSchema",
+    "fields": [
+      {
+        "jsonldPredicate": "@id",
+        "name": "https://w3id.org/cwl/cwl#CommandOutputRecordSchema/name",
+        "type": [
+          "null",
+          "string"
+        ]
+      }
+    ],
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputRecordField",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputRecordField"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CommandOutputEnumSchema",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#OutputEnumSchema",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputRecordSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputRecordSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputEnumSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputEnumSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputArraySchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputArraySchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputBinding",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputBinding"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#CommandOutputArraySchema",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#OutputArraySchema",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputRecordSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputRecordSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputEnumSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputEnumSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputArraySchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputArraySchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputBinding",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputBinding"
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#CommandInputParameter",
+    "extends": "https://w3id.org/cwl/cwl#InputParameter",
+    "doc": "An input parameter for a CommandLineTool.",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputRecordSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputRecordSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputEnumSchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputEnumSchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputArraySchema",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputArraySchema"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputBinding",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandLineBinding"
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#CommandOutputParameter",
+    "extends": "https://w3id.org/cwl/cwl#OutputParameter",
+    "doc": "An output parameter for a CommandLineTool.",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputBinding",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputBinding"
+      }
+    ],
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandOutputParameter/type",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#CWLType",
+          "https://w3id.org/cwl/cwl#stdout",
+          "https://w3id.org/cwl/cwl#stderr",
+          "https://w3id.org/cwl/cwl#CommandOutputRecordSchema",
+          "https://w3id.org/cwl/cwl#CommandOutputEnumSchema",
+          "https://w3id.org/cwl/cwl#CommandOutputArraySchema",
+          "string",
+          {
+            "type": "array",
+            "items": [
+              "https://w3id.org/cwl/cwl#CWLType",
+              "https://w3id.org/cwl/cwl#CommandOutputRecordSchema",
+              "https://w3id.org/cwl/cwl#CommandOutputEnumSchema",
+              "https://w3id.org/cwl/cwl#CommandOutputArraySchema",
+              "string"
+            ]
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#CommandOutputParameter/type/type",
+          "_type": "@vocab",
+          "refScope": 2,
+          "typeDSL": true
+        },
+        "doc": "Specify valid types of data that may be assigned to this parameter.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#stdout",
+    "type": "enum",
+    "symbols": [
+      "https://w3id.org/cwl/cwl#stdout"
+    ],
+    "docParent": "https://w3id.org/cwl/cwl#CommandOutputParameter",
+    "doc": "Only valid as a `type` for a `CommandLineTool` output with no\n`outputBinding` set.\n\nThe following\n```\noutputs:\n  an_output_name:\n    type: stdout\n\nstdout: a_stdout_file\n```\nis equivalent to\n```\noutputs:\n  an_output_name:\n    type: File\n    streamable: true\n    outputBinding:\n      glob: a_stdout_file\n\nstdout: a_stdout_file\n```\n\nIf there is no `stdout` name provided, a random filename will be created.\nFor example, the following\n```\noutputs:\n  an_output_name:\n    type: stdout\n```\nis equivalent to\n```\noutputs:\n  an_output_name:\n    type: File\n    streamable: true\n    outputBinding:\n      glob: random_stdout_filenameABCDEFG\n\nstdout: random_stdout_filenameABCDEFG\n```\n"
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#stderr",
+    "type": "enum",
+    "symbols": [
+      "https://w3id.org/cwl/cwl#stderr"
+    ],
+    "docParent": "https://w3id.org/cwl/cwl#CommandOutputParameter",
+    "doc": "Only valid as a `type` for a `CommandLineTool` output with no\n`outputBinding` set.\n\nThe following\n```\noutputs:\n  an_output_name:\n  type: stderr\n\nstderr: a_stderr_file\n```\nis equivalent to\n```\noutputs:\n  an_output_name:\n    type: File\n    streamable: true\n    outputBinding:\n      glob: a_stderr_file\n\nstderr: a_stderr_file\n```\n\nIf there is no `stderr` name provided, a random filename will be created.\nFor example, the following\n```\noutputs:\n  an_output_name:\n    type: stderr\n```\nis equivalent to\n```\noutputs:\n  an_output_name:\n    type: File\n    streamable: true\n    outputBinding:\n      glob: random_stderr_filenameABCDEFG\n\nstderr: random_stderr_filenameABCDEFG\n```\n"
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#CommandLineTool",
+    "extends": "https://w3id.org/cwl/cwl#Process",
+    "documentRoot": true,
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#InputParameter",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandInputParameter"
+      },
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputParameter",
+        "specializeTo": "https://w3id.org/cwl/cwl#CommandOutputParameter"
+      }
+    ],
+    "doc": "This defines the schema of the CWL Command Line Tool Description document.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineTool/class",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        },
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#CommandLineTool/class/CommandLineTool_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#CommandLineTool"
+          ],
+          "type": "enum"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineTool/baseCommand",
+        "doc": "Specifies the program to execute.  If an array, the first element of\nthe array is the command to execute, and subsequent elements are\nmandatory command line arguments.  The elements in `baseCommand` must\nappear before any command line bindings from `inputBinding` or\n`arguments`.\n\nIf `baseCommand` is not provided or is an empty array, the first\nelement of the command line produced after processing `inputBinding` or\n`arguments` must be used as the program to execute.\n\nIf the program includes a path separator character it must\nbe an absolute path, otherwise it is an error.  If the program does not\ninclude a path separator, search the `$PATH` variable in the runtime\nenvironment of the workflow runner find the absolute path of the\nexecutable.\n",
+        "type": [
+          "null",
+          "string",
+          {
+            "type": "array",
+            "items": "string"
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#baseCommand",
+          "_container": "@list"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineTool/arguments",
+        "doc": "Command line bindings which are not directly associated with input parameters.\n",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": [
+              "string",
+              "https://w3id.org/cwl/cwl#Expression",
+              "https://w3id.org/cwl/cwl#CommandLineBinding"
+            ]
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#arguments",
+          "_container": "@list"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineTool/stdin",
+        "type": [
+          "null",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "A path to a file whose contents must be piped into the command's\nstandard input stream.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineTool/stderr",
+        "type": [
+          "null",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "jsonldPredicate": "https://w3id.org/cwl/cwl#stderr",
+        "doc": "Capture the command's standard error stream to a file written to\nthe designated output directory.\n\nIf `stderr` is a string, it specifies the file name to use.\n\nIf `stderr` is an expression, the expression is evaluated and must\nreturn a string with the file name to use to capture stderr.  If the\nreturn value is not a string, or the resulting path contains illegal\ncharacters (such as the path separator `/`) it is an error.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineTool/stdout",
+        "type": [
+          "null",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "jsonldPredicate": "https://w3id.org/cwl/cwl#stdout",
+        "doc": "Capture the command's standard output stream to a file written to\nthe designated output directory.\n\nIf `stdout` is a string, it specifies the file name to use.\n\nIf `stdout` is an expression, the expression is evaluated and must\nreturn a string with the file name to use to capture stdout.  If the\nreturn value is not a string, or the resulting path contains illegal\ncharacters (such as the path separator `/`) it is an error.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineTool/successCodes",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": "int"
+          }
+        ],
+        "doc": "Exit codes that indicate the process completed successfully.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineTool/temporaryFailCodes",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": "int"
+          }
+        ],
+        "doc": "Exit codes that indicate the process failed due to a possibly\ntemporary condition, where executing the process with the same\nruntime environment and inputs may produce different results.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#CommandLineTool/permanentFailCodes",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": "int"
+          }
+        ],
+        "doc": "Exit codes that indicate the process failed due to a permanent logic error, where executing the process with the same runtime environment and same inputs is expected to always fail."
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#DockerRequirement",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "Indicates that a workflow component should be run in a\n[Docker](http://docker.com) container, and specifies how to fetch or build\nthe image.\n\nIf a CommandLineTool lists `DockerRequirement` under\n`hints` (or `requirements`), it may (or must) be run in the specified Docker\ncontainer.\n\nThe platform must first acquire or install the correct Docker image as\nspecified by `dockerPull`, `dockerImport`, `dockerLoad` or `dockerFile`.\n\nThe platform must execute the tool in the container using `docker run` with\nthe appropriate Docker image and tool command line.\n\nThe workflow platform may provide input files and the designated output\ndirectory through the use of volume bind mounts.  The platform should rewrite\nfile paths in the input object to correspond to the Docker bind mounted\nlocations. That is, the platform should rewrite values in the parameter context\nsuch as `runtime.outdir`, `runtime.tmpdir` and others to be valid paths\nwithin the container.\n\nWhen running a tool contained in Docker, the workflow platform must not\nassume anything about the contents of the Docker container, such as the\npresence or absence of specific software, except to assume that the\ngenerated command line represents a valid command within the runtime\nenvironment of the container.\n\n## Interaction with other requirements\n\nIf [EnvVarRequirement](#EnvVarRequirement) is specified alongside a\nDockerRequirement, the environment variables must be provided to Docker\nusing `--env` or `--env-file` and interact with the container's preexisting\nenvironment as defined by Docker.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#DockerRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#DockerRequirement/class/DockerRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#DockerRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'DockerRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerPull",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "Specify a Docker image to retrieve using `docker pull`."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerLoad",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "Specify a HTTP URL from which to download a Docker image using `docker load`."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerFile",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "Supply the contents of a Dockerfile which will be built using `docker build`."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerImport",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "Provide HTTP URL to download and gunzip a Docker images using `docker import."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerImageId",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "The image id that will be used for `docker run`.  May be a\nhuman-readable image name or the image identifier hash.  May be skipped\nif `dockerPull` is specified, in which case the `dockerPull` image id\nmust be used.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#DockerRequirement/dockerOutputDirectory",
+        "type": [
+          "null",
+          "string"
+        ],
+        "doc": "Set the designated output directory to a specific location inside the\nDocker container.\n"
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#SoftwareRequirement",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "A list of software packages that should be configured in the environment of\nthe defined process.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#SoftwareRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#SoftwareRequirement/class/SoftwareRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#SoftwareRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'SoftwareRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#SoftwareRequirement/packages",
+        "type": {
+          "type": "array",
+          "items": "https://w3id.org/cwl/cwl#SoftwarePackage"
+        },
+        "doc": "The list of software to be configured.",
+        "jsonldPredicate": {
+          "mapSubject": "package",
+          "mapPredicate": "specs"
+        }
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#SoftwarePackage",
+    "type": "record",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#SoftwarePackage/package",
+        "type": "string",
+        "doc": "The name of the software to be made available. If the name is\ncommon, inconsistent, or otherwise ambiguous it should be combined with\none or more identifiers in the `specs` field.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#SoftwarePackage/version",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": "string"
+          }
+        ],
+        "doc": "The (optional) versions of the software that are known to be\ncompatible.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#SoftwarePackage/specs",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": "string"
+          }
+        ],
+        "doc": "One or more [IRI](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier)s\nidentifying resources for installing or enabling the software named in\nthe `package` field. Implementations may provide resolvers which map\nthese software identifer IRIs to some configuration action; or they can\nuse only the name from the `package` field on a best effort basis.\n\nFor example, the IRI https://packages.debian.org/bowtie could\nbe resolved with `apt-get install bowtie`. The IRI\nhttps://anaconda.org/bioconda/bowtie could be resolved with `conda\ninstall -c bioconda bowtie`.\n\nIRIs can also be system independent and used to map to a specific\nsoftware installation or selection mechanism.\nUsing [RRID](https://www.identifiers.org/rrid/) as an example:\nhttps://identifiers.org/rrid/RRID:SCR_005476\ncould be fulfilled using the above mentioned Debian or bioconda\npackage, a local installation managed by [Environement Modules](http://modules.sourceforge.net/),\nor any other mechanism the platform chooses. IRIs can also be from\nidentifer sources that are discipline specific yet still system\nindependent. As an example, the equivalent [ELIXIR Tools and Data\nService Registry](https://bio.tools) IRI to the previous RRID example is\nhttps://bio.tools/tool/bowtie2/version/2.2.8.\nIf supported by a given registry, implementations are encouraged to\nquery these system independent sofware identifier IRIs directly for\nlinks to packaging systems.\n\nA site specific IRI can be listed as well. For example, an academic\ncomputing cluster using Environement Modules could list the IRI\n`https://hpc.example.edu/modules/bowtie-tbb/1.22` to indicate that\n`module load bowtie-tbb/1.1.2` should be executed to make available\n`bowtie` version 1.1.2 compiled with the TBB library prior to running\nthe accompanying Workflow or CommandLineTool. Note that the example IRI\nis specific to a particular institution and computing environment as\nthe Environment Modules system does not have a common namespace or\nstandardized naming convention.\n\nThis last example is the least portable and should only be used if\nmechanisms based off of the `package` field or more generic IRIs are\nunavailable or unsuitable. While harmless to other sites, site specific\nsoftware IRIs should be left out of shared CWL descriptions to avoid\nclutter.\n",
+        "jsonldPredicate": {
+          "_type": "@id",
+          "noLinkCheck": true
+        }
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#Dirent",
+    "type": "record",
+    "doc": "Define a file or subdirectory that must be placed in the designated output\ndirectory prior to executing the command line tool.  May be the result of\nexecuting an expression, such as building a configuration file from a\ntemplate.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#Dirent/entryname",
+        "type": [
+          "null",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#entryname"
+        },
+        "doc": "The name of the file or subdirectory to create in the output directory.\nIf `entry` is a File or Directory, the `entryname` field overrides the value\nof `basename` of the File or Directory object.  Optional.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Dirent/entry",
+        "type": [
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#entry"
+        },
+        "doc": "If the value is a string literal or an expression which evaluates to a\nstring, a new file must be created with the string as the file contents.\n\nIf the value is an expression that evaluates to a `File` object, this\nindicates the referenced file should be added to the designated output\ndirectory prior to executing the tool.\n\nIf the value is an expression that evaluates to a `Dirent` object, this\nindicates that the File or Directory in `entry` should be added to the\ndesignated output directory with the name in `entryname`.\n\nIf `writable` is false, the file may be made available using a bind\nmount or file system link to avoid unnecessary copying of the input\nfile.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Dirent/writable",
+        "type": [
+          "null",
+          "boolean"
+        ],
+        "default": false,
+        "doc": "If true, the file or directory must be writable by the tool.  Changes\nto the file or directory must be isolated and not visible by any other\nCommandLineTool process.  This may be implemented by making a copy of\nthe original file or directory.  Default false (files and directories\nread-only by default).\n\nA directory marked as `writable: true` implies that all files and\nsubdirectories are recursively writable as well.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#InitialWorkDirRequirement",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "Define a list of files and subdirectories that must be created by the workflow platform in the designated output directory prior to executing the command line tool.",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#InitialWorkDirRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#InitialWorkDirRequirement/class/InitialWorkDirRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#InitialWorkDirRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "InitialWorkDirRequirement",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#InitialWorkDirRequirement/listing",
+        "type": [
+          {
+            "type": "array",
+            "items": [
+              "https://w3id.org/cwl/cwl#File",
+              "https://w3id.org/cwl/cwl#Directory",
+              "https://w3id.org/cwl/cwl#Dirent",
+              "string",
+              "https://w3id.org/cwl/cwl#Expression"
+            ]
+          },
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#listing"
+        },
+        "doc": "The list of files or subdirectories that must be placed in the\ndesignated output directory prior to executing the command line tool.\n\nMay be an expression.  If so, the expression return value must validate\nas `{type: array, items: [File, Directory]}`.\n\nFiles or Directories which are listed in the input parameters and\nappear in the `InitialWorkDirRequirement` listing must have their\n`path` set to their staged location in the designated output directory.\nIf the same File or Directory appears more than once in the\n`InitialWorkDirRequirement` listing, the implementation must choose\nexactly one value for `path`; how this value is chosen is undefined.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#EnvVarRequirement",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "Define a list of environment variables which will be set in the\nexecution environment of the tool.  See `EnvironmentDef` for details.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#EnvVarRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#EnvVarRequirement/class/EnvVarRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#EnvVarRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'EnvVarRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#EnvVarRequirement/envDef",
+        "type": {
+          "type": "array",
+          "items": "https://w3id.org/cwl/cwl#EnvironmentDef"
+        },
+        "doc": "The list of environment variables.",
+        "jsonldPredicate": {
+          "mapSubject": "envName",
+          "mapPredicate": "envValue"
+        }
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#ShellCommandRequirement",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "Modify the behavior of CommandLineTool to generate a single string\ncontaining a shell command line.  Each item in the argument list must be\njoined into a string separated by single spaces and quoted to prevent\nintepretation by the shell, unless `CommandLineBinding` for that argument\ncontains `shellQuote: false`.  If `shellQuote: false` is specified, the\nargument is joined into the command string without quoting, which allows\nthe use of shell metacharacters such as `|` for pipes.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#ShellCommandRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#ShellCommandRequirement/class/ShellCommandRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#ShellCommandRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'ShellCommandRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#ResourceRequirement",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "Specify basic hardware resource requirements.\n\n\"min\" is the minimum amount of a resource that must be reserved to schedule\na job. If \"min\" cannot be satisfied, the job should not be run.\n\n\"max\" is the maximum amount of a resource that the job shall be permitted\nto use. If a node has sufficient resources, multiple jobs may be scheduled\non a single node provided each job's \"max\" resource requirements are\nmet. If a job attempts to exceed its \"max\" resource allocation, an\nimplementation may deny additional resources, which may result in job\nfailure.\n\nIf \"min\" is specified but \"max\" is not, then \"max\" == \"min\"\nIf \"max\" is specified by \"min\" is not, then \"min\" == \"max\".\n\nIt is an error if max < min.\n\nIt is an error if the value of any of these fields is negative.\n\nIf neither \"min\" nor \"max\" is specified for a resource, an implementation may provide a default.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#ResourceRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#ResourceRequirement/class/ResourceRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#ResourceRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'ResourceRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#ResourceRequirement/coresMin",
+        "type": [
+          "null",
+          "int",
+          "long",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "Minimum reserved number of CPU cores"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#ResourceRequirement/coresMax",
+        "type": [
+          "null",
+          "int",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "Maximum reserved number of CPU cores"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#ResourceRequirement/ramMin",
+        "type": [
+          "null",
+          "int",
+          "long",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "Minimum reserved RAM in mebibytes (2**20)"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#ResourceRequirement/ramMax",
+        "type": [
+          "null",
+          "int",
+          "long",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "Maximum reserved RAM in mebibytes (2**20)"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#ResourceRequirement/tmpdirMin",
+        "type": [
+          "null",
+          "long",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "Minimum reserved filesystem based storage for the designated temporary directory, in mebibytes (2**20)"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#ResourceRequirement/tmpdirMax",
+        "type": [
+          "null",
+          "int",
+          "long",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "Maximum reserved filesystem based storage for the designated temporary directory, in mebibytes (2**20)"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#ResourceRequirement/outdirMin",
+        "type": [
+          "null",
+          "int",
+          "long",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "Minimum reserved filesystem based storage for the designated output directory, in mebibytes (2**20)"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#ResourceRequirement/outdirMax",
+        "type": [
+          "null",
+          "int",
+          "long",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "Maximum reserved filesystem based storage for the designated output directory, in mebibytes (2**20)"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#WorkflowDoc",
+    "type": "documentation",
+    "doc": [
+      "# Common Workflow Language (CWL) Workflow Description, v1.0.2\n\nThis version:\n  * https://w3id.org/cwl/v1.0/\n\nCurrent version:\n  * https://w3id.org/cwl/\n",
+      "\n\n",
+      "\n",
+      "\n\n",
+      "# Abstract\n\nOne way to define a workflow is: an analysis task represented by a\ndirected graph describing a sequence of operations that transform an\ninput data set to output. This specification defines the Common Workflow\nLanguage (CWL) Workflow description, a vendor-neutral standard for\nrepresenting workflows intended to be portable across a variety of\ncomputing platforms.\n",
+      "\n",
+      "\n## Introduction to CWL Workflow standard v1.0.2\n\nThis specification represents the third stable release from the CWL\ngroup.  Since the initial v1.0 release, v1.0.2 introduces the following\nupdates to the CWL Command Line Tool standard.  Documents should continue\nto use `cwlVersion: v1.0` and existing v1.0 documents remain valid,\nhowever CWL documents that relied on previously undefined or\nunderspecified behavior may have slightly different behavior in v1.0.2.\n\n  * 12 March 2017:\n    * Mark `default` as not required for link checking.\n    * Add note that recursive subworkflows is not allowed.\n    * Fix mistake in discussion of extracting field names from workflow step ids.\n  * 23 July 2017: (v1.0.1)\n    * Add clarification about scattering over empty arrays.\n    * Clarify interpretation of `secondaryFiles` on inputs.\n    * Expanded discussion of semantics of `File` and `Directory` types\n    * Fixed typo \"EMACScript\" to \"ECMAScript\"\n    * Clarified application of input parameter default values when the input is `null` or undefined.\n  * 10 August 2017: (v1.0.2)\n    * Clarify behavior resolving expressions in `secondaryFile`\n\nSince draft-3, v1.0 introduces the following changes and additions\nto the CWL Workflow standard:\n\n  * The `inputs` and `outputs` fields have been renamed `in` and `out`.\n  * Syntax simplifcations: denoted by the `map<>` syntax. Example: `in`\n    contains a list of items, each with an id. Now one can specify\n    a mapping of that identifier to the corresponding\n    `InputParameter`.\n    ```\n    in:\n     - id: one\n       type: string\n       doc: First input parameter\n     - id: two\n       type: int\n       doc: Second input parameter\n    ```\n    can be\n    ```\n    in:\n     one:\n      type: string\n      doc: First input parameter\n     two:\n      type: int\n      doc: Second input parameter\n    ```\n  * The common field `description` has been renamed to `doc`.\n\n## Purpose\n\nThe Common Workflow Language Command Line Tool Description express\nworkflows for data-intensive science, such as Bioinformatics, Chemistry,\nPhysics, and Astronomy.  This specification is intended to define a data\nand execution model for Workflows that can be implemented on top of a\nvariety of computing platforms, ranging from an individual workstation to\ncluster, grid, cloud, and high performance computing systems.\n",
+      "\n"
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#ExpressionToolOutputParameter",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#OutputParameter",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#ExpressionToolOutputParameter/type",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#CWLType",
+          "https://w3id.org/cwl/cwl#OutputRecordSchema",
+          "https://w3id.org/cwl/cwl#OutputEnumSchema",
+          "https://w3id.org/cwl/cwl#OutputArraySchema",
+          "string",
+          {
+            "type": "array",
+            "items": [
+              "https://w3id.org/cwl/cwl#CWLType",
+              "https://w3id.org/cwl/cwl#OutputRecordSchema",
+              "https://w3id.org/cwl/cwl#OutputEnumSchema",
+              "https://w3id.org/cwl/cwl#OutputArraySchema",
+              "string"
+            ]
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#ExpressionToolOutputParameter/type/type",
+          "_type": "@vocab",
+          "refScope": 2,
+          "typeDSL": true
+        },
+        "doc": "Specify valid types of data that may be assigned to this parameter.\n"
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#ExpressionTool",
+    "extends": "https://w3id.org/cwl/cwl#Process",
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputParameter",
+        "specializeTo": "https://w3id.org/cwl/cwl#ExpressionToolOutputParameter"
+      }
+    ],
+    "documentRoot": true,
+    "doc": "Execute an expression as a Workflow step.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#ExpressionTool/class",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        },
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#ExpressionTool/class/ExpressionTool_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#ExpressionTool"
+          ],
+          "type": "enum"
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#ExpressionTool/expression",
+        "type": [
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "doc": "The expression to execute.  The expression must return a JSON object which\nmatches the output parameters of the ExpressionTool.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#LinkMergeMethod",
+    "type": "enum",
+    "docParent": "https://w3id.org/cwl/cwl#WorkflowStepInput",
+    "doc": "The input link merge method, described in [WorkflowStepInput](#WorkflowStepInput).",
+    "symbols": [
+      "https://w3id.org/cwl/cwl#LinkMergeMethod/merge_nested",
+      "https://w3id.org/cwl/cwl#LinkMergeMethod/merge_flattened"
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#WorkflowOutputParameter",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#OutputParameter",
+    "docParent": "https://w3id.org/cwl/cwl#Workflow",
+    "doc": "Describe an output parameter of a workflow.  The parameter must be\nconnected to one or more parameters defined in the workflow that will\nprovide the value of the output parameter.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowOutputParameter/outputSource",
+        "doc": "Specifies one or more workflow parameters that supply the value of to\nthe output parameter.\n",
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#outputSource",
+          "_type": "@id",
+          "refScope": 1
+        },
+        "type": [
+          "null",
+          "string",
+          {
+            "type": "array",
+            "items": "string"
+          }
         ]
-    },
-    {
-        "type": "record",
-        "name": "https://w3id.org/cwl/cwl#StepInputExpressionRequirement",
-        "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
-        "doc": "Indicate that the workflow platform must support the `valueFrom` field\nof [WorkflowStepInput](#WorkflowStepInput).\n",
-        "fields": [
-            {
-                "name": "https://w3id.org/cwl/cwl#StepInputExpressionRequirement/class",
-                "type": "string",
-                "doc": "Always 'StepInputExpressionRequirement'",
-                "jsonldPredicate": {
-                    "_id": "@type",
-                    "_type": "@vocab"
-                }
-            }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowOutputParameter/linkMerge",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#LinkMergeMethod"
+        ],
+        "jsonldPredicate": "cwl:linkMerge",
+        "doc": "The method to use to merge multiple sources into a single array.\nIf not specified, the default method is \"merge_nested\".\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowOutputParameter/type",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#CWLType",
+          "https://w3id.org/cwl/cwl#OutputRecordSchema",
+          "https://w3id.org/cwl/cwl#OutputEnumSchema",
+          "https://w3id.org/cwl/cwl#OutputArraySchema",
+          "string",
+          {
+            "type": "array",
+            "items": [
+              "https://w3id.org/cwl/cwl#CWLType",
+              "https://w3id.org/cwl/cwl#OutputRecordSchema",
+              "https://w3id.org/cwl/cwl#OutputEnumSchema",
+              "https://w3id.org/cwl/cwl#OutputArraySchema",
+              "string"
+            ]
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#WorkflowOutputParameter/type/type",
+          "_type": "@vocab",
+          "refScope": 2,
+          "typeDSL": true
+        },
+        "doc": "Specify valid types of data that may be assigned to this parameter.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#Sink",
+    "type": "record",
+    "abstract": true,
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#Sink/source",
+        "doc": "Specifies one or more workflow parameters that will provide input to\nthe underlying step parameter.\n",
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#source",
+          "_type": "@id",
+          "refScope": 2
+        },
+        "type": [
+          "null",
+          "string",
+          {
+            "type": "array",
+            "items": "string"
+          }
         ]
-    }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Sink/linkMerge",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#LinkMergeMethod"
+        ],
+        "jsonldPredicate": "cwl:linkMerge",
+        "doc": "The method to use to merge multiple inbound links into a single array.\nIf not specified, the default method is \"merge_nested\".\n"
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#WorkflowStepInput",
+    "extends": "https://w3id.org/cwl/cwl#Sink",
+    "docParent": "https://w3id.org/cwl/cwl#WorkflowStep",
+    "doc": "The input of a workflow step connects an upstream parameter (from the\nworkflow inputs, or the outputs of other workflows steps) with the input\nparameters of the underlying step.\n\n## Input object\n\nA WorkflowStepInput object must contain an `id` field in the form\n`#fieldname` or `#prefix/fieldname`.  When the `id` field contains a slash\n`/` the field name consists of the characters following the final slash\n(the prefix portion may contain one or more slashes to indicate scope).\nThis defines a field of the workflow step input object with the value of\nthe `source` parameter(s).\n\n## Merging\n\nTo merge multiple inbound data links,\n[MultipleInputFeatureRequirement](#MultipleInputFeatureRequirement) must be specified\nin the workflow or workflow step requirements.\n\nIf the sink parameter is an array, or named in a [workflow\nscatter](#WorkflowStep) operation, there may be multiple inbound data links\nlisted in the `source` field.  The values from the input links are merged\ndepending on the method specified in the `linkMerge` field.  If not\nspecified, the default method is \"merge_nested\".\n\n* **merge_nested**\n\n  The input must be an array consisting of exactly one entry for each\n  input link.  If \"merge_nested\" is specified with a single link, the value\n  from the link must be wrapped in a single-item list.\n\n* **merge_flattened**\n\n  1. The source and sink parameters must be compatible types, or the source\n     type must be compatible with single element from the \"items\" type of\n     the destination array parameter.\n  2. Source parameters which are arrays are concatenated.\n     Source parameters which are single element types are appended as\n     single elements.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStepInput/id",
+        "type": "string",
+        "jsonldPredicate": "@id",
+        "doc": "A unique identifier for this workflow input parameter."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStepInput/default",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#CWLObjectType"
+        ],
+        "doc": "The default value for this parameter to use if either there is no\n`source` field, or the value produced by the `source` is `null`.  The\ndefault must be applied prior to scattering or evaluating `valueFrom`.\n",
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#default",
+          "_container": "@list",
+          "noLinkCheck": true
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStepInput/valueFrom",
+        "type": [
+          "null",
+          "string",
+          "https://w3id.org/cwl/cwl#Expression"
+        ],
+        "jsonldPredicate": "cwl:valueFrom",
+        "doc": "To use valueFrom, [StepInputExpressionRequirement](#StepInputExpressionRequirement) must\nbe specified in the workflow or workflow step requirements.\n\nIf `valueFrom` is a constant string value, use this as the value for\nthis input parameter.\n\nIf `valueFrom` is a parameter reference or expression, it must be\nevaluated to yield the actual value to be assiged to the input field.\n\nThe `self` value in the parameter reference or expression must be\n1. `null` if there is no `source` field\n2. the value of the parameter(s) specified in the `source` field when this\nworkflow input parameter **is not** specified in this workflow step's `scatter` field.\n3. an element of the parameter specified in the `source` field when this workflow input\nparameter **is** specified in this workflow step's `scatter` field.\n\nThe value of `inputs` in the parameter reference or expression must be\nthe input object to the workflow step after assigning the `source`\nvalues, applying `default`, and then scattering.  The order of\nevaluating `valueFrom` among step input parameters is undefined and the\nresult of evaluating `valueFrom` on a parameter must not be visible to\nevaluation of `valueFrom` on other parameters.\n"
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#WorkflowStepOutput",
+    "docParent": "https://w3id.org/cwl/cwl#WorkflowStep",
+    "doc": "Associate an output parameter of the underlying process with a workflow\nparameter.  The workflow parameter (given in the `id` field) be may be used\nas a `source` to connect with input parameters of other workflow steps, or\nwith an output parameter of the process.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStepOutput/id",
+        "type": "string",
+        "jsonldPredicate": "@id",
+        "doc": "A unique identifier for this workflow output parameter.  This is the\nidentifier to use in the `source` field of `WorkflowStepInput` to\nconnect the output value to downstream parameters.\n"
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#ScatterMethod",
+    "type": "enum",
+    "docParent": "https://w3id.org/cwl/cwl#WorkflowStep",
+    "doc": "The scatter method, as described in [workflow step scatter](#WorkflowStep).",
+    "symbols": [
+      "https://w3id.org/cwl/cwl#ScatterMethod/dotproduct",
+      "https://w3id.org/cwl/cwl#ScatterMethod/nested_crossproduct",
+      "https://w3id.org/cwl/cwl#ScatterMethod/flat_crossproduct"
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#WorkflowStep",
+    "type": "record",
+    "docParent": "https://w3id.org/cwl/cwl#Workflow",
+    "doc": "A workflow step is an executable element of a workflow.  It specifies the\nunderlying process implementation (such as `CommandLineTool` or another\n`Workflow`) in the `run` field and connects the input and output parameters\nof the underlying process to workflow parameters.\n\n# Scatter/gather\n\nTo use scatter/gather,\n[ScatterFeatureRequirement](#ScatterFeatureRequirement) must be specified\nin the workflow or workflow step requirements.\n\nA \"scatter\" operation specifies that the associated workflow step or\nsubworkflow should execute separately over a list of input elements.  Each\njob making up a scatter operation is independent and may be executed\nconcurrently.\n\nThe `scatter` field specifies one or more input parameters which will be\nscattered.  An input parameter may be listed more than once.  The declared\ntype of each input parameter is implicitly becomes an array of items of the\ninput parameter type.  If a parameter is listed more than once, it becomes\na nested array.  As a result, upstream parameters which are connected to\nscattered parameters must be arrays.\n\nAll output parameter types are also implicitly wrapped in arrays.  Each job\nin the scatter results in an entry in the output array.\n\nIf any scattered parameter runtime value is an empty array, all outputs are\nset to empty arrays and no work is done for the step, according to\napplicable scattering rules.\n\nIf `scatter` declares more than one input parameter, `scatterMethod`\ndescribes how to decompose the input into a discrete set of jobs.\n\n  * **dotproduct** specifies that each of the input arrays are aligned and one\n      element taken from each array to construct each job.  It is an error\n      if all input arrays are not the same length.\n\n  * **nested_crossproduct** specifies the Cartesian product of the inputs,\n      producing a job for every combination of the scattered inputs.  The\n      output must be nested arrays for each level of scattering, in the\n      order that the input arrays are listed in the `scatter` field.\n\n  * **flat_crossproduct** specifies the Cartesian product of the inputs,\n      producing a job for every combination of the scattered inputs.  The\n      output arrays must be flattened to a single level, but otherwise listed in the\n      order that the input arrays are listed in the `scatter` field.\n\n# Subworkflows\n\nTo specify a nested workflow as part of a workflow step,\n[SubworkflowFeatureRequirement](#SubworkflowFeatureRequirement) must be\nspecified in the workflow or workflow step requirements.\n\nIt is a fatal error if a workflow directly or indirectly invokes itself as\na subworkflow (recursive workflows are not allowed).\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStep/id",
+        "type": "string",
+        "jsonldPredicate": "@id",
+        "doc": "The unique identifier for this workflow step."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStep/in",
+        "type": {
+          "type": "array",
+          "items": "https://w3id.org/cwl/cwl#WorkflowStepInput"
+        },
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#in",
+          "mapSubject": "id",
+          "mapPredicate": "source"
+        },
+        "doc": "Defines the input parameters of the workflow step.  The process is ready to\nrun when all required input parameters are associated with concrete\nvalues.  Input parameters include a schema for each parameter which is\nused to validate the input object.  It may also be used build a user\ninterface for constructing the input object.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStep/out",
+        "type": [
+          {
+            "type": "array",
+            "items": [
+              "string",
+              "https://w3id.org/cwl/cwl#WorkflowStepOutput"
+            ]
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#out",
+          "_type": "@id",
+          "identity": true
+        },
+        "doc": "Defines the parameters representing the output of the process.  May be\nused to generate and/or validate the output object.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStep/requirements",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": "https://w3id.org/cwl/cwl#ProcessRequirement"
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#requirements",
+          "mapSubject": "class"
+        },
+        "doc": "Declares requirements that apply to either the runtime environment or the\nworkflow engine that must be met in order to execute this workflow step.  If\nan implementation cannot satisfy all requirements, or a requirement is\nlisted which is not recognized by the implementation, it is a fatal\nerror and the implementation must not attempt to run the process,\nunless overridden at user option.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStep/hints",
+        "type": [
+          "null",
+          {
+            "type": "array",
+            "items": "Any"
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#hints",
+          "noLinkCheck": true,
+          "mapSubject": "class"
+        },
+        "doc": "Declares hints applying to either the runtime environment or the\nworkflow engine that may be helpful in executing this workflow step.  It is\nnot an error if an implementation cannot satisfy all hints, however\nthe implementation may report a warning.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStep/label",
+        "type": [
+          "null",
+          "string"
+        ],
+        "jsonldPredicate": "rdfs:label",
+        "doc": "A short, human-readable label of this process object."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStep/doc",
+        "type": [
+          "null",
+          "string"
+        ],
+        "jsonldPredicate": "rdfs:comment",
+        "doc": "A long, human-readable description of this process object."
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStep/run",
+        "type": [
+          "string",
+          "https://w3id.org/cwl/cwl#Process"
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#run",
+          "_type": "@id"
+        },
+        "doc": "Specifies the process to run.\n"
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStep/scatter",
+        "type": [
+          "null",
+          "string",
+          {
+            "type": "array",
+            "items": "string"
+          }
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#scatter",
+          "_type": "@id",
+          "_container": "@list",
+          "refScope": 0
+        }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#WorkflowStep/scatterMethod",
+        "doc": "Required if `scatter` is an array of more than one element.\n",
+        "type": [
+          "null",
+          "https://w3id.org/cwl/cwl#ScatterMethod"
+        ],
+        "jsonldPredicate": {
+          "_id": "https://w3id.org/cwl/cwl#scatterMethod",
+          "_type": "@vocab"
+        }
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#Workflow",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#Process",
+    "documentRoot": true,
+    "specialize": [
+      {
+        "specializeFrom": "https://w3id.org/cwl/cwl#OutputParameter",
+        "specializeTo": "https://w3id.org/cwl/cwl#WorkflowOutputParameter"
+      }
+    ],
+    "doc": "A workflow describes a set of **steps** and the **dependencies** between\nthose steps.  When a step produces output that will be consumed by a\nsecond step, the first step is a dependency of the second step.\n\nWhen there is a dependency, the workflow engine must execute the preceding\nstep and wait for it to successfully produce output before executing the\ndependent step.  If two steps are defined in the workflow graph that\nare not directly or indirectly dependent, these steps are **independent**,\nand may execute in any order or execute concurrently.  A workflow is\ncomplete when all steps have been executed.\n\nDependencies between parameters are expressed using the `source` field on\n[workflow step input parameters](#WorkflowStepInput) and [workflow output\nparameters](#WorkflowOutputParameter).\n\nThe `source` field expresses the dependency of one parameter on another\nsuch that when a value is associated with the parameter specified by\n`source`, that value is propagated to the destination parameter.  When all\ndata links inbound to a given step are fufilled, the step is ready to\nexecute.\n\n## Workflow success and failure\n\nA completed step must result in one of `success`, `temporaryFailure` or\n`permanentFailure` states.  An implementation may choose to retry a step\nexecution which resulted in `temporaryFailure`.  An implementation may\nchoose to either continue running other steps of a workflow, or terminate\nimmediately upon `permanentFailure`.\n\n* If any step of a workflow execution results in `permanentFailure`, then\nthe workflow status is `permanentFailure`.\n\n* If one or more steps result in `temporaryFailure` and all other steps\ncomplete `success` or are not executed, then the workflow status is\n`temporaryFailure`.\n\n* If all workflow steps are executed and complete with `success`, then the\nworkflow status is `success`.\n\n# Extensions\n\n[ScatterFeatureRequirement](#ScatterFeatureRequirement) and\n[SubworkflowFeatureRequirement](#SubworkflowFeatureRequirement) are\navailable as standard [extensions](#Extensions_and_Metadata) to core\nworkflow semantics.\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#Workflow/class",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        },
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#Workflow/class/Workflow_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#Workflow"
+          ],
+        "type": "enum"
+      }
+      },
+      {
+        "name": "https://w3id.org/cwl/cwl#Workflow/steps",
+        "doc": "The individual steps that make up the workflow.  Each step is executed when all of its\ninput data links are fufilled.  An implementation may choose to execute\nthe steps in a different order than listed and/or execute steps\nconcurrently, provided that dependencies between steps are met.\n",
+        "type": [
+          {
+            "type": "array",
+            "items": "https://w3id.org/cwl/cwl#WorkflowStep"
+          }
+        ],
+        "jsonldPredicate": {
+          "mapSubject": "id"
+        }
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#SubworkflowFeatureRequirement",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "Indicates that the workflow platform must support nested workflows in\nthe `run` field of [WorkflowStep](#WorkflowStep).\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#SubworkflowFeatureRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#SubworkflowFeatureRequirement/class/SubworkflowFeatureRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#SubworkflowFeatureRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'SubworkflowFeatureRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#ScatterFeatureRequirement",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "Indicates that the workflow platform must support the `scatter` and\n`scatterMethod` fields of [WorkflowStep](#WorkflowStep).\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#ScatterFeatureRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#ScatterFeatureRequirement/class/ScatterFeatureRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#ScatterFeatureRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'ScatterFeatureRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      }
+    ]
+  },
+  {
+    "name": "https://w3id.org/cwl/cwl#MultipleInputFeatureRequirement",
+    "type": "record",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "Indicates that the workflow platform must support multiple inbound data links\nlisted in the `source` field of [WorkflowStepInput](#WorkflowStepInput).\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#MultipleInputFeatureRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#MultipleInputFeatureRequirement/class/MultipleInputFeatureRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#MultipleInputFeatureRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'MultipleInputFeatureRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      }
+    ]
+  },
+  {
+    "type": "record",
+    "name": "https://w3id.org/cwl/cwl#StepInputExpressionRequirement",
+    "extends": "https://w3id.org/cwl/cwl#ProcessRequirement",
+    "doc": "Indicate that the workflow platform must support the `valueFrom` field\nof [WorkflowStepInput](#WorkflowStepInput).\n",
+    "fields": [
+      {
+        "name": "https://w3id.org/cwl/cwl#StepInputExpressionRequirement/class",
+        "type": {
+          "name": "https://w3id.org/cwl/cwl#StepInputExpressionRequirement/class/StepInputExpressionRequirement_class",
+          "symbols": [
+            "https://w3id.org/cwl/cwl#StepInputExpressionRequirement"
+          ],
+          "type": "enum"
+        },
+        "doc": "Always 'StepInputExpressionRequirement'",
+        "jsonldPredicate": {
+          "_id": "@type",
+          "_type": "@vocab"
+        }
+      }
+    ]
+  }
 ]
diff --git a/schema_salad/tests/metaschema-pre.yml b/schema_salad/tests/metaschema-pre.yml
index 684f23ebd..d69e7c9de 100644
--- a/schema_salad/tests/metaschema-pre.yml
+++ b/schema_salad/tests/metaschema-pre.yml
@@ -3,7 +3,7 @@
         "name": "https://w3id.org/cwl/salad#Semantic_Annotations_for_Linked_Avro_Data",
         "type": "documentation",
         "doc": [
-            "# Semantic Annotations for Linked Avro Data (SALAD)\n\nAuthor:\n\n* Peter Amstutz <peter.amstutz@curii.com>, Curii Corporation\n\nContributors:\n\n* The developers of Apache Avro\n* The developers of JSON-LD\n* Neboj\u0161a Tijani\u0107 <nebojsa.tijanic@sbgenomics.com>, Seven Bridges Genomics\n* Michael R. Crusoe, ELIXIR-DE\n\n# Abstract\n\nSalad is a schema language for describing structured linked data documents\nin JSON or YAML documents.  A Salad schema provides rules for\npreprocessing, structural validation, and link checking for documents\ndescribed by a Salad schema.  Salad builds on JSON-LD and the Apache Avro\ndata serialization system, and extends Avro with features for rich data\nmodeling such as inheritance, template specialization, object identifiers,\nand object references.  Salad was developed to provide a bridge between the\nrecord oriented data modeling supported by Apache Avro and the Semantic\nWeb.\n\n# Status of This Document\n\nThis document is the product of the [Common Workflow Language working\ngroup](https://groups.google.com/forum/#!forum/common-workflow-language).  The\nlatest version of this document is available in the \"schema_salad\" repository at\n\nhttps://github.com/common-workflow-language/schema_salad\n\nThe products of the CWL working group (including this document) are made available\nunder the terms of the Apache License, version 2.0.\n\n<!--ToC-->\n\n# Introduction\n\nThe JSON data model is an extremely popular way to represent structured\ndata.  It is attractive because of its relative simplicity and is a\nnatural fit with the standard types of many programming languages.\nHowever, this simplicity means that basic JSON lacks expressive features\nuseful for working with complex data structures and document formats, such\nas schemas, object references, and namespaces.\n\nJSON-LD is a W3C standard providing a way to describe how to interpret a\nJSON document as Linked Data by means of a \"context\".  JSON-LD provides a\npowerful solution for representing object references and namespaces in JSON\nbased on standard web URIs, but is not itself a schema language.  Without a\nschema providing a well defined structure, it is difficult to process an\narbitrary JSON-LD document as idiomatic JSON because there are many ways to\nexpress the same data that are logically equivalent but structurally\ndistinct.\n\nSeveral schema languages exist for describing and validating JSON data,\nsuch as the Apache Avro data serialization system, however none understand\nlinked data.  As a result, to fully take advantage of JSON-LD to build the\nnext generation of linked data applications, one must maintain separate\nJSON schema, JSON-LD context, RDF schema, and human documentation, despite\nsignificant overlap of content and obvious need for these documents to stay\nsynchronized.\n\nSchema Salad is designed to address this gap.  It provides a schema\nlanguage and processing rules for describing structured JSON content\npermitting URI resolution and strict document validation.  The schema\nlanguage supports linked data through annotations that describe the linked\ndata interpretation of the content, enables generation of JSON-LD context\nand RDF schema, and production of RDF triples by applying the JSON-LD\ncontext.  The schema language also provides for robust support of inline\ndocumentation.\n\n## Introduction to v1.1\n\nThis is the third version of the Schema Salad specification.  It is\ndeveloped concurrently with v1.1 of the Common Workflow Language for use in\nspecifying the Common Workflow Language, however Schema Salad is intended to be\nuseful to a broader audience.  Compared to the v1.0 schema salad\nspecification, the following changes have been made:\n\n* Support for `default` values on record fields to specify default values\n* Add subscoped fields (fields which introduce a new inner scope for identifiers)\n* Add the *inVocab* flag (default true) to indicate if a type is added to the vocabulary of well known terms or must be prefixed\n* Add *secondaryFilesDSL* micro DSL (domain specific language) to convert text strings to a secondaryFiles record type used in CWL\n* The `$mixin` feature has been removed from the specification, as it\n  is poorly documented, not included in conformance testing,\n  and not widely supported.\n\n## Introduction to v1.2\n\nThis is the fourth version of the Schema Salad specification. It was created to\nease the development of extensions to CWL v1.2. The only change is that\ninherited records can narrow the types of fields if those fields are re-specified\nwith a matching jsonldPredicate.\n\n## References to Other Specifications\n\n**Javascript Object Notation (JSON)**: http://json.org\n\n**JSON Linked Data (JSON-LD)**: http://json-ld.org\n\n**YAML**: https://yaml.org/spec/1.2/spec.html\n\n**Avro**: https://avro.apache.org/docs/current/spec.html\n\n**Uniform Resource Identifier (URI) Generic Syntax**: https://tools.ietf.org/html/rfc3986)\n\n**Resource Description Framework (RDF)**: http://www.w3.org/RDF/\n\n**UTF-8**: https://www.ietf.org/rfc/rfc2279.txt)\n\n## Scope\n\nThis document describes the syntax, data model, algorithms, and schema\nlanguage for working with Salad documents.  It is not intended to document\na specific implementation of Salad, however it may serve as a reference for\nthe behavior of conforming implementations.\n\n## Terminology\n\nThe terminology used to describe Salad documents is defined in the Concepts\nsection of the specification. The terms defined in the following list are\nused in building those definitions and in describing the actions of a\nSalad implementation:\n\n**may**: Conforming Salad documents and Salad implementations are permitted but\nnot required to be interpreted as described.\n\n**must**: Conforming Salad documents and Salad implementations are required\nto be interpreted as described; otherwise they are in error.\n\n**error**: A violation of the rules of this specification; results are\nundefined. Conforming implementations may detect and report an error and may\nrecover from it.\n\n**fatal error**: A violation of the rules of this specification; results\nare undefined. Conforming implementations must not continue to process the\ndocument and may report an error.\n\n**at user option**: Conforming software may or must (depending on the modal verb in\nthe sentence) behave as described; if it does, it must provide users a means to\nenable or disable the behavior described.\n\n# Document model\n\n## Data concepts\n\nAn **object** is a data structure equivalent to the \"object\" type in JSON,\nconsisting of a unordered set of name/value pairs (referred to here as\n**fields**) and where the name is a string and the value is a string, number,\nboolean, array, or object.\n\nA **document** is a file containing a serialized object, or an array of\nobjects.\n\nA **document type** is a class of files that share a common structure and\nsemantics.\n\nA **document schema** is a formal description of the grammar of a document type.\n\nA **base URI** is a context-dependent URI used to resolve relative references.\n\nAn **identifier** is a URI that designates a single document or single\nobject within a document.\n\nA **vocabulary** is the set of symbolic field names and enumerated symbols defined\nby a document schema, where each term maps to absolute URI.\n\n## Syntax\n\nConforming Salad v1.1 documents are serialized and loaded using a\nsubset of YAML 1.2 syntax and UTF-8 text encoding.  Salad documents\nare written using the [JSON-compatible subset of YAML described in\nsection 10.2](https://yaml.org/spec/1.2/spec.html#id2803231).  The\nfollowing features of YAML must not be used in conforming Salad\ndocuments:\n\n* Use of explicit node tags with leading `!` or `!!`\n* Use of anchors with leading `&` and aliases with leading `*`\n* %YAML directives\n* %TAG directives\n\nIt is a fatal error if the document is not valid YAML.\n\nA Salad document must consist only of either a single root object or an\narray of objects.\n\n## Document context\n\n### Implied context\n\nThe implicit context consists of the vocabulary defined by the schema and\nthe base URI.  By default, the base URI must be the URI that was used to\nload the document.  It may be overridden by an explicit context.\n\n### Explicit context\n\nIf a document consists of a root object, this object may contain the\nfields `$base`, `$namespaces`, `$schemas`, and `$graph`:\n\n  * `$base`: Must be a string.  Set the base URI for the document used to\n    resolve relative references.\n\n  * `$namespaces`: Must be an object with strings as values.  The keys of\n    the object are namespace prefixes used in the document; the values of\n    the object are the prefix expansions.\n\n  * `$schemas`: Must be an array of strings.  This field may list URI\n    references to documents in RDF-XML format which will be queried for RDF\n    schema data.  The subjects and predicates described by the RDF schema\n    may provide additional semantic context for the document, and may be\n    used for validation of prefixed extension fields found in the document.\n\nOther directives beginning with `$` must be ignored.\n\n## Document graph\n\nIf a document consists of a single root object, this object may contain the\nfield `$graph`.  This field must be an array of objects.  If present, this\nfield holds the primary content of the document.  A document that consists\nof array of objects at the root is an implicit graph.\n\n## Document metadata\n\nIf a document consists of a single root object, metadata about the\ndocument, such as authorship, may be declared in the root object.\n\n## Document schema\n\nDocument preprocessing, link validation and schema validation require a\ndocument schema.  A schema may consist of:\n\n  * At least one record definition object which defines valid fields that\n  make up a record type.  Record field definitions include the valid types\n  that may be assigned to each field and annotations to indicate fields\n  that represent identifiers and links, described below in \"Semantic\n  Annotations\".\n\n  * Any number of enumerated type objects which define a set of finite set of symbols that are\n  valid value of the type.\n\n  * Any number of documentation objects which allow in-line documentation of the schema.\n\nThe schema for defining a salad schema (the metaschema) is described in\ndetail in the [Schema](#Schema) section.\n\n## Record field annotations\n\nIn a document schema, record field definitions may include the field\n`jsonldPredicate`, which may be either a string or object.  Implementations\nmust use the following document preprocessing of fields by the following\nrules:\n\n  * If the value of `jsonldPredicate` is `@id`, the field is an identifier\n  field.\n\n  * If the value of `jsonldPredicate` is an object, and that\n  object contains the field `_type` with the value `@id`, the\n  field is a link field.  If the field `jsonldPredicate` also\n  has the field `identity` with the value `true`, the field is\n  resolved with [identifier resolution](#Identifier_resolution).\n  Otherwise it is resolved with [link resolution](#Link_resolution).\n\n  * If the value of `jsonldPredicate` is an object which contains the\n  field `_type` with the value `@vocab`, the field value is subject to\n  [vocabulary resolution](#Vocabulary_resolution).\n\n## Document traversal\n\nTo perform document preprocessing, link validation and schema\nvalidation, the document must be traversed starting from the fields or\narray items of the root object or array and recursively visiting each child\nitem which contains an object or arrays.\n\n## Short names\n\nThe \"short name\" of a fully qualified identifier is the portion of\nthe identifier following the final slash `/` of either the fragment\nidentifier following `#` or the path portion, if there is no fragment.\nSome examples:\n\n* the short name of `http://example.com/foo` is `foo`\n* the short name of `http://example.com/#bar` is `bar`\n* the short name of `http://example.com/foo/bar` is `bar`\n* the short name of `http://example.com/foo#bar` is `bar`\n* the short name of `http://example.com/#foo/bar` is `bar`\n* the short name of `http://example.com/foo#bar/baz` is `baz`\n\n## Inheritance and specialization\n\nA record definition may inherit from one or more record definitions\nwith the `extends` field.  This copies the fields defined in the\nparent record(s) as the base for the new record.  A record definition\nmay `specialize` type declarations of the fields inherited from the\nbase record.  For each field inherited from the base record, any\ninstance of the type in `specializeFrom` is replaced with the type in\n`specializeTo`.  The type in `specializeTo` should extend from the\ntype in `specializeFrom`.\n\nA record definition may be `abstract`.  This means the record\ndefinition is not used for validation on its own, but may be extended\nby other definitions.  If an abstract type appears in a field\ndefinition, it is logically replaced with a union of all concrete\nsubtypes of the abstract type.  In other words, the field value does\nnot validate as the abstract type, but must validate as some concrete\ntype that inherits from the abstract type.\n\n# Document preprocessing\n\nAfter processing the explicit context (if any), document preprocessing\nbegins.  Starting from the document root, object fields values or array\nitems which contain objects or arrays are recursively traversed\ndepth-first.  For each visited object, field names, identifier fields, link\nfields, vocabulary fields, and `$import` and `$include` directives must be\nprocessed as described in this section.  The order of traversal of child\nnodes within a parent node is undefined.\n",
+            "# Semantic Annotations for Linked Avro Data (SALAD)\n\nAuthor:\n\n* Peter Amstutz <peter.amstutz@curii.com>, Curii Corporation\n\nContributors:\n\n* The developers of Apache Avro\n* The developers of JSON-LD\n* Neboj\u0161a Tijani\u0107 <nebojsa.tijanic@sbgenomics.com>, Seven Bridges Genomics\n* Michael R. Crusoe, ELIXIR-DE\n* Iacopo Colonnelli, University of Torino\n\n# Abstract\n\nSalad is a schema language for describing structured linked data documents\nin JSON or YAML documents.  A Salad schema provides rules for\npreprocessing, structural validation, and link checking for documents\ndescribed by a Salad schema.  Salad builds on JSON-LD and the Apache Avro\ndata serialization system, and extends Avro with features for rich data\nmodeling such as inheritance, template specialization, object identifiers,\nand object references.  Salad was developed to provide a bridge between the\nrecord oriented data modeling supported by Apache Avro and the Semantic\nWeb.\n\n# Status of This Document\n\nThis document is the product of the [Common Workflow Language working\ngroup](https://groups.google.com/forum/#!forum/common-workflow-language).  The\nlatest version of this document is available in the \"schema_salad\" repository at\n\nhttps://github.com/common-workflow-language/schema_salad\n\nThe products of the CWL working group (including this document) are made available\nunder the terms of the Apache License, version 2.0.\n\n<!--ToC-->\n\n# Introduction\n\nThe JSON data model is an extremely popular way to represent structured\ndata.  It is attractive because of its relative simplicity and is a\nnatural fit with the standard types of many programming languages.\nHowever, this simplicity means that basic JSON lacks expressive features\nuseful for working with complex data structures and document formats, such\nas schemas, object references, and namespaces.\n\nJSON-LD is a W3C standard providing a way to describe how to interpret a\nJSON document as Linked Data by means of a \"context\".  JSON-LD provides a\npowerful solution for representing object references and namespaces in JSON\nbased on standard web URIs, but is not itself a schema language.  Without a\nschema providing a well defined structure, it is difficult to process an\narbitrary JSON-LD document as idiomatic JSON because there are many ways to\nexpress the same data that are logically equivalent but structurally\ndistinct.\n\nSeveral schema languages exist for describing and validating JSON data,\nsuch as the Apache Avro data serialization system, however none understand\nlinked data.  As a result, to fully take advantage of JSON-LD to build the\nnext generation of linked data applications, one must maintain separate\nJSON schema, JSON-LD context, RDF schema, and human documentation, despite\nsignificant overlap of content and obvious need for these documents to stay\nsynchronized.\n\nSchema Salad is designed to address this gap.  It provides a schema\nlanguage and processing rules for describing structured JSON content\npermitting URI resolution and strict document validation.  The schema\nlanguage supports linked data through annotations that describe the linked\ndata interpretation of the content, enables generation of JSON-LD context\nand RDF schema, and production of RDF triples by applying the JSON-LD\ncontext.  The schema language also provides for robust support of inline\ndocumentation.\n\n## Introduction to v1.1\n\nThis is the third version of the Schema Salad specification.  It is\ndeveloped concurrently with v1.1 of the Common Workflow Language for use in\nspecifying the Common Workflow Language, however Schema Salad is intended to be\nuseful to a broader audience.  Compared to the v1.0 schema salad\nspecification, the following changes have been made:\n\n* Support for `default` values on record fields to specify default values\n* Add subscoped fields (fields which introduce a new inner scope for identifiers)\n* Add the *inVocab* flag (default true) to indicate if a type is added to the vocabulary of well known terms or must be prefixed\n* Add *secondaryFilesDSL* micro DSL (domain specific language) to convert text strings to a secondaryFiles record type used in CWL\n* The `$mixin` feature has been removed from the specification, as it\n  is poorly documented, not included in conformance testing,\n  and not widely supported.\n\n## Introduction to v1.2\n\nThis is the fourth version of the Schema Salad specification. It was created to\nease the development of extensions to CWL v1.2. The only change is that\ninherited records can narrow the types of fields if those fields are re-specified\nwith a matching jsonldPredicate.\n\n## Introduction to v1.3\n\nThis is the fifth version of the Schema Salad specification. It was created to\nenhance code generation by representing CWL data types as specific Python objects\n(instead of  relying on the generic `Any` type). The following changes have been made:\n\n* Support for the Avro `map` schema\n* Add named versions of the `map` and `union` Avro types\n* Support for nested named `union` type definitions\n\n## References to Other Specifications\n\n**Javascript Object Notation (JSON)**: http://json.org\n\n**JSON Linked Data (JSON-LD)**: http://json-ld.org\n\n**YAML**: https://yaml.org/spec/1.2/spec.html\n\n**Avro**: https://avro.apache.org/docs/current/spec.html\n\n**Uniform Resource Identifier (URI) Generic Syntax**: https://tools.ietf.org/html/rfc3986)\n\n**Resource Description Framework (RDF)**: http://www.w3.org/RDF/\n\n**UTF-8**: https://www.ietf.org/rfc/rfc2279.txt)\n\n## Scope\n\nThis document describes the syntax, data model, algorithms, and schema\nlanguage for working with Salad documents.  It is not intended to document\na specific implementation of Salad, however it may serve as a reference for\nthe behavior of conforming implementations.\n\n## Terminology\n\nThe terminology used to describe Salad documents is defined in the Concepts\nsection of the specification. The terms defined in the following list are\nused in building those definitions and in describing the actions of a\nSalad implementation:\n\n**may**: Conforming Salad documents and Salad implementations are permitted but\nnot required to be interpreted as described.\n\n**must**: Conforming Salad documents and Salad implementations are required\nto be interpreted as described; otherwise they are in error.\n\n**error**: A violation of the rules of this specification; results are\nundefined. Conforming implementations may detect and report an error and may\nrecover from it.\n\n**fatal error**: A violation of the rules of this specification; results\nare undefined. Conforming implementations must not continue to process the\ndocument and may report an error.\n\n**at user option**: Conforming software may or must (depending on the modal verb in\nthe sentence) behave as described; if it does, it must provide users a means to\nenable or disable the behavior described.\n\n# Document model\n\n## Data concepts\n\nAn **object** is a data structure equivalent to the \"object\" type in JSON,\nconsisting of a unordered set of name/value pairs (referred to here as\n**fields**) and where the name is a string and the value is a string, number,\nboolean, array, or object.\n\nA **document** is a file containing a serialized object, or an array of\nobjects.\n\nA **document type** is a class of files that share a common structure and\nsemantics.\n\nA **document schema** is a formal description of the grammar of a document type.\n\nA **base URI** is a context-dependent URI used to resolve relative references.\n\nAn **identifier** is a URI that designates a single document or single\nobject within a document.\n\nA **vocabulary** is the set of symbolic field names and enumerated symbols defined\nby a document schema, where each term maps to absolute URI.\n\n## Syntax\n\nConforming Salad v1.1 documents are serialized and loaded using a\nsubset of YAML 1.2 syntax and UTF-8 text encoding.  Salad documents\nare written using the [JSON-compatible subset of YAML described in\nsection 10.2](https://yaml.org/spec/1.2/spec.html#id2803231).  The\nfollowing features of YAML must not be used in conforming Salad\ndocuments:\n\n* Use of explicit node tags with leading `!` or `!!`\n* Use of anchors with leading `&` and aliases with leading `*`\n* %YAML directives\n* %TAG directives\n\nIt is a fatal error if the document is not valid YAML.\n\nA Salad document must consist only of either a single root object or an\narray of objects.\n\n## Document context\n\n### Implied context\n\nThe implicit context consists of the vocabulary defined by the schema and\nthe base URI.  By default, the base URI must be the URI that was used to\nload the document.  It may be overridden by an explicit context.\n\n### Explicit context\n\nIf a document consists of a root object, this object may contain the\nfields `$base`, `$namespaces`, `$schemas`, and `$graph`:\n\n  * `$base`: Must be a string.  Set the base URI for the document used to\n    resolve relative references.\n\n  * `$namespaces`: Must be an object with strings as values.  The keys of\n    the object are namespace prefixes used in the document; the values of\n    the object are the prefix expansions.\n\n  * `$schemas`: Must be an array of strings.  This field may list URI\n    references to documents in RDF-XML format which will be queried for RDF\n    schema data.  The subjects and predicates described by the RDF schema\n    may provide additional semantic context for the document, and may be\n    used for validation of prefixed extension fields found in the document.\n\nOther directives beginning with `$` must be ignored.\n\n## Document graph\n\nIf a document consists of a single root object, this object may contain the\nfield `$graph`.  This field must be an array of objects.  If present, this\nfield holds the primary content of the document.  A document that consists\nof array of objects at the root is an implicit graph.\n\n## Document metadata\n\nIf a document consists of a single root object, metadata about the\ndocument, such as authorship, may be declared in the root object.\n\n## Document schema\n\nDocument preprocessing, link validation and schema validation require a\ndocument schema.  A schema may consist of:\n\n  * At least one record definition object which defines valid fields that\n  make up a record type.  Record field definitions include the valid types\n  that may be assigned to each field and annotations to indicate fields\n  that represent identifiers and links, described below in \"Semantic\n  Annotations\".\n\n  * Any number of enumerated type objects which define a set of finite set of symbols that are\n  valid value of the type.\n\n  * Any number of documentation objects which allow in-line documentation of the schema.\n\nThe schema for defining a salad schema (the metaschema) is described in\ndetail in the [Schema](#Schema) section.\n\n## Record field annotations\n\nIn a document schema, record field definitions may include the field\n`jsonldPredicate`, which may be either a string or object.  Implementations\nmust use the following document preprocessing of fields by the following\nrules:\n\n  * If the value of `jsonldPredicate` is `@id`, the field is an identifier\n  field.\n\n  * If the value of `jsonldPredicate` is an object, and that\n  object contains the field `_type` with the value `@id`, the\n  field is a link field.  If the field `jsonldPredicate` also\n  has the field `identity` with the value `true`, the field is\n  resolved with [identifier resolution](#Identifier_resolution).\n  Otherwise it is resolved with [link resolution](#Link_resolution).\n\n  * If the value of `jsonldPredicate` is an object which contains the\n  field `_type` with the value `@vocab`, the field value is subject to\n  [vocabulary resolution](#Vocabulary_resolution).\n\n## Document traversal\n\nTo perform document preprocessing, link validation and schema\nvalidation, the document must be traversed starting from the fields or\narray items of the root object or array and recursively visiting each child\nitem which contains an object or arrays.\n\n## Short names\n\nThe \"short name\" of a fully qualified identifier is the portion of\nthe identifier following the final slash `/` of either the fragment\nidentifier following `#` or the path portion, if there is no fragment.\nSome examples:\n\n* the short name of `http://example.com/foo` is `foo`\n* the short name of `http://example.com/#bar` is `bar`\n* the short name of `http://example.com/foo/bar` is `bar`\n* the short name of `http://example.com/foo#bar` is `bar`\n* the short name of `http://example.com/#foo/bar` is `bar`\n* the short name of `http://example.com/foo#bar/baz` is `baz`\n\n## Inheritance and specialization\n\nA record definition may inherit from one or more record definitions\nwith the `extends` field.  This copies the fields defined in the\nparent record(s) as the base for the new record.  A record definition\nmay `specialize` type declarations of the fields inherited from the\nbase record.  For each field inherited from the base record, any\ninstance of the type in `specializeFrom` is replaced with the type in\n`specializeTo`.  The type in `specializeTo` should extend from the\ntype in `specializeFrom`.\n\nA record definition may be `abstract`.  This means the record\ndefinition is not used for validation on its own, but may be extended\nby other definitions.  If an abstract type appears in a field\ndefinition, it is logically replaced with a union of all concrete\nsubtypes of the abstract type.  In other words, the field value does\nnot validate as the abstract type, but must validate as some concrete\ntype that inherits from the abstract type.\n\n# Document preprocessing\n\nAfter processing the explicit context (if any), document preprocessing\nbegins.  Starting from the document root, object fields values or array\nitems which contain objects or arrays are recursively traversed\ndepth-first.  For each visited object, field names, identifier fields, link\nfields, vocabulary fields, and `$import` and `$include` directives must be\nprocessed as described in this section.  The order of traversal of child\nnodes within a parent node is undefined.\n",
             "## Field name resolution\n\nThe document schema declares the vocabulary of known field names.  During\npreprocessing traversal, field name in the document which are not part of\nthe schema vocabulary must be resolved to absolute URIs.  Under \"strict\"\nvalidation, it is an error for a document to include fields which are not\npart of the vocabulary and not resolvable to absolute URIs.  Fields names\nwhich are not part of the vocabulary are resolved using the following\nrules:\n\n* If a field name URI begins with a namespace prefix declared in the\ndocument context (`@context`) followed by a colon `:`, the prefix and\ncolon must be replaced by the namespace declared in `@context`.\n\n* If there is a vocabulary term which maps to the URI of a resolved\nfield, the field name must be replace with the vocabulary term.\n\n* If a field name URI is an absolute URI consisting of a scheme and path\nand is not part of the vocabulary, no processing occurs.\n\nField name resolution is not relative.  It must not be affected by the\nbase URI.\n\n### Field name resolution example\n\nGiven the following schema:\n\n```\n",
             "{\n  \"$namespaces\": {\n    \"acid\": \"http://example.com/acid#\"\n  },\n  \"$graph\": [{\n    \"name\": \"ExampleType\",\n    \"type\": \"record\",\n    \"documentRoot\": true,\n    \"fields\": [{\n      \"name\": \"base\",\n      \"type\": \"string\",\n      \"jsonldPredicate\": \"http://example.com/base\"\n    }]\n  }]\n}\n",
             "```\n\nProcess the following example:\n\n```\n",
@@ -143,6 +143,8 @@
                     "RecordSchema",
                     "EnumSchema",
                     "ArraySchema",
+                    "MapSchema",
+                    "UnionSchema",
                     "string",
                     {
                         "type": "array",
@@ -151,6 +153,8 @@
                             "RecordSchema",
                             "EnumSchema",
                             "ArraySchema",
+                            "MapSchema",
+                            "UnionSchema",
                             "string"
                         ]
                     }
@@ -259,6 +263,8 @@
                     "RecordSchema",
                     "EnumSchema",
                     "ArraySchema",
+                    "MapSchema",
+                    "UnionSchema",
                     "string",
                     {
                         "type": "array",
@@ -267,6 +273,8 @@
                             "RecordSchema",
                             "EnumSchema",
                             "ArraySchema",
+                            "MapSchema",
+                            "UnionSchema",
                             "string"
                         ]
                     }
@@ -298,6 +306,112 @@
             }
         ]
     },
+    {
+      "name": "https://w3id.org/cwl/salad#MapSchema",
+      "type": "record",
+      "fields": [
+        {
+          "doc": "Must be `map`",
+          "type": {
+            "type": "enum",
+            "name": "https://w3id.org/cwl/salad#MapSchema/type/Map_name",
+            "symbols": [
+              "https://w3id.org/cwl/salad#map"
+            ]
+          },
+          "jsonldPredicate": {
+            "_id": "https://w3id.org/cwl/salad#type",
+            "_type": "@vocab",
+            "typeDSL": true,
+            "refScope": 2
+          },
+          "name": "https://w3id.org/cwl/salad#MapSchema/type"
+        },
+        {
+          "type": [
+            "PrimitiveType",
+            "RecordSchema",
+            "EnumSchema",
+            "ArraySchema",
+            "MapSchema",
+            "UnionSchema",
+            "string",
+            {
+              "type": "array",
+              "items": [
+                "PrimitiveType",
+                "RecordSchema",
+                "EnumSchema",
+                "ArraySchema",
+                "MapSchema",
+                "UnionSchema",
+                "string"
+              ]
+            }
+          ],
+          "jsonldPredicate": {
+            "_id": "https://w3id.org/cwl/salad#values",
+            "_type": "@vocab",
+            "refScope": 2
+          },
+          "doc": "Defines the type of the map elements.",
+          "name": "https://w3id.org/cwl/salad#MapSchema/values"
+        }
+      ]
+    },
+    {
+      "name": "https://w3id.org/cwl/salad#UnionSchema",
+      "type": "record",
+      "fields": [
+        {
+          "type": [
+            "PrimitiveType",
+            "RecordSchema",
+            "EnumSchema",
+            "ArraySchema",
+            "MapSchema",
+            "UnionSchema",
+            "string",
+            {
+              "type": "array",
+              "items": [
+                "PrimitiveType",
+                "RecordSchema",
+                "EnumSchema",
+                "ArraySchema",
+                "MapSchema",
+                "UnionSchema",
+                "string"
+              ]
+            }
+          ],
+          "jsonldPredicate": {
+            "_id": "https://w3id.org/cwl/salad#names",
+            "_type": "@vocab",
+            "refScope": 2
+          },
+          "doc": "Defines the type of the union elements.",
+          "name": "https://w3id.org/cwl/salad#UnionSchema/names"
+        },
+        {
+          "doc": "Must be `union`",
+          "type": {
+            "type": "enum",
+            "name": "https://w3id.org/cwl/salad#UnionSchema/type/Union_name",
+            "symbols": [
+              "https://w3id.org/cwl/salad#union"
+            ]
+          },
+          "jsonldPredicate": {
+            "_id": "https://w3id.org/cwl/salad#type",
+            "_type": "@vocab",
+            "typeDSL": true,
+            "refScope": 2
+          },
+          "name": "https://w3id.org/cwl/salad#UnionSchema/type"
+        }
+      ]
+    },
     {
         "name": "https://w3id.org/cwl/salad#JsonldPredicate",
         "type": "record",
@@ -519,7 +633,9 @@
                     "null",
                     "boolean"
                 ],
-                "doc": "If true, indicates that the type is a valid at the document root.  At\nleast one type in a schema must be tagged with `documentRoot: true`.\n"
+                "doc": "If true, indicates that the type is a valid at the document root.  At\nleast one type in a schema must be tagged with `documentRoot: true`.\n",
+                "jsonldPredicate": "sld:documentRoot"
+
             }
         ]
     },
@@ -644,6 +760,41 @@
             }
         ]
     },
+    {
+      "name": "https://w3id.org/cwl/salad#SaladMapSchema",
+      "docParent": "https://w3id.org/cwl/salad#Schema",
+      "type": "record",
+      "extends": [
+        "https://w3id.org/cwl/salad#NamedType",
+        "https://w3id.org/cwl/salad#MapSchema",
+        "https://w3id.org/cwl/salad#SchemaDefinedType"
+      ],
+      "documentRoot": true,
+      "doc": "Define a map type.\n"
+    },
+    {
+        "name": "https://w3id.org/cwl/salad#SaladUnionSchema",
+        "docParent": "https://w3id.org/cwl/salad#Schema",
+        "type": "record",
+        "extends": [
+            "https://w3id.org/cwl/salad#NamedType",
+            "https://w3id.org/cwl/salad#UnionSchema",
+            "https://w3id.org/cwl/salad#DocType"
+        ],
+        "documentRoot": true,
+        "doc": "Define a union type.\n",
+        "fields" : [
+          {
+            "name": "https://w3id.org/cwl/salad#SaladUnionSchema/documentRoot",
+            "type": [
+              "null",
+              "boolean"
+            ],
+            "doc": "If true, indicates that the type is a valid at the document root.  At\nleast one type in a schema must be tagged with `documentRoot: true`.\n",
+            "jsonldPredicate": "sld:documentRoot"
+          }
+        ]
+    },
     {
         "name": "https://w3id.org/cwl/salad#Documentation",
         "type": "record",
diff --git a/schema_salad/tests/test_examples.py b/schema_salad/tests/test_examples.py
index 079706cb5..fe1e3583d 100644
--- a/schema_salad/tests/test_examples.py
+++ b/schema_salad/tests/test_examples.py
@@ -394,12 +394,12 @@ def test_secondaryFile_dsl_ref() -> None:
 
 def test_scoped_id() -> None:
     ldr = Loader({})
-    ctx = {
+    ctx: ContextType = {
         "id": "@id",
         "location": {"@id": "@id", "@type": "@id"},
         "bar": "http://example.com/bar",
         "ex": "http://example.com/",
-    }  # type: ContextType
+    }
     ldr.add_context(ctx)
 
     ra, _ = ldr.resolve_all(cmap({"id": "foo", "bar": {"id": "baz"}}), "http://example.com")
@@ -499,10 +499,10 @@ def test_yaml_datetime() -> None:
 
 def test_subscoped_id() -> None:
     ldr = Loader({})
-    ctx = {
+    ctx: ContextType = {
         "id": "@id",
         "bar": {"subscope": "bar"},
-    }  # type: ContextType
+    }
     ldr.add_context(ctx)
 
     ra, _ = ldr.resolve_all(cmap({"id": "foo", "bar": {"id": "baz"}}), "http://example.com")
@@ -586,7 +586,7 @@ def test_blank_node_id() -> None:
     # was not given.
 
     ldr = Loader({})
-    ctx = {"id": "@id"}  # type: ContextType
+    ctx: ContextType = {"id": "@id"}
     ldr.add_context(ctx)
 
     ra, _ = ldr.resolve_all(cmap({"id": "_:foo"}), "http://example.com")
@@ -607,7 +607,7 @@ def test_can_use_Any() -> None:
 
 def test_nullable_links() -> None:
     ldr = schema_salad.ref_resolver.Loader({})
-    ctx = {"link": {"@type": "@id"}}  # type: ContextType
+    ctx: ContextType = {"link": {"@type": "@id"}}
     ldr.add_context(ctx)
 
     ra, _ = ldr.resolve_all(cmap({"link": None}), "http://example.com", checklinks=True)
diff --git a/schema_salad/tests/test_fetch.py b/schema_salad/tests/test_fetch.py
index 5609aa34f..4a843b46e 100644
--- a/schema_salad/tests/test_fetch.py
+++ b/schema_salad/tests/test_fetch.py
@@ -62,7 +62,7 @@ def fetch_text(self, url: str, content_types: Optional[List[str]] = None) -> str
 """
         raise RuntimeError(f"Not foo.cwl, was {url}")
 
-    def check_exists(self, url):  # type: (str) -> bool
+    def check_exists(self, url: str) -> bool:
         return url == "baz:bar/foo.cwl"
 
     def urljoin(self, base: str, url: str) -> str:
diff --git a/schema_salad/tests/test_makedoc.py b/schema_salad/tests/test_makedoc.py
index a0ff265a6..02a959e72 100644
--- a/schema_salad/tests/test_makedoc.py
+++ b/schema_salad/tests/test_makedoc.py
@@ -239,5 +239,5 @@ def test_detect_changes_in_html(metaschema_doc: str, tmp_path: Path) -> None:
     with open(result, "w") as h:
         h.write(metaschema_doc)
     assert (
-        hasher.hexdigest() == "400bd8b4f3af31ffaeeb04b7780a140b0d26151645016e49a1a375964b67ea7e"
+        hasher.hexdigest() == "92730b4fd87186421b7e5f656f15280461beeb8d7bb853e405ae85238bb12580"
     ), result
diff --git a/schema_salad/tests/test_print_oneline.py b/schema_salad/tests/test_print_oneline.py
index 308dfd3cc..e563ac4ea 100644
--- a/schema_salad/tests/test_print_oneline.py
+++ b/schema_salad/tests/test_print_oneline.py
@@ -94,8 +94,8 @@ def test_print_oneline_for_errors_in_the_same_line() -> None:
             assert msgs[0].endswith(src + ":14:5: missing required field 'id'")
             assert msgs[1].endswith(
                 src + ":14:5: invalid field 'aa', expected one of: 'label', "
-                "'secondaryFiles', 'format', 'streamable', 'doc', 'id', "
-                "'outputBinding', 'type'"
+                "'secondaryFiles', 'streamable', 'doc', 'id', "
+                "'outputBinding', 'format', 'type'"
             )
             print("\n", e)
             raise
diff --git a/schema_salad/tests/test_schema/Base.yml b/schema_salad/tests/test_schema/Base.yml
new file mode 100644
index 000000000..2b636c032
--- /dev/null
+++ b/schema_salad/tests/test_schema/Base.yml
@@ -0,0 +1,483 @@
+$base: "https://w3id.org/cwl/cwl#"
+
+$namespaces:
+  cwl: "https://w3id.org/cwl/cwl#"
+  sld: "https://w3id.org/cwl/salad#"
+
+$graph:
+
+- name: CWLType
+  type: enum
+  extends: "sld:PrimitiveType"
+  symbols:
+    - cwl:File
+    - cwl:Directory
+  doc:
+    - "Extends primitive types with the concept of a file and directory as a builtin type."
+    - "File: A File object"
+    - "Directory: A Directory object"
+
+- name: CWLArraySchema
+  type: record
+  extends: "sld:ArraySchema"
+  fields:
+    items:
+      type:
+        - PrimitiveType
+        - CWLRecordSchema
+        - EnumSchema
+        - CWLArraySchema
+        - string
+        - type: array
+          items:
+            - PrimitiveType
+            - CWLRecordSchema
+            - EnumSchema
+            - CWLArraySchema
+            - string
+      jsonldPredicate:
+        _id: "sld:items"
+        _type: "@vocab"
+        refScope: 2
+      doc: "Defines the type of the array elements."
+
+- name: CWLRecordField
+  type: record
+  extends: "sld:RecordField"
+  fields:
+    - name: type
+      type:
+        - PrimitiveType
+        - CWLRecordSchema
+        - EnumSchema
+        - CWLArraySchema
+        - string
+        - type: array
+          items:
+            - PrimitiveType
+            - CWLRecordSchema
+            - EnumSchema
+            - CWLArraySchema
+            - string
+      jsonldPredicate:
+        _id: sld:type
+        _type: "@vocab"
+        typeDSL: true
+        refScope: 2
+      doc: |
+        The field type
+
+- name: CWLRecordSchema
+  type: record
+  extends: "sld:RecordSchema"
+  fields:
+    fields:
+      type: CWLRecordField[]?
+      jsonldPredicate:
+        _id: sld:fields
+        mapSubject: name
+        mapPredicate: type
+      doc: "Defines the fields of the record."
+
+- name: File
+  type: record
+  docParent: "#CWLType"
+  doc: |
+    Represents a file (or group of files when `secondaryFiles` is provided) that
+    will be accessible by tools using standard POSIX file system call API such as
+    open(2) and read(2).
+
+    Files are represented as objects with `class` of `File`.  File objects have
+    a number of properties that provide metadata about the file.
+
+    The `location` property of a File is a URI that uniquely identifies the
+    file.  Implementations must support the file:// URI scheme and may support
+    other schemes such as http://.  The value of `location` may also be a
+    relative reference, in which case it must be resolved relative to the URI
+    of the document it appears in.  Alternately to `location`, implementations
+    must also accept the `path` property on File, which must be a filesystem
+    path available on the same host as the CWL runner (for inputs) or the
+    runtime environment of a command line tool execution (for command line tool
+    outputs).
+
+    If no `location` or `path` is specified, a file object must specify
+    `contents` with the UTF-8 text content of the file.  This is a "file
+    literal".  File literals do not correspond to external resources, but are
+    created on disk with `contents` with when needed for a executing a tool.
+    Where appropriate, expressions can return file literals to define new files
+    on a runtime.  The maximum size of `contents` is 64 kilobytes.
+
+    The `basename` property defines the filename on disk where the file is
+    staged.  This may differ from the resource name.  If not provided,
+    `basename` must be computed from the last path part of `location` and made
+    available to expressions.
+
+    The `secondaryFiles` property is a list of File or Directory objects that
+    must be staged in the same directory as the primary file.  It is an error
+    for file names to be duplicated in `secondaryFiles`.
+
+    The `size` property is the size in bytes of the File.  It must be computed
+    from the resource and made available to expressions.  The `checksum` field
+    contains a cryptographic hash of the file content for use it verifying file
+    contents.  Implementations may, at user option, enable or disable
+    computation of the `checksum` field for performance or other reasons.
+    However, the ability to compute output checksums is required to pass the
+    CWL conformance test suite.
+
+    When executing a CommandLineTool, the files and secondary files may be
+    staged to an arbitrary directory, but must use the value of `basename` for
+    the filename.  The `path` property must be file path in the context of the
+    tool execution runtime (local to the compute node, or within the executing
+    container).  All computed properties should be available to expressions.
+    File literals also must be staged and `path` must be set.
+
+    When collecting CommandLineTool outputs, `glob` matching returns file paths
+    (with the `path` property) and the derived properties. This can all be
+    modified by `outputEval`.  Alternately, if the file `cwl.output.json` is
+    present in the output, `outputBinding` is ignored.
+
+    File objects in the output must provide either a `location` URI or a `path`
+    property in the context of the tool execution runtime (local to the compute
+    node, or within the executing container).
+
+    When evaluating an ExpressionTool, file objects must be referenced via
+    `location` (the expression tool does not have access to files on disk so
+    `path` is meaningless) or as file literals.  It is legal to return a file
+    object with an existing `location` but a different `basename`.  The
+    `loadContents` field of ExpressionTool inputs behaves the same as on
+    CommandLineTool inputs, however it is not meaningful on the outputs.
+
+    An ExpressionTool may forward file references from input to output by using
+    the same value for `location`.
+
+  fields:
+    - name: class
+      type:
+        type: enum
+        name: File_class
+        symbols:
+          - cwl:File
+      jsonldPredicate:
+        _id: "@type"
+        _type: "@vocab"
+      doc: Must be `File` to indicate this object describes a file.
+    - name: location
+      type: string?
+      doc: |
+        An IRI that identifies the file resource.  This may be a relative
+        reference, in which case it must be resolved using the base IRI of the
+        document.  The location may refer to a local or remote resource; the
+        implementation must use the IRI to retrieve file content.  If an
+        implementation is unable to retrieve the file content stored at a
+        remote resource (due to unsupported protocol, access denied, or other
+        issue) it must signal an error.
+
+        If the `location` field is not provided, the `contents` field must be
+        provided.  The implementation must assign a unique identifier for
+        the `location` field.
+
+        If the `path` field is provided but the `location` field is not, an
+        implementation may assign the value of the `path` field to `location`,
+        then follow the rules above.
+      jsonldPredicate:
+        _id: "@id"
+        _type: "@id"
+    - name: path
+      type: string?
+      doc: |
+        The local host path where the File is available when a CommandLineTool is
+        executed.  This field must be set by the implementation.  The final
+        path component must match the value of `basename`.  This field
+        must not be used in any other context.  The command line tool being
+        executed must be able to to access the file at `path` using the POSIX
+        `open(2)` syscall.
+
+        As a special case, if the `path` field is provided but the `location`
+        field is not, an implementation may assign the value of the `path`
+        field to `location`, and remove the `path` field.
+
+        If the `path` contains [POSIX shell metacharacters](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02)
+        (`|`,`&`, `;`, `<`, `>`, `(`,`)`, `$`,`` ` ``, `\`, `"`, `'`,
+        `<space>`, `<tab>`, and `<newline>`) or characters
+        [not allowed](http://www.iana.org/assignments/idna-tables-6.3.0/idna-tables-6.3.0.xhtml)
+        for [Internationalized Domain Names for Applications](https://tools.ietf.org/html/rfc6452)
+        then implementations may terminate the process with a
+        `permanentFailure`.
+      jsonldPredicate:
+        "_id": "cwl:path"
+        "_type": "@id"
+    - name: basename
+      type: string?
+      doc: |
+        The base name of the file, that is, the name of the file without any
+        leading directory path.  The base name must not contain a slash `/`.
+
+        If not provided, the implementation must set this field based on the
+        `location` field by taking the final path component after parsing
+        `location` as an IRI.  If `basename` is provided, it is not required to
+        match the value from `location`.
+
+        When this file is made available to a CommandLineTool, it must be named
+        with `basename`, i.e. the final component of the `path` field must match
+        `basename`.
+      jsonldPredicate: "cwl:basename"
+    - name: dirname
+      type: string?
+      doc: |
+        The name of the directory containing file, that is, the path leading up
+        to the final slash in the path such that `dirname + '/' + basename ==
+        path`.
+
+        The implementation must set this field based on the value of `path`
+        prior to evaluating parameter references or expressions in a
+        CommandLineTool document.  This field must not be used in any other
+        context.
+    - name: nameroot
+      type: string?
+      doc: |
+        The basename root such that `nameroot + nameext == basename`, and
+        `nameext` is empty or begins with a period and contains at most one
+        period.  For the purposess of path splitting leading periods on the
+        basename are ignored; a basename of `.cshrc` will have a nameroot of
+        `.cshrc`.
+
+        The implementation must set this field automatically based on the value
+        of `basename` prior to evaluating parameter references or expressions.
+    - name: nameext
+      type: string?
+      doc: |
+        The basename extension such that `nameroot + nameext == basename`, and
+        `nameext` is empty or begins with a period and contains at most one
+        period.  Leading periods on the basename are ignored; a basename of
+        `.cshrc` will have an empty `nameext`.
+
+        The implementation must set this field automatically based on the value
+        of `basename` prior to evaluating parameter references or expressions.
+    - name: checksum
+      type: string?
+      doc: |
+        Optional hash code for validating file integrity.  Currently must be in the form
+        "sha1$ + hexadecimal string" using the SHA-1 algorithm.
+    - name: size
+      type:
+        - "null"
+        - int
+        - long
+      doc: Optional file size
+    - name: "secondaryFiles"
+      type:
+        - "null"
+        - type: array
+          items: [File, Directory]
+      jsonldPredicate: "cwl:secondaryFiles"
+      doc: |
+        A list of additional files or directories that are associated with the
+        primary file and must be transferred alongside the primary file.
+        Examples include indexes of the primary file, or external references
+        which must be included when loading primary document.  A file object
+        listed in `secondaryFiles` may itself include `secondaryFiles` for
+        which the same rules apply.
+    - name: format
+      type: string?
+      jsonldPredicate:
+        _id: cwl:format
+        _type: "@id"
+        identity: true
+        noLinkCheck: true
+      doc: |
+        The format of the file: this must be an IRI of a concept node that
+        represents the file format, preferrably defined within an ontology.
+        If no ontology is available, file formats may be tested by exact match.
+
+        Reasoning about format compatability must be done by checking that an
+        input file format is the same, `owl:equivalentClass` or
+        `rdfs:subClassOf` the format required by the input parameter.
+        `owl:equivalentClass` is transitive with `rdfs:subClassOf`, e.g. if
+        `<B> owl:equivalentClass <C>` and `<B> owl:subclassOf <A>` then infer
+        `<C> owl:subclassOf <A>`.
+
+        File format ontologies may be provided in the "$schemas" metadata at the
+        root of the document.  If no ontologies are specified in `$schemas`, the
+        runtime may perform exact file format matches.
+    - name: contents
+      type: string?
+      doc: |
+        File contents literal.  Maximum of 64 KiB.
+
+        If neither `location` nor `path` is provided, `contents` must be
+        non-null.  The implementation must assign a unique identifier for the
+        `location` field.  When the file is staged as input to CommandLineTool,
+        the value of `contents` must be written to a file.
+
+        If `loadContents` of `inputBinding` or `outputBinding` is true and
+        `location` is valid, the implementation must read up to the first 64
+        KiB of text from the file and place it in the "contents" field.
+
+
+- name: Directory
+  type: record
+  docAfter: "#File"
+  doc: |
+    Represents a directory to present to a command line tool.
+
+    Directories are represented as objects with `class` of `Directory`.  Directory objects have
+    a number of properties that provide metadata about the directory.
+
+    The `location` property of a Directory is a URI that uniquely identifies
+    the directory.  Implementations must support the file:// URI scheme and may
+    support other schemes such as http://.  Alternately to `location`,
+    implementations must also accept the `path` property on Directory, which
+    must be a filesystem path available on the same host as the CWL runner (for
+    inputs) or the runtime environment of a command line tool execution (for
+    command line tool outputs).
+
+    A Directory object may have a `listing` field.  This is a list of File and
+    Directory objects that are contained in the Directory.  For each entry in
+    `listing`, the `basename` property defines the name of the File or
+    Subdirectory when staged to disk.  If `listing` is not provided, the
+    implementation must have some way of fetching the Directory listing at
+    runtime based on the `location` field.
+
+    If a Directory does not have `location`, it is a Directory literal.  A
+    Directory literal must provide `listing`.  Directory literals must be
+    created on disk at runtime as needed.
+
+    The resources in a Directory literal do not need to have any implied
+    relationship in their `location`.  For example, a Directory listing may
+    contain two files located on different hosts.  It is the responsibility of
+    the runtime to ensure that those files are staged to disk appropriately.
+    Secondary files associated with files in `listing` must also be staged to
+    the same Directory.
+
+    When executing a CommandLineTool, Directories must be recursively staged
+    first and have local values of `path` assigend.
+
+    Directory objects in CommandLineTool output must provide either a
+    `location` URI or a `path` property in the context of the tool execution
+    runtime (local to the compute node, or within the executing container).
+
+    An ExpressionTool may forward file references from input to output by using
+    the same value for `location`.
+
+    Name conflicts (the same `basename` appearing multiple times in `listing`
+    or in any entry in `secondaryFiles` in the listing) is a fatal error.
+
+  fields:
+    - name: class
+      type:
+        type: enum
+        name: Directory_class
+        symbols:
+          - cwl:Directory
+      jsonldPredicate:
+        _id: "@type"
+        _type: "@vocab"
+      doc: Must be `Directory` to indicate this object describes a Directory.
+    - name: location
+      type: string?
+      doc: |
+        An IRI that identifies the directory resource.  This may be a relative
+        reference, in which case it must be resolved using the base IRI of the
+        document.  The location may refer to a local or remote resource.  If
+        the `listing` field is not set, the implementation must use the
+        location IRI to retrieve directory listing.  If an implementation is
+        unable to retrieve the directory listing stored at a remote resource (due to
+        unsupported protocol, access denied, or other issue) it must signal an
+        error.
+
+        If the `location` field is not provided, the `listing` field must be
+        provided.  The implementation must assign a unique identifier for
+        the `location` field.
+
+        If the `path` field is provided but the `location` field is not, an
+        implementation may assign the value of the `path` field to `location`,
+        then follow the rules above.
+      jsonldPredicate:
+        _id: "@id"
+        _type: "@id"
+    - name: path
+      type: string?
+      doc: |
+        The local path where the Directory is made available prior to executing a
+        CommandLineTool.  This must be set by the implementation.  This field
+        must not be used in any other context.  The command line tool being
+        executed must be able to to access the directory at `path` using the POSIX
+        `opendir(2)` syscall.
+
+        If the `path` contains [POSIX shell metacharacters](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02)
+        (`|`,`&`, `;`, `<`, `>`, `(`,`)`, `$`,`` ` ``, `\`, `"`, `'`,
+        `<space>`, `<tab>`, and `<newline>`) or characters
+        [not allowed](http://www.iana.org/assignments/idna-tables-6.3.0/idna-tables-6.3.0.xhtml)
+        for [Internationalized Domain Names for Applications](https://tools.ietf.org/html/rfc6452)
+        then implementations may terminate the process with a
+        `permanentFailure`.
+      jsonldPredicate:
+        _id: "cwl:path"
+        _type: "@id"
+    - name: basename
+      type: string?
+      doc: |
+        The base name of the directory, that is, the name of the file without any
+        leading directory path.  The base name must not contain a slash `/`.
+
+        If not provided, the implementation must set this field based on the
+        `location` field by taking the final path component after parsing
+        `location` as an IRI.  If `basename` is provided, it is not required to
+        match the value from `location`.
+
+        When this file is made available to a CommandLineTool, it must be named
+        with `basename`, i.e. the final component of the `path` field must match
+        `basename`.
+      jsonldPredicate: "cwl:basename"
+    - name: listing
+      type:
+        - "null"
+        - type: array
+          items: [File, Directory]
+      doc: |
+        List of files or subdirectories contained in this directory.  The name
+        of each file or subdirectory is determined by the `basename` field of
+        each `File` or `Directory` object.  It is an error if a `File` shares a
+        `basename` with any other entry in `listing`.  If two or more
+        `Directory` object share the same `basename`, this must be treated as
+        equivalent to a single subdirectory with the listings recursively
+        merged.
+      jsonldPredicate:
+        _id: "cwl:listing"
+
+
+- name: CWLObjectType
+  type: union
+  names:
+    - boolean
+    - int
+    - long
+    - float
+    - double
+    - string
+    - File
+    - Directory
+    - type: array
+      items:
+        - "null"
+        - CWLObjectType
+    - type: map
+      values:
+        - "null"
+        - CWLObjectType
+  doc: |
+    Generic type representing a valid CWL object. It is used to represent
+    `default` values passed to CWL `InputParameter` and `WorkflowStepInput`
+    record fields.
+
+- name: CWLInputFile
+  type: map
+  values:
+    - "null"
+    - CWLObjectType
+  doc: |
+    Type representing a valid CWL input file as a `map<string, CWLObjectType>`.
+  jsonldPredicate:
+    _id: "cwl:inputfile"
+    _container: "@list"
+    noLinkCheck: true
diff --git a/schema_salad/tests/test_schema/CommandLineTool.yml b/schema_salad/tests/test_schema/CommandLineTool.yml
index da8b42995..5b9eb0f24 100644
--- a/schema_salad/tests/test_schema/CommandLineTool.yml
+++ b/schema_salad/tests/test_schema/CommandLineTool.yml
@@ -9,7 +9,7 @@ $graph:
   type: documentation
   doc:
     - |
-      # Common Workflow Language (CWL) Command Line Tool Description, v1.0
+      # Common Workflow Language (CWL) Command Line Tool Description, v1.0.2
 
       This version:
         * https://w3id.org/cwl/v1.0/
@@ -36,16 +36,42 @@ $graph:
     - {$include: intro.md}
 
     - |
-      ## Introduction to v1.0
+      ## Introduction to CWL Command Line Tool standard v1.0.2
 
-      This specification represents the first full release from the CWL group.
-      Since draft-3, version 1.0 introduces the following changes and additions:
+      This specification represents the third stable release from the CWL
+      group.  Since the initial v1.0 release, v1.0.2 introduces the following
+      updates to the CWL Command Line Tool standard.  Documents should continue
+      to use `cwlVersion: v1.0` and existing v1.0 documents remain valid,
+      however CWL documents that relied on previously undefined or
+      underspecified behavior may have slightly different behavior in v1.0.2.
+
+        * 13 July 2016: Mark `baseCommand` as optional and update descriptive text.
+        * 14 November 2016: Clarify [SoftwareRequirement](#SoftwareRequirement)
+          `spec` fields.
+        * 12 March 2017:
+          * Mark `default` as not required for link checking.
+          * Add note that files in InitialWorkDir must have path in output directory.
+          * Add note that writable: true applies recursively.
+        * 23 July 2017: (v1.0.1)
+          * Add clarification about scattering over empty arrays.
+          * Clarify interpretation of `secondaryFiles` on inputs.
+          * Expanded discussion of semantics of `File` and `Directory` types
+          * Fixed typo "EMACScript" to "ECMAScript"
+          * Clarified application of input parameter default values when the input is `null` or undefined.
+          * Clarified valid types and meaning of the format field on inputs versus outputs
+          * Clarify that command line arguments must not interpreted as shell except when shellQuote: false
+          * Clarify behavior of `entryname`
+        * 10 August 2017: (v1.0.2)
+          * Clarify behavior resolving expressions in `secondaryFile`
+
+      Since draft-3, v1.0 introduces the following changes and additions
+      to the CWL Command Line Tool standard:
 
         * The [Directory](#Directory) type.
         * Syntax simplifcations: denoted by the `map<>` syntax. Example: inputs
           contains a list of items, each with an id. Now one can specify
           a mapping of that identifier to the corresponding
-          `CommandInputParamater`.
+          `CommandInputParameter`.
           ```
           inputs:
            - id: one
@@ -74,11 +100,6 @@ $graph:
           dependencies of a tool.
         * The common `description` field has been renamed to `doc`.
 
-      ## Errata
-
-      Post v1.0 release changes to the spec.
-
-        * 13 July 2016: Mark `baseCommand` as optional and update descriptive text.
 
       ## Purpose
 
@@ -155,10 +176,14 @@ $graph:
       - **File**: Add `prefix` and the value of
         [`File.path`](#File) to the command line.
 
+      - **Directory**: Add `prefix` and the value of
+        [`Directory.path`](#Directory) to the command line.
+
       - **array**: If `itemSeparator` is specified, add `prefix` and the join
           the array into a single string with `itemSeparator` separating the
           items.  Otherwise first add `prefix`, then recursively process
           individual elements.
+          If the array is empty, it does not add anything to command line.
 
       - **object**: Add `prefix` only, and recursively add object fields for
           which `inputBinding` is specified.
@@ -168,12 +193,14 @@ $graph:
   fields:
     - name: position
       type: int?
+      default: 0
       doc: "The sorting key.  Default position is 0."
     - name: prefix
       type: string?
       doc: "Command line prefix to add before the value."
     - name: separate
       type: boolean?
+      default: true
       doc: |
         If true (default), then the prefix and value must be added as separate
         command line arguments; if false, prefix and value must be concatenated
@@ -196,18 +223,25 @@ $graph:
         If `valueFrom` is an expression, evaluate the expression to yield the
         actual value to use to build the command line and apply the binding
         rules above.  If the inputBinding is associated with an input
-        parameter, the value of `self` in the expression will be the value of the
-        input parameter.
+        parameter, the value of `self` in the expression will be the value of
+        the input parameter.  Input parameter defaults (as specified by the
+        `InputParameter.default` field) must be applied before evaluating the
+        expression.
 
         When a binding is part of the `CommandLineTool.arguments` field,
         the `valueFrom` field is required.
+
     - name: shellQuote
       type: boolean?
+      default: true
       doc: |
         If `ShellCommandRequirement` is in the requirements for the current command,
         this controls whether the value is quoted on the command line (default is true).
         Use `shellQuote: false` to inject metacharacters for operations such as pipes.
 
+        If `shellQuote` is true or not provided, the implementation must not
+        permit interpretation of any shell metacharacters or directives.
+
 - type: record
   name: CommandOutputBinding
   extends: OutputBinding
@@ -215,12 +249,13 @@ $graph:
     Describes how to generate an output parameter based on the files produced
     by a CommandLineTool.
 
-    The output parameter is generated by applying these operations in
-    the following order:
+    The output parameter value is generated by applying these operations in the
+    following order:
 
       - glob
       - loadContents
       - outputEval
+      - secondaryFiles
   fields:
     - name: glob
       type:
@@ -415,8 +450,8 @@ $graph:
     The following
     ```
     outputs:
-       an_output_name:
-       type: stdout
+      an_output_name:
+        type: stdout
 
     stdout: a_stdout_file
     ```
@@ -517,7 +552,11 @@ $graph:
       jsonldPredicate:
         "_id": "@type"
         "_type": "@vocab"
-      type: string
+      type:
+        type: enum
+        name: CommandLineTool_class
+        symbols:
+          - cwl:CommandLineTool
     - name: baseCommand
       doc: |
         Specifies the program to execute.  If an array, the first element of
@@ -621,9 +660,11 @@ $graph:
     the appropriate Docker image and tool command line.
 
     The workflow platform may provide input files and the designated output
-    directory through the use of volume bind mounts.  The platform may rewrite
+    directory through the use of volume bind mounts.  The platform should rewrite
     file paths in the input object to correspond to the Docker bind mounted
-    locations.
+    locations. That is, the platform should rewrite values in the parameter context
+    such as `runtime.outdir`, `runtime.tmpdir` and others to be valid paths
+    within the container.
 
     When running a tool contained in Docker, the workflow platform must not
     assume anything about the contents of the Docker container, such as the
@@ -640,7 +681,11 @@ $graph:
 
   fields:
     - name: class
-      type: string
+      type:
+        type: enum
+        name: DockerRequirement_class
+        symbols:
+          - cwl:DockerRequirement
       doc: "Always 'DockerRequirement'"
       jsonldPredicate:
         "_id": "@type"
@@ -679,7 +724,11 @@ $graph:
     the defined process.
   fields:
     - name: class
-      type: string
+      type:
+        type: enum
+        name: SoftwareRequirement_class
+        symbols:
+          - cwl:SoftwareRequirement
       doc: "Always 'SoftwareRequirement'"
       jsonldPredicate:
         "_id": "@type"
@@ -696,26 +745,60 @@ $graph:
   fields:
     - name: package
       type: string
-      doc: "The common name of the software to be configured."
+      doc: |
+        The name of the software to be made available. If the name is
+        common, inconsistent, or otherwise ambiguous it should be combined with
+        one or more identifiers in the `specs` field.
     - name: version
       type: string[]?
-      doc: "The (optional) version of the software to configured."
+      doc: |
+        The (optional) versions of the software that are known to be
+        compatible.
     - name: specs
       type: string[]?
+      jsonldPredicate: {_type: "@id", noLinkCheck: true}
       doc: |
-        Must be one or more IRIs identifying resources for installing or
-        enabling the software.  Implementations may provide resolvers which map
-        well-known software spec IRIs to some configuration action.
-
-        For example, an IRI `https://packages.debian.org/jessie/bowtie` could
-        be resolved with `apt-get install bowtie`.  An IRI
-        `https://anaconda.org/bioconda/bowtie` could be resolved with `conda
+        One or more [IRI](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier)s
+        identifying resources for installing or enabling the software named in
+        the `package` field. Implementations may provide resolvers which map
+        these software identifer IRIs to some configuration action; or they can
+        use only the name from the `package` field on a best effort basis.
+
+        For example, the IRI https://packages.debian.org/bowtie could
+        be resolved with `apt-get install bowtie`. The IRI
+        https://anaconda.org/bioconda/bowtie could be resolved with `conda
         install -c bioconda bowtie`.
 
-        Tools may also provide IRIs to index entries such as
-        [RRID](http://www.identifiers.org/rrid/), such as
-        `http://identifiers.org/rrid/RRID:SCR_005476`
-
+        IRIs can also be system independent and used to map to a specific
+        software installation or selection mechanism.
+        Using [RRID](https://www.identifiers.org/rrid/) as an example:
+        https://identifiers.org/rrid/RRID:SCR_005476
+        could be fulfilled using the above mentioned Debian or bioconda
+        package, a local installation managed by [Environement Modules](http://modules.sourceforge.net/),
+        or any other mechanism the platform chooses. IRIs can also be from
+        identifer sources that are discipline specific yet still system
+        independent. As an example, the equivalent [ELIXIR Tools and Data
+        Service Registry](https://bio.tools) IRI to the previous RRID example is
+        https://bio.tools/tool/bowtie2/version/2.2.8.
+        If supported by a given registry, implementations are encouraged to
+        query these system independent sofware identifier IRIs directly for
+        links to packaging systems.
+
+        A site specific IRI can be listed as well. For example, an academic
+        computing cluster using Environement Modules could list the IRI
+        `https://hpc.example.edu/modules/bowtie-tbb/1.22` to indicate that
+        `module load bowtie-tbb/1.1.2` should be executed to make available
+        `bowtie` version 1.1.2 compiled with the TBB library prior to running
+        the accompanying Workflow or CommandLineTool. Note that the example IRI
+        is specific to a particular institution and computing environment as
+        the Environment Modules system does not have a common namespace or
+        standardized naming convention.
+
+        This last example is the least portable and should only be used if
+        mechanisms based off of the `package` field or more generic IRIs are
+        unavailable or unsuitable. While harmless to other sites, site specific
+        software IRIs should be left out of shared CWL descriptions to avoid
+        clutter.
 
 - name: Dirent
   type: record
@@ -731,7 +814,8 @@ $graph:
         _id: cwl:entryname
       doc: |
         The name of the file or subdirectory to create in the output directory.
-        If `entry` is a File or Directory, this overrides `basename`.  Optional.
+        If `entry` is a File or Directory, the `entryname` field overrides the value
+        of `basename` of the File or Directory object.  Optional.
     - name: entry
       type: [string, Expression]
       jsonldPredicate:
@@ -753,6 +837,7 @@ $graph:
         file.
     - name: writable
       type: boolean?
+      default: false
       doc: |
         If true, the file or directory must be writable by the tool.  Changes
         to the file or directory must be isolated and not visible by any other
@@ -760,6 +845,9 @@ $graph:
         the original file or directory.  Default false (files and directories
         read-only by default).
 
+        A directory marked as `writable: true` implies that all files and
+        subdirectories are recursively writable as well.
+
 
 - name: InitialWorkDirRequirement
   type: record
@@ -770,7 +858,11 @@ $graph:
     command line tool.
   fields:
     - name: class
-      type: string
+      type:
+        type: enum
+        name: InitialWorkDirRequirement_class
+        symbols:
+          - cwl:InitialWorkDirRequirement
       doc: InitialWorkDirRequirement
       jsonldPredicate:
         "_id": "@type"
@@ -790,6 +882,13 @@ $graph:
         May be an expression.  If so, the expression return value must validate
         as `{type: array, items: [File, Directory]}`.
 
+        Files or Directories which are listed in the input parameters and
+        appear in the `InitialWorkDirRequirement` listing must have their
+        `path` set to their staged location in the designated output directory.
+        If the same File or Directory appears more than once in the
+        `InitialWorkDirRequirement` listing, the implementation must choose
+        exactly one value for `path`; how this value is chosen is undefined.
+
 
 - name: EnvVarRequirement
   type: record
@@ -799,7 +898,11 @@ $graph:
     execution environment of the tool.  See `EnvironmentDef` for details.
   fields:
     - name: class
-      type: string
+      type:
+        type: enum
+        name: EnvVarRequirement_class
+        symbols:
+          - cwl:EnvVarRequirement
       doc: "Always 'EnvVarRequirement'"
       jsonldPredicate:
         "_id": "@type"
@@ -825,7 +928,11 @@ $graph:
     the use of shell metacharacters such as `|` for pipes.
   fields:
     - name: class
-      type: string
+      type:
+        type: enum
+        name: ShellCommandRequirement_class
+        symbols:
+          - cwl:ShellCommandRequirement
       doc: "Always 'ShellCommandRequirement'"
       jsonldPredicate:
         "_id": "@type"
@@ -859,13 +966,17 @@ $graph:
 
   fields:
     - name: class
-      type: string
+      type:
+        type: enum
+        name: ResourceRequirement_class
+        symbols:
+          - cwl:ResourceRequirement
       doc: "Always 'ResourceRequirement'"
       jsonldPredicate:
         "_id": "@type"
         "_type": "@vocab"
     - name: coresMin
-      type: ["null", long, string, Expression]
+      type: ["null", int, long, string, Expression]
       doc: Minimum reserved number of CPU cores
 
     - name: coresMax
@@ -873,11 +984,11 @@ $graph:
       doc: Maximum reserved number of CPU cores
 
     - name: ramMin
-      type: ["null", long, string, Expression]
+      type: ["null", int, long, string, Expression]
       doc: Minimum reserved RAM in mebibytes (2**20)
 
     - name: ramMax
-      type: ["null", long, string, Expression]
+      type: ["null", int, long, string, Expression]
       doc: Maximum reserved RAM in mebibytes (2**20)
 
     - name: tmpdirMin
@@ -885,13 +996,13 @@ $graph:
       doc: Minimum reserved filesystem based storage for the designated temporary directory, in mebibytes (2**20)
 
     - name: tmpdirMax
-      type: ["null", long, string, Expression]
+      type: ["null", int, long, string, Expression]
       doc: Maximum reserved filesystem based storage for the designated temporary directory, in mebibytes (2**20)
 
     - name: outdirMin
-      type: ["null", long, string, Expression]
+      type: ["null", int, long, string, Expression]
       doc: Minimum reserved filesystem based storage for the designated output directory, in mebibytes (2**20)
 
     - name: outdirMax
-      type: ["null", long, string, Expression]
+      type: ["null", int, long, string, Expression]
       doc: Maximum reserved filesystem based storage for the designated output directory, in mebibytes (2**20)
diff --git a/schema_salad/tests/test_schema/Process.yml b/schema_salad/tests/test_schema/Process.yml
index 5e4d35222..ab6b7b09b 100644
--- a/schema_salad/tests/test_schema/Process.yml
+++ b/schema_salad/tests/test_schema/Process.yml
@@ -13,6 +13,8 @@ $graph:
 
 - $import: "metaschema_base.yml"
 
+- $import: Base.yml
+
 - name: BaseTypesDoc
   type: documentation
   doc: |
@@ -38,271 +40,6 @@ $graph:
     - cwl:v1.0.dev4
     - cwl:v1.0
 
-- name: CWLType
-  type: enum
-  extends: "sld:PrimitiveType"
-  symbols:
-    - cwl:File
-    - cwl:Directory
-  doc:
-    - "Extends primitive types with the concept of a file and directory as a builtin type."
-    - "File: A File object"
-    - "Directory: A Directory object"
-
-- name: File
-  type: record
-  docParent: "#CWLType"
-  doc: |
-    Represents a file (or group of files if `secondaryFiles` is specified) that
-    must be accessible by tools using standard POSIX file system call API such as
-    open(2) and read(2).
-  fields:
-    - name: class
-      type:
-        type: enum
-        name: File_class
-        symbols:
-          - cwl:File
-      jsonldPredicate:
-        _id: "@type"
-        _type: "@vocab"
-      doc: Must be `File` to indicate this object describes a file.
-    - name: location
-      type: string?
-      doc: |
-        An IRI that identifies the file resource.  This may be a relative
-        reference, in which case it must be resolved using the base IRI of the
-        document.  The location may refer to a local or remote resource; the
-        implementation must use the IRI to retrieve file content.  If an
-        implementation is unable to retrieve the file content stored at a
-        remote resource (due to unsupported protocol, access denied, or other
-        issue) it must signal an error.
-
-        If the `location` field is not provided, the `contents` field must be
-        provided.  The implementation must assign a unique identifier for
-        the `location` field.
-
-        If the `path` field is provided but the `location` field is not, an
-        implementation may assign the value of the `path` field to `location`,
-        then follow the rules above.
-      jsonldPredicate:
-        _id: "@id"
-        _type: "@id"
-    - name: path
-      type: string?
-      doc: |
-        The local host path where the File is available when a CommandLineTool is
-        executed.  This field must be set by the implementation.  The final
-        path component must match the value of `basename`.  This field
-        must not be used in any other context.  The command line tool being
-        executed must be able to to access the file at `path` using the POSIX
-        `open(2)` syscall.
-
-        As a special case, if the `path` field is provided but the `location`
-        field is not, an implementation may assign the value of the `path`
-        field to `location`, and remove the `path` field.
-
-        If the `path` contains [POSIX shell metacharacters](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02)
-        (`|`,`&`, `;`, `<`, `>`, `(`,`)`, `$`,`` ` ``, `\`, `"`, `'`,
-        `<space>`, `<tab>`, and `<newline>`) or characters
-        [not allowed](http://www.iana.org/assignments/idna-tables-6.3.0/idna-tables-6.3.0.xhtml)
-        for [Internationalized Domain Names for Applications](https://tools.ietf.org/html/rfc6452)
-        then implementations may terminate the process with a
-        `permanentFailure`.
-      jsonldPredicate:
-        "_id": "cwl:path"
-        "_type": "@id"
-    - name: basename
-      type: string?
-      doc: |
-        The base name of the file, that is, the name of the file without any
-        leading directory path.  The base name must not contain a slash `/`.
-
-        If not provided, the implementation must set this field based on the
-        `location` field by taking the final path component after parsing
-        `location` as an IRI.  If `basename` is provided, it is not required to
-        match the value from `location`.
-
-        When this file is made available to a CommandLineTool, it must be named
-        with `basename`, i.e. the final component of the `path` field must match
-        `basename`.
-      jsonldPredicate: "cwl:basename"
-    - name: dirname
-      type: string?
-      doc: |
-        The name of the directory containing file, that is, the path leading up
-        to the final slash in the path such that `dirname + '/' + basename ==
-        path`.
-
-        The implementation must set this field based on the value of `path`
-        prior to evaluating parameter references or expressions in a
-        CommandLineTool document.  This field must not be used in any other
-        context.
-    - name: nameroot
-      type: string?
-      doc: |
-        The basename root such that `nameroot + nameext == basename`, and
-        `nameext` is empty or begins with a period and contains at most one
-        period.  For the purposess of path splitting leading periods on the
-        basename are ignored; a basename of `.cshrc` will have a nameroot of
-        `.cshrc`.
-
-        The implementation must set this field automatically based on the value
-        of `basename` prior to evaluating parameter references or expressions.
-    - name: nameext
-      type: string?
-      doc: |
-        The basename extension such that `nameroot + nameext == basename`, and
-        `nameext` is empty or begins with a period and contains at most one
-        period.  Leading periods on the basename are ignored; a basename of
-        `.cshrc` will have an empty `nameext`.
-
-        The implementation must set this field automatically based on the value
-        of `basename` prior to evaluating parameter references or expressions.
-    - name: checksum
-      type: string?
-      doc: |
-        Optional hash code for validating file integrity.  Currently must be in the form
-        "sha1$ + hexadecimal string" using the SHA-1 algorithm.
-    - name: size
-      type: long?
-      doc: Optional file size
-    - name: "secondaryFiles"
-      type:
-        - "null"
-        - type: array
-          items: [File, Directory]
-      jsonldPredicate: "cwl:secondaryFiles"
-      doc: |
-        A list of additional files that are associated with the primary file
-        and must be transferred alongside the primary file.  Examples include
-        indexes of the primary file, or external references which must be
-        included when loading primary document.  A file object listed in
-        `secondaryFiles` may itself include `secondaryFiles` for which the same
-        rules apply.
-    - name: format
-      type: string?
-      jsonldPredicate:
-        _id: cwl:format
-        _type: "@id"
-        identity: true
-      doc: |
-        The format of the file: this must be an IRI of a concept node that
-        represents the file format, preferrably defined within an ontology.
-        If no ontology is available, file formats may be tested by exact match.
-
-        Reasoning about format compatability must be done by checking that an
-        input file format is the same, `owl:equivalentClass` or
-        `rdfs:subClassOf` the format required by the input parameter.
-        `owl:equivalentClass` is transitive with `rdfs:subClassOf`, e.g. if
-        `<B> owl:equivalentClass <C>` and `<B> owl:subclassOf <A>` then infer
-        `<C> owl:subclassOf <A>`.
-
-        File format ontologies may be provided in the "$schema" metadata at the
-        root of the document.  If no ontologies are specified in `$schema`, the
-        runtime may perform exact file format matches.
-    - name: contents
-      type: string?
-      doc: |
-        File contents literal.  Maximum of 64 KiB.
-
-        If neither `location` nor `path` is provided, `contents` must be
-        non-null.  The implementation must assign a unique identifier for the
-        `location` field.  When the file is staged as input to CommandLineTool,
-        the value of `contents` must be written to a file.
-
-        If `loadContents` of `inputBinding` or `outputBinding` is true and
-        `location` is valid, the implementation must read up to the first 64
-        KiB of text from the file and place it in the "contents" field.
-
-
-- name: Directory
-  type: record
-  docAfter: "#File"
-  doc: |
-    Represents a directory to present to a command line tool.
-  fields:
-    - name: class
-      type:
-        type: enum
-        name: Directory_class
-        symbols:
-          - cwl:Directory
-      jsonldPredicate:
-        _id: "@type"
-        _type: "@vocab"
-      doc: Must be `Directory` to indicate this object describes a Directory.
-    - name: location
-      type: string?
-      doc: |
-        An IRI that identifies the directory resource.  This may be a relative
-        reference, in which case it must be resolved using the base IRI of the
-        document.  The location may refer to a local or remote resource.  If
-        the `listing` field is not set, the implementation must use the
-        location IRI to retrieve directory listing.  If an implementation is
-        unable to retrieve the directory listing stored at a remote resource (due to
-        unsupported protocol, access denied, or other issue) it must signal an
-        error.
-
-        If the `location` field is not provided, the `listing` field must be
-        provided.  The implementation must assign a unique identifier for
-        the `location` field.
-
-        If the `path` field is provided but the `location` field is not, an
-        implementation may assign the value of the `path` field to `location`,
-        then follow the rules above.
-      jsonldPredicate:
-        _id: "@id"
-        _type: "@id"
-    - name: path
-      type: string?
-      doc: |
-        The local path where the Directory is made available prior to executing a
-        CommandLineTool.  This must be set by the implementation.  This field
-        must not be used in any other context.  The command line tool being
-        executed must be able to to access the directory at `path` using the POSIX
-        `opendir(2)` syscall.
-
-        If the `path` contains [POSIX shell metacharacters](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02)
-        (`|`,`&`, `;`, `<`, `>`, `(`,`)`, `$`,`` ` ``, `\`, `"`, `'`,
-        `<space>`, `<tab>`, and `<newline>`) or characters
-        [not allowed](http://www.iana.org/assignments/idna-tables-6.3.0/idna-tables-6.3.0.xhtml)
-        for [Internationalized Domain Names for Applications](https://tools.ietf.org/html/rfc6452)
-        then implementations may terminate the process with a
-        `permanentFailure`.
-      jsonldPredicate:
-        _id: "cwl:path"
-        _type: "@id"
-    - name: basename
-      type: string?
-      doc: |
-        The base name of the directory, that is, the name of the file without any
-        leading directory path.  The base name must not contain a slash `/`.
-
-        If not provided, the implementation must set this field based on the
-        `location` field by taking the final path component after parsing
-        `location` as an IRI.  If `basename` is provided, it is not required to
-        match the value from `location`.
-
-        When this file is made available to a CommandLineTool, it must be named
-        with `basename`, i.e. the final component of the `path` field must match
-        `basename`.
-      jsonldPredicate: "cwl:basename"
-    - name: listing
-      type:
-        - "null"
-        - type: array
-          items: [File, Directory]
-      doc: |
-        List of files or subdirectories contained in this directory.  The name
-        of each file or subdirectory is determined by the `basename` field of
-        each `File` or `Directory` object.  It is an error if a `File` shares a
-        `basename` with any other entry in `listing`.  If two or more
-        `Directory` object share the same `basename`, this must be treated as
-        equivalent to a single subdirectory with the listings recursively
-        merged.
-      jsonldPredicate:
-        _id: "cwl:listing"
 
 - name: SchemaBase
   type: record
@@ -335,13 +72,29 @@ $graph:
       doc: |
         Only valid when `type: File` or is an array of `items: File`.
 
-        Describes files that must be included alongside the primary file(s).
+        Provides a pattern or expression specifying files or directories that
+        must be included alongside the primary file.  All listed secondary
+        files must be present.  An implementation may fail workflow execution
+        if an expected secondary file does not exist.
 
         If the value is an expression, the value of `self` in the expression
-        must be the primary input or output File to which this binding applies.
-
-        If the value is a string, it specifies that the following pattern
-        should be applied to the primary file:
+        must be the primary input or output File object to which this binding
+        applies.  The `basename`, `nameroot` and `nameext` fields must be
+        present in `self`.  For `CommandLineTool` outputs the `path` field must
+        also be present.  The expression must return a filename string relative
+        to the path to the primary File, a File or Directory object with either
+        `path` or `location` and `basename` fields set, or an array consisting
+        of strings or File or Directory objects.  It is legal to reference an
+        unchanged File or Directory object taken from input as a secondaryFile.
+
+        To work on non-filename-preserving storage systems, portable tool
+        descriptions should avoid constructing new values from `location`, but
+        should construct relative references using `basename` or `nameroot`
+        instead.
+
+        If a value in `secondaryFiles` is a string that is not an expression,
+        it specifies that the following pattern should be applied to the path
+        of the primary file to yield a filename relative to the primary File:
 
           1. If string begins with one or more caret `^` characters, for each
             caret, remove the last file extension from the path (the last
@@ -349,30 +102,9 @@ $graph:
             extensions, the path is unchanged.
           2. Append the remainder of the string to the end of the file path.
 
-    - name: format
-      type:
-        - "null"
-        - string
-        - type: array
-          items: string
-        - Expression
-      jsonldPredicate:
-        _id: cwl:format
-        _type: "@id"
-        identity: true
-      doc: |
-        Only valid when `type: File` or is an array of `items: File`.
-
-        For input parameters, this must be one or more IRIs of concept nodes
-        that represents file formats which are allowed as input to this
-        parameter, preferrably defined within an ontology.  If no ontology is
-        available, file formats may be tested by exact match.
-
-        For output parameters, this is the file format that will be assigned to
-        the output parameter.
-
     - name: streamable
       type: boolean?
+      default: false
       doc: |
         Only valid when `type: File` or is an array of `items: File`.
 
@@ -435,13 +167,13 @@ $graph:
 
 - name: InputRecordField
   type: record
-  extends: "sld:RecordField"
+  extends: CWLRecordField
   specialize:
-    - specializeFrom: "sld:RecordSchema"
+    - specializeFrom: CWLRecordSchema
       specializeTo: InputRecordSchema
     - specializeFrom: "sld:EnumSchema"
       specializeTo: InputEnumSchema
-    - specializeFrom: "sld:ArraySchema"
+    - specializeFrom: CWLArraySchema
       specializeTo: InputArraySchema
     - specializeFrom: "sld:PrimitiveType"
       specializeTo: CWLType
@@ -457,9 +189,9 @@ $graph:
 
 - name: InputRecordSchema
   type: record
-  extends: ["sld:RecordSchema", InputSchema]
+  extends: [CWLRecordSchema, InputSchema]
   specialize:
-    - specializeFrom: "sld:RecordField"
+    - specializeFrom: CWLRecordField
       specializeTo: InputRecordField
   fields:
     - name: name
@@ -481,13 +213,13 @@ $graph:
 
 - name: InputArraySchema
   type: record
-  extends: ["sld:ArraySchema", InputSchema]
+  extends: [CWLArraySchema, InputSchema]
   specialize:
-    - specializeFrom: "sld:RecordSchema"
+    - specializeFrom: CWLRecordSchema
       specializeTo: InputRecordSchema
     - specializeFrom: "sld:EnumSchema"
       specializeTo: InputEnumSchema
-    - specializeFrom: "sld:ArraySchema"
+    - specializeFrom: CWLArraySchema
       specializeTo: InputArraySchema
     - specializeFrom: "sld:PrimitiveType"
       specializeTo: CWLType
@@ -499,13 +231,13 @@ $graph:
 
 - name: OutputRecordField
   type: record
-  extends: "sld:RecordField"
+  extends: CWLRecordField
   specialize:
-    - specializeFrom: "sld:RecordSchema"
+    - specializeFrom: CWLRecordSchema
       specializeTo: OutputRecordSchema
     - specializeFrom: "sld:EnumSchema"
       specializeTo: OutputEnumSchema
-    - specializeFrom: "sld:ArraySchema"
+    - specializeFrom: CWLArraySchema
       specializeTo: OutputArraySchema
     - specializeFrom: "sld:PrimitiveType"
       specializeTo: CWLType
@@ -517,10 +249,10 @@ $graph:
 
 - name: OutputRecordSchema
   type: record
-  extends: ["sld:RecordSchema", "#OutputSchema"]
+  extends: [CWLRecordSchema, "#OutputSchema"]
   docParent: "#OutputParameter"
   specialize:
-    - specializeFrom: "sld:RecordField"
+    - specializeFrom: CWLRecordField
       specializeTo: OutputRecordField
 
 
@@ -535,14 +267,14 @@ $graph:
 
 - name: OutputArraySchema
   type: record
-  extends: ["sld:ArraySchema", OutputSchema]
+  extends: [CWLArraySchema, OutputSchema]
   docParent: "#OutputParameter"
   specialize:
-    - specializeFrom: "sld:RecordSchema"
+    - specializeFrom: CWLRecordSchema
       specializeTo: OutputRecordSchema
     - specializeFrom: "sld:EnumSchema"
       specializeTo: OutputEnumSchema
-    - specializeFrom: "sld:ArraySchema"
+    - specializeFrom: CWLArraySchema
       specializeTo: OutputArraySchema
     - specializeFrom: "sld:PrimitiveType"
       specializeTo: CWLType
@@ -561,6 +293,27 @@ $graph:
       jsonldPredicate: "@id"
       doc: "The unique identifier for this parameter object."
 
+    - name: format
+      type:
+        - "null"
+        - string
+        - type: array
+          items: string
+        - Expression
+      jsonldPredicate:
+        _id: cwl:format
+        _type: "@id"
+        identity: true
+        noLinkCheck: true
+      doc: |
+        Only valid when `type: File` or is an array of `items: File`.
+
+        This must be one or more IRIs of concept nodes
+        that represents file formats which are allowed as input to this
+        parameter, preferrably defined within an ontology.  If no ontology is
+        available, file formats may be tested by exact match.
+
+
     - name: inputBinding
       type: InputBinding?
       jsonldPredicate: "cwl:inputBinding"
@@ -569,11 +322,16 @@ $graph:
         into a concrete form for execution, such as command line parameters.
 
     - name: default
-      type: Any?
-      jsonldPredicate: "cwl:default"
+      type: CWLObjectType?
+      jsonldPredicate:
+        _id: "cwl:default"
+        _container: "@list"
+        noLinkCheck: true
       doc: |
-        The default value for this parameter if not provided in the input
-        object.
+        The default value to use for this parameter if the parameter is missing
+        from the input object, or if the value of the parameter in the input
+        object is `null`.  Default values are applied before evaluating expressions
+        (e.g. dependent `valueFrom` fields).
 
     - name: type
       type:
@@ -611,6 +369,22 @@ $graph:
       jsonldPredicate: "cwl:outputBinding"
       doc: |
         Describes how to handle the outputs of a process.
+    - name: format
+      type:
+        - "null"
+        - string
+        - Expression
+      jsonldPredicate:
+        _id: cwl:format
+        _type: "@id"
+        identity: true
+        noLinkCheck: true
+      doc: |
+        Only valid when `type: File` or is an array of `items: File`.
+
+        This is the file format that will be assigned to
+        the output parameter.
+
 
 
 - type: record
@@ -653,6 +427,13 @@ $graph:
         values.  Input parameters include a schema for each parameter which is
         used to validate the input object.  It may also be used to build a user
         interface for constructing the input object.
+
+        When accepting an input object, all input parameters must have a value.
+        If an input parameter is missing from the input object, it must be
+        assigned a value of `null` (or the value of `default` for that
+        parameter, if provided) for the purposes of validation and evaluation
+        of expressions.
+
     - name: outputs
       type:
         type: array
@@ -677,7 +458,10 @@ $graph:
         error and the implementation must not attempt to run the process,
         unless overridden at user option.
     - name: hints
-      type: Any[]?
+      type:
+        - "null"
+        - type: array
+          items: [ProcessRequirement, Any]
       doc: |
         Declares hints applying to either the runtime environment or the
         workflow engine that may be helpful in executing this process.  It is
@@ -713,7 +497,11 @@ $graph:
     interpolatation.
   fields:
     - name: class
-      type: string
+      type:
+        type: enum
+        name: InlineJavascriptRequirement_class
+        symbols:
+          - cwl:InlineJavascriptRequirement
       doc: "Always 'InlineJavascriptRequirement'"
       jsonldPredicate:
         "_id": "@type"
@@ -739,7 +527,11 @@ $graph:
     to earlier schema definitions.
   fields:
     - name: class
-      type: string
+      type:
+        type: enum
+        name: SchemaDefRequirement_class
+        symbols:
+          - cwl:SchemaDefRequirement
       doc: "Always 'SchemaDefRequirement'"
       jsonldPredicate:
         "_id": "@type"
diff --git a/schema_salad/tests/test_schema/Workflow.yml b/schema_salad/tests/test_schema/Workflow.yml
index 387b25cba..e410de8d5 100644
--- a/schema_salad/tests/test_schema/Workflow.yml
+++ b/schema_salad/tests/test_schema/Workflow.yml
@@ -10,7 +10,7 @@ $graph:
   type: documentation
   doc:
     - |
-      # Common Workflow Language (CWL) Workflow Description, v1.0
+      # Common Workflow Language (CWL) Workflow Description, v1.0.2
 
       This version:
         * https://w3id.org/cwl/v1.0/
@@ -34,10 +34,30 @@ $graph:
 
     - |
 
-      ## Introduction to v1.0
-
-      This specification represents the first full release from the CWL group.
-      Since draft-3, this draft introduces the following changes and additions:
+      ## Introduction to CWL Workflow standard v1.0.2
+
+      This specification represents the third stable release from the CWL
+      group.  Since the initial v1.0 release, v1.0.2 introduces the following
+      updates to the CWL Command Line Tool standard.  Documents should continue
+      to use `cwlVersion: v1.0` and existing v1.0 documents remain valid,
+      however CWL documents that relied on previously undefined or
+      underspecified behavior may have slightly different behavior in v1.0.2.
+
+        * 12 March 2017:
+          * Mark `default` as not required for link checking.
+          * Add note that recursive subworkflows is not allowed.
+          * Fix mistake in discussion of extracting field names from workflow step ids.
+        * 23 July 2017: (v1.0.1)
+          * Add clarification about scattering over empty arrays.
+          * Clarify interpretation of `secondaryFiles` on inputs.
+          * Expanded discussion of semantics of `File` and `Directory` types
+          * Fixed typo "EMACScript" to "ECMAScript"
+          * Clarified application of input parameter default values when the input is `null` or undefined.
+        * 10 August 2017: (v1.0.2)
+          * Clarify behavior resolving expressions in `secondaryFile`
+
+      Since draft-3, v1.0 introduces the following changes and additions
+      to the CWL Workflow standard:
 
         * The `inputs` and `outputs` fields have been renamed `in` and `out`.
         * Syntax simplifcations: denoted by the `map<>` syntax. Example: `in`
@@ -117,7 +137,11 @@ $graph:
       jsonldPredicate:
         "_id": "@type"
         "_type": "@vocab"
-      type: string
+      type:
+        type: enum
+        name: ExpressionTool_class
+        symbols:
+          - cwl:ExpressionTool
     - name: expression
       type: [string, Expression]
       doc: |
@@ -149,7 +173,7 @@ $graph:
       jsonldPredicate:
         "_id": "cwl:outputSource"
         "_type": "@id"
-        refScope: 0
+        refScope: 1
       type:
         - string?
         - string[]?
@@ -218,10 +242,11 @@ $graph:
     ## Input object
 
     A WorkflowStepInput object must contain an `id` field in the form
-    `#fieldname` or `#stepname.fieldname`.  When the `id` field contains a
-    period `.` the field name consists of the characters following the final
-    period.  This defines a field of the workflow step input object with the
-    value of the `source` parameter(s).
+    `#fieldname` or `#prefix/fieldname`.  When the `id` field contains a slash
+    `/` the field name consists of the characters following the final slash
+    (the prefix portion may contain one or more slashes to indicate scope).
+    This defines a field of the workflow step input object with the value of
+    the `source` parameter(s).
 
     ## Merging
 
@@ -256,11 +281,15 @@ $graph:
       jsonldPredicate: "@id"
       doc: "A unique identifier for this workflow input parameter."
     - name: default
-      type: ["null", Any]
+      type: CWLObjectType?
       doc: |
-        The default value for this parameter if there is no `source`
-        field.
-      jsonldPredicate: "cwl:default"
+        The default value for this parameter to use if either there is no
+        `source` field, or the value produced by the `source` is `null`.  The
+        default must be applied prior to scattering or evaluating `valueFrom`.
+      jsonldPredicate:
+        _id: "cwl:default"
+        _container: "@list"
+        noLinkCheck: true
     - name: valueFrom
       type:
         - "null"
@@ -277,16 +306,19 @@ $graph:
         If `valueFrom` is a parameter reference or expression, it must be
         evaluated to yield the actual value to be assiged to the input field.
 
-        The `self` value of in the parameter reference or expression must be
-        the value of the parameter(s) specified in the `source` field, or
-        null if there is no `source` field.
+        The `self` value in the parameter reference or expression must be
+        1. `null` if there is no `source` field
+        2. the value of the parameter(s) specified in the `source` field when this
+        workflow input parameter **is not** specified in this workflow step's `scatter` field.
+        3. an element of the parameter specified in the `source` field when this workflow input
+        parameter **is** specified in this workflow step's `scatter` field.
 
         The value of `inputs` in the parameter reference or expression must be
         the input object to the workflow step after assigning the `source`
-        values and then scattering.  The order of evaluating `valueFrom` among
-        step input parameters is undefined and the result of evaluating
-        `valueFrom` on a parameter must not be visible to evaluation of
-        `valueFrom` on other parameters.
+        values, applying `default`, and then scattering.  The order of
+        evaluating `valueFrom` among step input parameters is undefined and the
+        result of evaluating `valueFrom` on a parameter must not be visible to
+        evaluation of `valueFrom` on other parameters.
 
 
 - type: record
@@ -339,13 +371,18 @@ $graph:
 
     The `scatter` field specifies one or more input parameters which will be
     scattered.  An input parameter may be listed more than once.  The declared
-    type of each input parameter is implicitly wrapped in an array for each
-    time it appears in the `scatter` field.  As a result, upstream parameters
-    which are connected to scattered parameters may be arrays.
+    type of each input parameter is implicitly becomes an array of items of the
+    input parameter type.  If a parameter is listed more than once, it becomes
+    a nested array.  As a result, upstream parameters which are connected to
+    scattered parameters must be arrays.
 
     All output parameter types are also implicitly wrapped in arrays.  Each job
     in the scatter results in an entry in the output array.
 
+    If any scattered parameter runtime value is an empty array, all outputs are
+    set to empty arrays and no work is done for the step, according to
+    applicable scattering rules.
+
     If `scatter` declares more than one input parameter, `scatterMethod`
     describes how to decompose the input into a discrete set of jobs.
 
@@ -369,6 +406,9 @@ $graph:
     [SubworkflowFeatureRequirement](#SubworkflowFeatureRequirement) must be
     specified in the workflow or workflow step requirements.
 
+    It is a fatal error if a workflow directly or indirectly invokes itself as
+    a subworkflow (recursive workflows are not allowed).
+
   fields:
     - name: id
       type: string
@@ -465,7 +505,7 @@ $graph:
     those steps.  When a step produces output that will be consumed by a
     second step, the first step is a dependency of the second step.
 
-    When there is a dependency, the workflow engine must execute the preceeding
+    When there is a dependency, the workflow engine must execute the preceding
     step and wait for it to successfully produce output before executing the
     dependent step.  If two steps are defined in the workflow graph that
     are not directly or indirectly dependent, these steps are **independent**,
@@ -512,7 +552,11 @@ $graph:
       jsonldPredicate:
         "_id": "@type"
         "_type": "@vocab"
-      type: string
+      type:
+        type: enum
+        name: Workflow_class
+        symbols:
+          - cwl:Workflow
     - name: steps
       doc: |
         The individual steps that make up the workflow.  Each step is executed when all of its
@@ -534,7 +578,11 @@ $graph:
     the `run` field of [WorkflowStep](#WorkflowStep).
   fields:
     - name: "class"
-      type: "string"
+      type:
+        type: enum
+        name: SubworkflowFeatureRequirement_class
+        symbols:
+          - cwl:SubworkflowFeatureRequirement
       doc: "Always 'SubworkflowFeatureRequirement'"
       jsonldPredicate:
         "_id": "@type"
@@ -548,7 +596,11 @@ $graph:
     `scatterMethod` fields of [WorkflowStep](#WorkflowStep).
   fields:
     - name: "class"
-      type: "string"
+      type:
+        type: enum
+        name: ScatterFeatureRequirement_class
+        symbols:
+          - cwl:ScatterFeatureRequirement
       doc: "Always 'ScatterFeatureRequirement'"
       jsonldPredicate:
         "_id": "@type"
@@ -562,7 +614,11 @@ $graph:
     listed in the `source` field of [WorkflowStepInput](#WorkflowStepInput).
   fields:
     - name: "class"
-      type: "string"
+      type:
+        type: enum
+        name: MultipleInputFeatureRequirement_class
+        symbols:
+          - cwl:MultipleInputFeatureRequirement
       doc: "Always 'MultipleInputFeatureRequirement'"
       jsonldPredicate:
         "_id": "@type"
@@ -576,7 +632,11 @@ $graph:
     of [WorkflowStepInput](#WorkflowStepInput).
   fields:
     - name: "class"
-      type: "string"
+      type:
+        type: enum
+        name: StepInputExpressionRequirement_class
+        symbols:
+          - cwl:StepInputExpressionRequirement
       doc: "Always 'StepInputExpressionRequirement'"
       jsonldPredicate:
         "_id": "@type"
diff --git a/schema_salad/tests/test_schema/metaschema_base.yml b/schema_salad/tests/test_schema/metaschema_base.yml
index 1033d04b4..25607ce81 100644
--- a/schema_salad/tests/test_schema/metaschema_base.yml
+++ b/schema_salad/tests/test_schema/metaschema_base.yml
@@ -39,8 +39,9 @@ $graph:
     - "xsd:string"
   doc:
     - |
-      Salad data types are based on Avro schema declarations.  Refer to the
-      [Avro schema declaration documentation](https://avro.apache.org/docs/current/spec.html#schemas) for
+      Names of salad data types (based on Avro schema declarations).
+
+      Refer to the [Avro schema declaration documentation](https://avro.apache.org/docs/current/spec.html#schemas) for
       detailed information.
     - "null: no value"
     - "boolean: a binary value"
@@ -76,6 +77,8 @@ $graph:
         - RecordSchema
         - EnumSchema
         - ArraySchema
+        - MapSchema
+        - UnionSchema
         - string
         - type: array
           items:
@@ -83,6 +86,8 @@ $graph:
             - RecordSchema
             - EnumSchema
             - ArraySchema
+            - MapSchema
+            - UnionSchema
             - string
       jsonldPredicate:
         _id: sld:type
@@ -167,6 +172,8 @@ $graph:
         - RecordSchema
         - EnumSchema
         - ArraySchema
+        - MapSchema
+        - UnionSchema
         - string
         - type: array
           items:
@@ -174,9 +181,91 @@ $graph:
             - RecordSchema
             - EnumSchema
             - ArraySchema
+            - MapSchema
+            - UnionSchema
             - string
       jsonldPredicate:
         _id: "sld:items"
         _type: "@vocab"
         refScope: 2
       doc: "Defines the type of the array elements."
+
+
+- name: MapSchema
+  type: record
+  fields:
+    type:
+      doc: "Must be `map`"
+      type:
+        type: enum
+        name: Map_name
+        symbols:
+          - "sld:map"
+      jsonldPredicate:
+        _id: "sld:type"
+        _type: "@vocab"
+        typeDSL: true
+        refScope: 2
+    values:
+      type:
+        - PrimitiveType
+        - RecordSchema
+        - EnumSchema
+        - ArraySchema
+        - MapSchema
+        - UnionSchema
+        - string
+        - type: array
+          items:
+            - PrimitiveType
+            - RecordSchema
+            - EnumSchema
+            - ArraySchema
+            - MapSchema
+            - UnionSchema
+            - string
+      jsonldPredicate:
+        _id: "sld:values"
+        _type: "@vocab"
+        refScope: 2
+      doc: "Defines the type of the map elements."
+
+
+- name: UnionSchema
+  type: record
+  fields:
+    type:
+      doc: "Must be `union`"
+      type:
+        type: enum
+        name: Union_name
+        symbols:
+          - "sld:union"
+      jsonldPredicate:
+        _id: "sld:type"
+        _type: "@vocab"
+        typeDSL: true
+        refScope: 2
+    names:
+      type:
+        - PrimitiveType
+        - RecordSchema
+        - EnumSchema
+        - ArraySchema
+        - MapSchema
+        - UnionSchema
+        - string
+        - type: array
+          items:
+            - PrimitiveType
+            - RecordSchema
+            - EnumSchema
+            - ArraySchema
+            - MapSchema
+            - UnionSchema
+            - string
+      jsonldPredicate:
+        _id: "sld:names"
+        _type: "@vocab"
+        refScope: 2
+      doc: "Defines the type of the union elements."