From f3e945c5d8e02859c511381bba0c119443af114c Mon Sep 17 00:00:00 2001 From: Dennis Kobert Date: Fri, 28 Aug 2026 19:18:54 +0000 Subject: [PATCH] Introduce the serve surface: the frame claim's proof-bearing lift on Node --- node-graph/libraries/core-types/src/node.rs | 20 +++++++++++++++++++ node-graph/libraries/core-types/src/record.rs | 18 +++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/node-graph/libraries/core-types/src/node.rs b/node-graph/libraries/core-types/src/node.rs index 07c5f68d51..53b90604fd 100644 --- a/node-graph/libraries/core-types/src/node.rs +++ b/node-graph/libraries/core-types/src/node.rs @@ -314,11 +314,31 @@ impl Iterator for ListIter<'_, T> { } } +/// The output marker of a node that serves records through the caller's +/// frame claim instead of returning a plain value. It satisfies the lift +/// bounds so [`Node::serve`] stays callable and overridable on the erased +/// surface. +#[derive(Clone, Copy, Debug, Default, PartialEq, dyn_any::DynAny)] +pub struct Records; + pub trait Node { type Output; fn eval(&self, input: &Input) -> GPoll; + /// Serves the node's record through the caller's claim: the writes land + /// in the claim and the returned proof is mintable only by its closing + /// methods, so the served record is of the claimed layout by + /// construction. The default lifts a plain output's element; record + /// servers override it. + fn serve<'e, 'l>(&self, input: &Input, slot: crate::record::FrameClaim<'l>) -> GPoll> + where + Self::Output: Send + Sync + dyn_any::StaticTypeSized, + Input: crate::context::ExtractArena, + { + slot.lift_served(self.eval(input), input.arena()) + } + /// The count of items at one absolute nesting level (innermost `0`). The /// leveled primitive a structure node overrides to report a pushed level's /// size; the scalar base is one item at every level. Uncertainty rides the diff --git a/node-graph/libraries/core-types/src/record.rs b/node-graph/libraries/core-types/src/record.rs index 8fc14829c4..9ad333d0d8 100644 --- a/node-graph/libraries/core-types/src/record.rs +++ b/node-graph/libraries/core-types/src/record.rs @@ -1801,6 +1801,24 @@ impl<'l> FrameClaim<'l> { None => unsafe { (&raw mut self.inline).cast::>().read() }, } } + + /// [`Self::lift`] with the proof-bearing return for [`Node::serve`]. + pub fn lift_served<'e, T: Send + Sync + dyn_any::StaticTypeSized>(self, poll: GPoll, arena: &'e crate::arena::Arena) -> GPoll> { + self.lift(poll, arena).map(|value| Served { value }) + } +} + +/// The proof a record was served through a frame claim: mintable only by the +/// claim's closing methods, so holding one means the record is of the +/// claimed layout. +pub struct Served<'e> { + value: RecordValue<'e>, +} + +impl<'e> Served<'e> { + pub fn value(self) -> RecordValue<'e> { + self.value + } } impl Drop for FrameClaim<'_> {