diff --git a/src/buildstream/_frontend/app.py b/src/buildstream/_frontend/app.py
index 9550fea409829c21fb4852dae03da515f02e4830..480e4191005e13fe79cf00422b41e399d0b5d1bc 100644
--- a/src/buildstream/_frontend/app.py
+++ b/src/buildstream/_frontend/app.py
@@ -342,7 +342,7 @@ class App():
             if project_name:
                 # If project name was specified, user interaction is not desired, just
                 # perform some validation and write the project.conf
-                _yaml.assert_symbol_name(None, project_name, 'project name')
+                _yaml.assert_symbol_name(project_name, 'project name')
                 self._assert_format_version(format_version)
                 self._assert_element_path(element_path)
 
diff --git a/src/buildstream/_loader/types.pyx b/src/buildstream/_loader/types.pyx
index 5b8388e28fff7937ad6d9da1fd2c0d34a0615e32..cd206cfb44f4e4ee829b68aa47e9e17ca120372e 100644
--- a/src/buildstream/_loader/types.pyx
+++ b/src/buildstream/_loader/types.pyx
@@ -65,12 +65,11 @@ cdef class Dependency:
     cdef public str junction
 
     def __init__(self,
-                 object dep,
-                 _yaml.ProvenanceInformation provenance,
+                 _yaml.Node dep,
                  str default_dep_type=None):
         cdef str dep_type
 
-        self.provenance = provenance
+        self.provenance = _yaml.node_get_provenance(dep)
 
         if type(dep) is _yaml.ScalarNode:
             self.name = dep.as_str()
@@ -100,7 +99,7 @@ cdef class Dependency:
 
         else:
             raise LoadError(LoadErrorReason.INVALID_DATA,
-                            "{}: Dependency is not specified as a string or a dictionary".format(provenance))
+                            "{}: Dependency is not specified as a string or a dictionary".format(self.provenance))
 
         # `:` characters are not allowed in filename if a junction was
         # explicitly specified
@@ -140,13 +139,9 @@ cdef class Dependency:
 cdef void _extract_depends_from_node(_yaml.Node node, str key, str default_dep_type, list acc) except *:
     cdef _yaml.SequenceNode depends = node.get_sequence(key, [])
     cdef _yaml.Node dep_node
-    cdef _yaml.ProvenanceInformation dep_provenance
 
     for dep_node in depends:
-        # FIXME: the provenance information would be obtainable from the Node directly if we stop
-        #        stripping provenance and have proper nodes for str elements
-        dep_provenance = <_yaml.ProvenanceInformation> _yaml.node_get_provenance(dep_node)
-        dependency = Dependency(dep_node, dep_provenance, default_dep_type=default_dep_type)
+        dependency = Dependency(dep_node, default_dep_type=default_dep_type)
         acc.append(dependency)
 
     # Now delete the field, we dont want it anymore
diff --git a/src/buildstream/_options/option.py b/src/buildstream/_options/option.py
index 6aa4cdc011459d4b9cda0df55c44e683307c8fe1..49b18da480509582b3dcd65c0b2b177bbfbbc628 100644
--- a/src/buildstream/_options/option.py
+++ b/src/buildstream/_options/option.py
@@ -66,8 +66,7 @@ class Option():
 
         # Assert valid symbol name for variable name
         if self.variable is not None:
-            p = _yaml.node_get_provenance(node, 'variable')
-            _yaml.assert_symbol_name(p, self.variable, 'variable name')
+            _yaml.assert_symbol_name(self.variable, 'variable name', ref_node=node.get_node('variable'))
 
     # load_value()
     #
diff --git a/src/buildstream/_options/optionarch.py b/src/buildstream/_options/optionarch.py
index e7735eaa28bdd38f14d81e7a70474f2380c2a1f1..75be1a8ff9175756e80014bee90d0a74cb7b9ff5 100644
--- a/src/buildstream/_options/optionarch.py
+++ b/src/buildstream/_options/optionarch.py
@@ -55,7 +55,7 @@ class OptionArch(OptionEnum):
                     # Do not terminate the loop early to ensure we validate
                     # all values in the list.
             except PlatformError as e:
