diff --git a/meta/lib/oe/sbom30.py b/meta/lib/oe/sbom30.py
index b33860e4aa..713eec92d4 100644
--- a/meta/lib/oe/sbom30.py
+++ b/meta/lib/oe/sbom30.py
@@ -97,6 +97,32 @@ class OELicenseScannedExtension(oe.spdx30.extension_Extension):
     INTERNAL = True
 
 
+class OEIncompleteExtension(oe.spdx30.extension_Extension):
+    """
+    The presence of this extension means that the Element is incomplete, and
+    should not be preferred over a completely defined Element
+    """
+
+    TYPE = OE_SPDX_BASE + "incomplete"
+    CLOSED = True
+    INTERNAL = True
+
+    @classmethod
+    def is_incomplete(cls, obj):
+        for e in obj.extension:
+            if isinstance(e, cls):
+                return True
+        return False
+
+    @classmethod
+    def is_complete(cls, obj):
+        return not cls.is_incomplete(obj)
+
+    @classmethod
+    def set(cls, obj):
+        obj.extension.append(cls())
+
+
 class OEDocumentExtension(oe.spdx30.extension_Extension):
     """
     This extension is added to a SpdxDocument to indicate various useful bits
@@ -194,21 +220,35 @@ def to_list(l):
 
 class Dedup(object):
     def __init__(self, objset):
-        self.unique = set()
-        self.dedup = {}
+        self.unique = []
+        self.dup_idx = {}
         self.objset = objset
+        self.has_duplicates = False
 
-    def find_duplicates(self, cmp, typ, **kwargs):
+    def find_duplicates(self, cmp, typ, choose=None, **kwargs):
         for o in self.objset.foreach_filter(typ, **kwargs):
-            for u in self.unique:
+            for idx, u in enumerate(self.unique):
                 if cmp(u, o):
-                    self.dedup[o] = u
+                    self.has_duplicates = True
+                    self.dup_idx[o] = idx
+                    if choose:
+                        self.unique[idx] = choose(u, o)
                     break
             else:
-                self.unique.add(o)
+                self.dup_idx[o] = len(self.unique)
+                self.unique.append(o)
 
     def get(self, o):
-        return self.dedup.get(o, o)
+        idx = self.dup_idx.get(o, -1)
+        if idx < 0:
+            return o
+        return self.unique[idx]
+
+    def duplicates(self):
+        for o, idx in self.dup_idx.items():
+            d = self.unique[idx]
+            if d is not o:
+                yield o, d
 
 
 class ObjectSet(oe.spdx30.SHACLObjectSet):
@@ -582,23 +622,34 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
         if license_list_version.count(".") < 2:
             license_list_version += ".0"
 
+        license_text = sorted(license_text_map.items(), key=lambda t: t[0])
+
+        incomplete = not license_text and "licenseref-" in license_expression.lower()
+
         spdxid = [
-            "license",
+            "license" if not incomplete else "partial-license",
             license_list_version,
             re.sub(r"[^a-zA-Z0-9_-]", "_", license_expression),
         ]
 
-        license_text = sorted(license_text_map.items(), key=lambda t: t[0])
-
         if not license_text:
-            lic = self.find_filter(
+            incomplete_lic = None
+            for lic in self.foreach_filter(
                 oe.spdx30.simplelicensing_LicenseExpression,
                 simplelicensing_licenseExpression=license_expression,
                 simplelicensing_licenseListVersion=license_list_version,
-            )
-            if lic is not None:
-                return lic
+            ):
+                if OEIncompleteExtension.is_complete(lic):
+                    return lic
+
+                if not incomplete_lic:
+                    incomplete_lic = lic
+
+            if incomplete and incomplete_lic:
+                return incomplete_lic
+
         else:
+            # Note: This branch will never be entered for incomplete licenses
             spdxid.append(spdxid_hash(*(v for _, v in license_text)))
             lic = self.find_by_id(self.new_spdxid(*spdxid))
             if lic is not None:
@@ -618,6 +669,9 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
                 oe.spdx30.DictionaryEntry(key=key, value=value._id)
             )
 
+        if incomplete:
+            OEIncompleteExtension.set(lic)
+
         return lic
 
     def scan_declared_licenses(self, spdx_file, filepath, license_data):
@@ -953,16 +1007,16 @@ class ObjectSet(oe.spdx30.SHACLObjectSet):
 
             return True
 
-        if d.dedup:
+        if d.has_duplicates:
             for o in self.objects:
                 o.walk(visit)
 
-            for k, v in d.dedup.items():
+            for dup, replacement in d.duplicates():
                 bb.debug(
                     1,
-                    f"Removing duplicate {k.__class__.__name__} {k._id or id(k)} -> {v._id or id(v)}",
+                    f"Removing duplicate {dup.__class__.__name__} {dup._id or id(dup)} -> {replacement._id or id(replacement)}",
                 )
-                self.objects.discard(k)
+                self.objects.discard(dup)
 
             self.create_index()
 
@@ -1148,9 +1202,6 @@ def create_sbom(d, name, root_elements, add_objectsets=[]):
             + "\n  ".join(sorted(list(missing_spdxids)))
         )
 
-    # Filter out internal extensions from final SBoMs
-    objset.remove_internal_extensions()
-
     # SBoM should be the only root element of the document
     objset.doc.rootElement = [sbom]
 
@@ -1161,6 +1212,15 @@ def create_sbom(d, name, root_elements, add_objectsets=[]):
             == b.simplelicensing_licenseListVersion
         )
 
+    def choose_license_expressions(current, new):
+        if OEIncompleteExtension.is_complete(current):
+            return current
+
+        if OEIncompleteExtension.is_incomplete(new):
+            return current
+
+        return new
+
     def cmp_creation_info(a, b):
         data_a = {k: a[k] for k in a}
         data_b = {k: b[k] for k in b}
@@ -1173,9 +1233,13 @@ def create_sbom(d, name, root_elements, add_objectsets=[]):
         dedup.find_duplicates(
             cmp_license_expression,
             oe.spdx30.simplelicensing_LicenseExpression,
+            choose=choose_license_expressions,
         )
 
         # Deduplicate creation info
         dedup.find_duplicates(cmp_creation_info, oe.spdx30.CreationInfo)
 
+    # Filter out internal extensions from final SBoMs
+    objset.remove_internal_extensions()
+
     return objset, sbom
