diff --git a/node-graph/libraries/graphic-types/src/graphic.rs b/node-graph/libraries/graphic-types/src/graphic.rs
index a4a2488a0e..2a8e000ad9 100644
--- a/node-graph/libraries/graphic-types/src/graphic.rs
+++ b/node-graph/libraries/graphic-types/src/graphic.rs
@@ -1307,10 +1307,55 @@ fn deep_repark_graphic_list(value: &dyn core_types::list::AnyAttributeValue, are
Some(Some(Box::new(Some(list))))
}
+/// The promote for graphic-list fields, the deep field glue's third half: the
+/// owned halves copy content groups out to the owned form and replay them back,
+/// while this maps the content transient-to-persistent in one pass.
+///
+/// THE TWO-LEVEL SHARING LAW: the field's own header is not provenance-shared.
+/// It moves where the content is group-free, since the payload then owns all of
+/// its content and the transient arena confirms the reference is its own park,
+/// and otherwise it clones into a fresh persistent park. One level inside, a
+/// content group's interior is arena-resident and its provenance is decidable,
+/// so it takes [`map_groups_to_persistent`]'s Cow dispatch: an interior the
+/// persistent region already holds is shared pointer for pointer.
+///
+/// # Safety
+/// `src` must point at a live parked graphic-list field, and `dst` at the field
+/// slot the promoted reference is written to.
+unsafe fn promote_graphic_list(src: *const u8, dst: *mut u8, promotion: &core_types::record::Promotion<'_>) -> Option<()> {
+ // SAFETY: the caller's contract; the slot holds one optional reference.
+ let Some(list) = (unsafe { src.cast::