-                provenance = _yaml.node_get_provenance(node, key='values', indices=[index])
+                provenance = _yaml.node_get_provenance(node.get_sequence('values').scalar_at(index))
                 prefix = ""
                 if provenance:
                     prefix = "{}: ".format(provenance)
diff --git a/src/buildstream/_options/optionpool.py b/src/buildstream/_options/optionpool.py
index 5155f62f423042b2898f903e93b52b2b73fd8727..cd4bfb6dcc3d922e0cb6e434b8389ac08a77b056 100644
--- a/src/buildstream/_options/optionpool.py
+++ b/src/buildstream/_options/optionpool.py
@@ -68,8 +68,7 @@ class OptionPool():
         for option_name, option_definition in options.items():
 
             # Assert that the option name is a valid symbol
-            p = _yaml.node_get_provenance(options, option_name)
-            _yaml.assert_symbol_name(p, option_name, "option name", allow_dashes=False)
+            _yaml.assert_symbol_name(option_name, "option name", ref_node=option_definition, allow_dashes=False)
 
             opt_type_name = option_definition.get_str('type')
             try:
@@ -262,31 +261,27 @@ class OptionPool():
                             "{}: {}".format(p, assertion.strip()))
 
         if conditions is not None:
-
-            # Collect provenance first, we need to delete the (?) key
-            # before any composition occurs.
-            provenance = [
-                _yaml.node_get_provenance(node, '(?)', indices=[i])
-                for i in range(len(conditions))
-            ]
             del node['(?)']
 
-            for condition, p in zip(conditions, provenance):
+            for condition in conditions:
                 tuples = list(condition.items())
                 if len(tuples) > 1:
+                    provenance = _yaml.node_get_provenance(condition)
                     raise LoadError(LoadErrorReason.INVALID_DATA,
-                                    "{}: Conditional statement has more than one key".format(p))
+                                    "{}: Conditional statement has more than one key".format(provenance))
 
                 expression, value = tuples[0]
                 try:
                     apply_fragment = self._evaluate(expression)
                 except LoadError as e:
                     # Prepend the provenance of the error
-                    raise LoadError(e.reason, "{}: {}".format(p, e)) from e
+                    provenance = _yaml.node_get_provenance(condition)
+                    raise LoadError(e.reason, "{}: {}".format(provenance, e)) from e
 
                 if type(value) is not _yaml.MappingNode:  # pylint: disable=unidiomatic-typecheck
+                    provenance = _yaml.node_get_provenance(condition)
                     raise LoadError(LoadErrorReason.ILLEGAL_COMPOSITE,
-                                    "{}: Only values of type 'dict' can be composed.".format(p))
+                                    "{}: Only values of type 'dict' can be composed.".format(provenance))
 
                 # Apply the yaml fragment if its condition evaluates to true
                 if apply_fragment:
diff --git a/src/buildstream/_project.py b/src/buildstream/_project.py
index 0c80c22b9431a12260771d1a10bab138acc004db..45eae03f9404b045771245c6ad6239d5cf01df2c 100644
--- a/src/buildstream/_project.py
+++ b/src/buildstream/_project.py
@@ -247,8 +247,7 @@ class Project():
     # will always be raised if both parameters are set to ``True``.
     #
     # Args:
-    #    node (dict): A dictionary loaded from YAML
-    #    key (str): The key whose value contains a path to validate
+    #    node (ScalarNode): A Node loaded from YAML containing the path to validate
     #    check_is_file (bool): If ``True`` an error will also be raised
     #                          if path does not point to a regular file.
     #                          Defaults to ``False``
@@ -262,21 +261,21 @@ class Project():
     #    (LoadError): In case that the project path is not valid or does not
     #                 exist
     #
-    def get_path_from_node(self, node, key, *,
+    def get_path_from_node(self, node, *,
                            check_is_file=False, check_is_dir=False):
-        path_str = node.get_str(key)
+        path_str = node.as_str()
         path = Path(path_str)
         full_path = self._absolute_directory_path / path
 
-        provenance = _yaml.node_get_provenance(node, key=key)
-
         if full_path.is_symlink():
+            provenance = _yaml.node_get_provenance(node)
             raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
                             "{}: Specified path '{}' must not point to "
                             "symbolic links "
                             .format(provenance, path_str))
 
         if path.parts and path.parts[0] == '..':
+            provenance = _yaml.node_get_provenance(node)
             raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
                             "{}: Specified path '{}' first component must "
                             "not be '..'"
@@ -288,6 +287,7 @@ class Project():
             else:
                 full_resolved_path = full_path.resolve(strict=True)  # pylint: disable=unexpected-keyword-arg
         except FileNotFoundError:
+            provenance = _yaml.node_get_provenance(node)
             raise LoadError(LoadErrorReason.MISSING_FILE,
                             "{}: Specified path '{}' does not exist"
                             .format(provenance, path_str))
@@ -296,12 +296,14 @@ class Project():
             full_resolved_path == self._absolute_directory_path)
 
         if not is_inside:
+            provenance = _yaml.node_get_provenance(node)
             raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
                             "{}: Specified path '{}' must not lead outside of the "
                             "project directory"
                             .format(provenance, path_str))
 
         if path.is_absolute():
+            provenance = _yaml.node_get_provenance(node)
             raise LoadError(LoadErrorReason.PROJ_PATH_INVALID,
                             "{}: Absolute path: '{}' invalid.\n"
                             "Please specify a path relative to the project's root."
@@ -310,17 +312,20 @@ class Project():
         if full_resolved_path.is_socket() or (
                 full_resolved_path.is_fifo() or
                 full_resolved_path.is_block_device()):
+            provenance = _yaml.node_get_provenance(node)
             raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
                             "{}: Specified path '{}' points to an unsupported "
                             "file kind"
                             .format(provenance, path_str))
 
         if check_is_file and not full_resolved_path.is_file():
+            provenance = _yaml.node_get_provenance(node)
             raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
                             "{}: Specified path '{}' is not a regular file"
                             .format(provenance, path_str))
 
         if check_is_dir and not full_resolved_path.is_dir():
+            provenance = _yaml.node_get_provenance(node)
             raise LoadError(LoadErrorReason.PROJ_PATH_INVALID_KIND,
                             "{}: Specified path '{}' is not a directory"
                             .format(provenance, path_str))
@@ -589,12 +594,12 @@ class Project():
         self.name = self._project_conf.get_str('name')
 
         # Validate that project name is a valid symbol name
-        _yaml.assert_symbol_name(_yaml.node_get_provenance(pre_config_node, 'name'),
-                                 self.name, "project name")
+        _yaml.assert_symbol_name(self.name, "project name",
+                                 ref_node=pre_config_node.get_node('name'))
 
         self.element_path = os.path.join(
             self.directory,
-            self.get_path_from_node(pre_config_node, 'element-path',
+            self.get_path_from_node(pre_config_node.get_scalar('element-path'),
                                     check_is_dir=True)
         )
 
@@ -947,7 +952,7 @@ class Project():
                     del origin_node[group]
 
             if origin_node.get_str('origin') == 'local':
-                path = self.get_path_from_node(origin, 'path',
+                path = self.get_path_from_node(origin.get_scalar('path'),
                                                check_is_dir=True)
                 # paths are passed in relative to the project, but must be absolute
                 origin_node['path'] = os.path.join(self.directory, path)
diff --git a/src/buildstream/_yaml.pxd b/src/buildstream/_yaml.pxd
index a3edd83f08b79ee7e7bf5a033ba673cd02aaa432..7b52090779694566a78e1858f3465b3f5fadf115 100644
--- a/src/buildstream/_yaml.pxd
+++ b/src/buildstream/_yaml.pxd
@@ -76,6 +76,7 @@ cdef class SequenceNode(Node):
     cpdef void append(self, object value)
     cpdef MappingNode mapping_at(self, int index)
     cpdef Node node_at(self, int index, list allowed_types=*)
+    cpdef ScalarNode scalar_at(self, int index)
     cpdef SequenceNode sequence_at(self, int index)
     cpdef list as_str_list(self)
 
@@ -90,4 +91,4 @@ cdef class ProvenanceInformation:
     cdef public bint is_synthetic
 
 
-cpdef ProvenanceInformation node_get_provenance(Node node, str key=*, list indices=*)
+cpdef ProvenanceInformation node_get_provenance(Node node, str key=*)
diff --git a/src/buildstream/_yaml.pyx b/src/buildstream/_yaml.pyx
index f35fed823e02f60c625c32ef37a7d912625cc90a..4e02f8cbe04fa764daa7d644b7fde2962e769cfe 100644
--- a/src/buildstream/_yaml.pyx
+++ b/src/buildstream/_yaml.pyx
@@ -623,15 +623,26 @@ cdef class SequenceNode(Node):
                             .format(provenance, path, MappingNode.__name__))
         return value
 
-    cpdef Node node_at(self, int key, list allowed_types = None):
-        cdef value = self.value[key]
+    cpdef Node node_at(self, int index, list allowed_types = None):
+        cdef value = self.value[index]
 
         if allowed_types and type(value) not in allowed_types:
             provenance = node_get_provenance(self)
             raise LoadError(LoadErrorReason.INVALID_DATA,
                             "{}: Value of '{}' is not one of the following: {}.".format(
-                                provenance, key, ", ".join(allowed_types)))
+                                provenance, index, ", ".join(allowed_types)))
+
+        return value
 
+    cpdef ScalarNode scalar_at(self, int index):
+        value = self.value[index]
+
+        if type(value) is not ScalarNode:
+            provenance = node_get_provenance(self)
+            path = ["[{}]".format(p) for p in provenance.toplevel._find(self)] + ["[{}]".format(index)]
+            raise LoadError(LoadErrorReason.INVALID_DATA,
+                            "{}: Value of '{}' is not of the expected type '{}'"
+                            .format(provenance, path, ScalarNode.__name__))
         return value
 
     cpdef SequenceNode sequence_at(self, int index):
@@ -1125,23 +1136,15 @@ cpdef Node load_data(str data, int file_index=_SYNTHETIC_FILE_INDEX, str file_na
 # Args:
 #   node (Node): a dictionary
 #   key (str): key in the dictionary
-#   indices (list of indexes): Index path, in the case of list values
 #
 # Returns: The Provenance of the dict, member or list element
 #
-cpdef ProvenanceInformation node_get_provenance(Node node, str key=None, list indices=None):
+cpdef ProvenanceInformation node_get_provenance(Node node, str key=None):
     if key is None:
         # Retrieving the provenance for this node directly
         return ProvenanceInformation(node)
 
-    if key and not indices:
-        return ProvenanceInformation((<MappingNode> node).value.get(key))
-
-    cdef Node nodeish = <Node> (<MappingNode> node).value.get(key)
-    for idx in indices:
-        nodeish = <Node> (<SequenceNode> nodeish).value[idx]
-
-    return ProvenanceInformation(nodeish)
+    return ProvenanceInformation((<MappingNode> node).value.get(key))
 
 
 # new_synthetic_file()
@@ -1219,9 +1222,9 @@ cdef Node _new_node_from_list(list inlist, Node ref_node):
 # are required to be symbols.
 #
 # Args:
-#    provenance (Provenance): The provenance of the loaded symbol, or None
 #    symbol_name (str): The loaded symbol name
 #    purpose (str): The purpose of the string, for an error message
+#    ref_node (Node): The node of the loaded symbol, or None
 #    allow_dashes (bool): Whether dashes are allowed for this symbol
 #
 # Raises:
@@ -1230,7 +1233,7 @@ cdef Node _new_node_from_list(list inlist, Node ref_node):
 # Note that dashes are generally preferred for variable names and
 # usage in YAML, but things such as option names which will be
 # evaluated with jinja2 cannot use dashes.
-def assert_symbol_name(ProvenanceInformation provenance, str symbol_name, str purpose, *, bint allow_dashes=True):
+def assert_symbol_name(str symbol_name, str purpose, *, Node ref_node=None, bint allow_dashes=True):
     cdef str valid_chars = string.digits + string.ascii_letters + '_'
     if allow_dashes:
         valid_chars += '-'
@@ -1250,8 +1253,10 @@ def assert_symbol_name(ProvenanceInformation provenance, str symbol_name, str pu
             detail += " or dashes"
 
         message = "Invalid symbol name for {}: '{}'".format(purpose, symbol_name)
-        if provenance is not None:
-            message = "{}: {}".format(provenance, message)
+        if ref_node:
+            provenance = node_get_provenance(ref_node)
+            if provenance is not None:
+                message = "{}: {}".format(provenance, message)
 
         raise LoadError(LoadErrorReason.INVALID_SYMBOL_NAME,
                         message, detail=detail)
diff --git a/src/buildstream/plugin.py b/src/buildstream/plugin.py
index d9854fb59210164cf88d09460818251fc655bb36..a5b21928812932b9105fefedb053deefa517b197 100644
--- a/src/buildstream/plugin.py
+++ b/src/buildstream/plugin.py
@@ -383,7 +383,7 @@ class Plugin():
         """
         return _yaml.new_empty_node()
 
-    def node_get_project_path(self, node, key, *,
+    def node_get_project_path(self, node, *,
                               check_is_file=False, check_is_dir=False):
         """Fetches a project path from a dictionary node and validates it
 
@@ -397,8 +397,7 @@ class Plugin():
         ``True``.
 
         Args:
-           node (dict): A dictionary loaded from YAML
-           key (str): The key whose value contains a path to validate
+           node (ScalarNode): A Node loaded from YAML containing the path to validate
            check_is_file (bool): If ``True`` an error will also be raised
                                  if path does not point to a regular file.
                                  Defaults to ``False``
@@ -423,7 +422,7 @@ class Plugin():
 
         """
 
-        return self.__project.get_path_from_node(node, key,
+        return self.__project.get_path_from_node(node,
                                                  check_is_file=check_is_file,
                                                  check_is_dir=check_is_dir)
 
diff --git a/src/buildstream/plugins/sources/local.py b/src/buildstream/plugins/sources/local.py
index ff0cf1679e5e354a7c774d35dcbf27ef65caa9d5..e28098c38450fdccf3221470e1521e6b2ae12a57 100644
--- a/src/buildstream/plugins/sources/local.py
+++ b/src/buildstream/plugins/sources/local.py
@@ -55,7 +55,7 @@ class LocalSource(Source):
 
     def configure(self, node):
         node.validate_keys(['path', *Source.COMMON_CONFIG_KEYS])
-        self.path = self.node_get_project_path(node, 'path')
+        self.path = self.node_get_project_path(node.get_scalar('path'))
         self.fullpath = os.path.join(self.get_project_directory(), self.path)
 
     def preflight(self):
diff --git a/src/buildstream/plugins/sources/patch.py b/src/buildstream/plugins/sources/patch.py
index 01117db7892c02101aad6965ea3edd856697c600..1e70039bd6340d169e2326985bc663bfec6eefab 100644
--- a/src/buildstream/plugins/sources/patch.py
+++ b/src/buildstream/plugins/sources/patch.py
@@ -55,7 +55,7 @@ class PatchSource(Source):
     BST_REQUIRES_PREVIOUS_SOURCES_STAGE = True
 
     def configure(self, node):
-        self.path = self.node_get_project_path(node, 'path',
+        self.path = self.node_get_project_path(node.get_scalar('path'),
                                                check_is_file=True)
         self.strip_level = node.get_int("strip-level", default=1)
         self.fullpath = os.path.join(self.get_project_directory(), self.path)
diff --git a/tests/internals/yaml.py b/tests/internals/yaml.py
index 37a2c9931cacb111526d049e14f67876538184ec..8ceed9d156eff00f8d3458535c7835685fa0287c 100644
--- a/tests/internals/yaml.py
+++ b/tests/internals/yaml.py
@@ -24,8 +24,8 @@ def test_load_yaml(datafiles):
     assert loaded.get_str('kind') == 'pony'
 
 
-def assert_provenance(filename, line, col, node, key=None, indices=None):
-    provenance = _yaml.node_get_provenance(node, key=key, indices=indices)
+def assert_provenance(filename, line, col, node, key=None):
+    provenance = _yaml.node_get_provenance(node, key=key)
 
     assert isinstance(provenance, _yaml.ProvenanceInformation)
 
@@ -68,7 +68,7 @@ def test_element_provenance(datafiles):
 
     loaded = _yaml.load(filename)
     assert loaded.get_str('kind') == 'pony'
-    assert_provenance(filename, 5, 2, loaded, 'moods', [1])
+    assert_provenance(filename, 5, 2, loaded.get_sequence('moods').scalar_at(1))
 
 
 @pytest.mark.datafiles(os.path.join(DATA_DIR))