use super::introspection::{
state_args_stub, state_caller_stub, state_capture_all_stub, state_capture_call_stub,
state_capture_module_stub, state_capture_stub, state_locals_stub, state_resume_frame_stub,
state_resume_stub,
};
use shape_runtime::module_exports::{ModuleContext, ModuleExports, ModuleParam};
use shape_runtime::type_schema::{FieldType, TypeSchema};
use shape_runtime::typed_module_exports::{ConcreteType, TypedReturn};
use shape_runtime::marshal::register_typed_function;
use shape_value::KindedSlot;
pub fn create_state_module() -> ModuleExports {
let mut module = ModuleExports::new("std::core::state");
module.description = "Content-addressed VM state primitives".to_string();
module.add_type_schema(TypeSchema::new(
"FunctionRef",
vec![
("name".to_string(), FieldType::String),
("hash".to_string(), FieldType::String),
],
));
module.add_type_schema(TypeSchema::new(
"FrameState",
vec![
("function_name".to_string(), FieldType::String),
("blob_hash".to_string(), FieldType::String),
("ip".to_string(), FieldType::I64),
("locals".to_string(), FieldType::Any),
("args".to_string(), FieldType::Any),
("upvalues".to_string(), FieldType::Any),
],
));
module.add_type_schema(TypeSchema::new(
"VmState",
vec![
("frames".to_string(), FieldType::Any),
("module_bindings".to_string(), FieldType::Any),
("instruction_count".to_string(), FieldType::I64),
],
));
module.add_type_schema(TypeSchema::new(
"ModuleState",
vec![("bindings".to_string(), FieldType::Any)],
));
module.add_type_schema(TypeSchema::new(
"CallPayload",
vec![
("hash".to_string(), FieldType::String),
("args".to_string(), FieldType::Any),
],
));
register_typed_function(
&mut module,
"hash",
"SHA-256 content hash of any value",
vec![ModuleParam {
name: "value".into(),
type_name: "any".into(),
required: true,
description: "Value to hash".into(),
..Default::default()
}],
ConcreteType::String,
state_hash,
);
register_typed_function(
&mut module,
"fn_hash",
"Get a function's content hash from its FunctionBlob",
vec![ModuleParam {
name: "f".into(),
type_name: "any".into(),
required: true,
description: "Function value".into(),
..Default::default()
}],
ConcreteType::String,
state_fn_hash,
);
register_typed_function(
&mut module,
"schema_hash",
"Content hash of a type's schema definition",
vec![ModuleParam {
name: "type_name".into(),
type_name: "string".into(),
required: true,
description: "Name of the type to hash".into(),
..Default::default()
}],
ConcreteType::String,
state_schema_hash,
);
register_typed_function(
&mut module,
"serialize",
"Serialize a value to MessagePack bytes",
vec![ModuleParam {
name: "value".into(),
type_name: "any".into(),
required: true,
description: "Value to serialize".into(),
..Default::default()
}],
ConcreteType::ArrayInt,
state_serialize,
);
register_typed_function(
&mut module,
"deserialize",
"Deserialize MessagePack bytes back to a value",
vec![ModuleParam {
name: "bytes".into(),
type_name: "Array<int>".into(),
required: true,
description: "MessagePack byte array".into(),
..Default::default()
}],
ConcreteType::Any,
state_deserialize,
);
register_typed_function(
&mut module,
"diff",
"Compute delta between two values using content-hash trees",
vec![
ModuleParam {
name: "old".into(),
type_name: "any".into(),
required: true,
description: "Old value".into(),
..Default::default()
},
ModuleParam {
name: "new".into(),
type_name: "any".into(),
required: true,
description: "New value".into(),
..Default::default()
},
],
ConcreteType::Named("Delta".into()),
state_diff,
);
register_typed_function(
&mut module,
"patch",
"Apply a delta to a base value, producing the updated value",
vec![
ModuleParam {
name: "base".into(),
type_name: "any".into(),
required: true,
description: "Base value".into(),
..Default::default()
},
ModuleParam {
name: "delta".into(),
type_name: "Delta".into(),
required: true,
description: "Delta to apply".into(),
..Default::default()
},
],
ConcreteType::Any,
state_patch,
);
register_typed_function(
&mut module,
"capture",
"Capture current function's frame state",
vec![],
ConcreteType::Named("FrameState".into()),
state_capture_stub,
);
register_typed_function(
&mut module,
"capture_all",
"Capture full VM execution state",
vec![],
ConcreteType::Named("VmState".into()),
state_capture_all_stub,
);
register_typed_function(
&mut module,
"capture_module",
"Capture module-level bindings and type schemas",
vec![],
ConcreteType::Named("ModuleState".into()),
state_capture_module_stub,
);
register_typed_function(
&mut module,
"capture_call",
"Build a ready-to-call payload without executing",
vec![
ModuleParam {
name: "f".into(),
type_name: "any".into(),
required: true,
description: "Function to capture".into(),
..Default::default()
},
ModuleParam {
name: "args".into(),
type_name: "Array<any>".into(),
required: true,
description: "Arguments for the call".into(),
..Default::default()
},
],
ConcreteType::Named("CallPayload".into()),
state_capture_call_stub,
);
register_typed_function(
&mut module,
"resume",
"Resume full VM state (does not return)",
vec![ModuleParam {
name: "vm".into(),
type_name: "VmState".into(),
required: true,
description: "VM state to resume".into(),
..Default::default()
}],
ConcreteType::Named("never".into()),
state_resume_stub,
);
register_typed_function(
&mut module,
"resume_frame",
"Re-enter a captured function frame and return its result",
vec![ModuleParam {
name: "f".into(),
type_name: "FrameState".into(),
required: true,
description: "Frame state to resume".into(),
..Default::default()
}],
ConcreteType::Any,
state_resume_frame_stub,
);
register_typed_function(
&mut module,
"caller",
"Get a reference to the calling function",
vec![],
ConcreteType::Named("FunctionRef?".into()),
state_caller_stub,
);
register_typed_function(
&mut module,
"args",
"Get the current function's arguments as an array",
vec![],
ConcreteType::Named("Array<any>".into()),
state_args_stub,
);
register_typed_function(
&mut module,
"locals",
"Get the current scope's local variables as a map",
vec![],
ConcreteType::Named("Map<string, any>".into()),
state_locals_stub,
);
register_typed_function(
&mut module,
"snapshot",
"Create a snapshot of the current execution state. This is a suspension point: the engine saves all state and returns Snapshot::Hash(id). When resumed from a snapshot, execution continues here and returns Snapshot::Resumed.",
vec![],
ConcreteType::Named("Snapshot".into()),
state_capture_all_stub,
);
module
}
fn content_surface(op: &str) -> String {
format!(
"{op}: W17-snapshot-resume surface — kind-threaded \
slot_to_serializable / serializable_to_slot replacement for the \
deleted nanboxed_to_serializable / serializable_to_nanboxed \
pair has not landed; state.diff / state.patch additionally \
depend on the deleted 1486-LoC `state_diff` runtime module's \
kind-threaded rebuild. Tracked as W17-snapshot-resume per \
docs/cluster-audits/phase-2d-playbook.md §3. \
ADR-006 §2.7.4 (snapshot serialization deferral) + §2.7.5.1 \
(post-proof wire-format shape for new HeapKinds).",
)
}
fn ephemeral_store() -> Result<shape_runtime::snapshot::SnapshotStore, String> {
let tmp = tempfile::tempdir().map_err(|e| {
format!(
"W17-snapshot-resume surface — tempdir creation failed: {e}. \
ADR-006 §2.7.4."
)
})?;
let store = shape_runtime::snapshot::SnapshotStore::new(tmp.path()).map_err(|e| {
format!(
"W17-snapshot-resume surface — SnapshotStore::new failed: {e}. \
ADR-006 §2.7.4."
)
})?;
std::mem::forget(tmp);
Ok(store)
}
fn slot_to_serialized_bytes(slot: &KindedSlot) -> Result<Vec<u8>, String> {
use shape_runtime::snapshot::slot_to_serializable;
let store = ephemeral_store()?;
let sv = slot_to_serializable(slot.slot().raw(), slot.kind(), &store)?;
let bytes = bincode::serialize(&sv).map_err(|e| {
format!(
"state.serialize: W17-snapshot-resume surface — bincode \
serialization failed: {e}. ADR-006 §2.7.5.1."
)
})?;
Ok(bytes)
}
pub(crate) fn state_hash(
args: &[KindedSlot],
_ctx: &ModuleContext,
) -> Result<TypedReturn, String> {
let Some(arg) = args.first() else {
return Err(content_surface("state.hash"));
};
let bytes = slot_to_serialized_bytes(arg)?;
let digest = shape_runtime::hash_bytes(&bytes);
Ok(TypedReturn::Concrete(
shape_runtime::typed_module_exports::ConcreteReturn::String(digest.hex().to_string()),
))
}
pub(crate) fn state_fn_hash(
args: &[KindedSlot],
ctx: &ModuleContext,
) -> Result<TypedReturn, String> {
use shape_value::{HeapKind, NativeKind};
let Some(arg) = args.first() else {
return Err(content_surface("state.fn_hash"));
};
let bits = arg.slot().raw();
let function_id = match arg.kind() {
NativeKind::Int64 | NativeKind::UInt64 => Some(bits as u16),
NativeKind::Ptr(HeapKind::Closure) => {
if bits == 0 {
None
} else {
let ptr = bits as *const u8;
Some(unsafe {
shape_value::v2::closure_raw::typed_closure_function_id(ptr)
})
}
}
_ => None,
};
let Some(fid) = function_id else {
return Err(format!(
"state.fn_hash: W17-snapshot-resume surface — argument is not a \
function value (kind={:?}); function-handle decoding for \
HeapKind::FunctionRef / TraitObject not yet wired. ADR-006 \
§2.7.4.",
arg.kind()
));
};
let Some(hashes) = ctx.function_hashes else {
return Err(format!(
"state.fn_hash: W17-snapshot-resume surface — \
ctx.function_hashes is None at this dispatch surface; \
content-addressed metadata not propagated through \
invoke_module_fn_id_stub. ADR-006 §2.7.4."
));
};
let Some(maybe_hash) = hashes.get(fid as usize) else {
return Err(format!(
"state.fn_hash: function_id {fid} out of range \
(program has {} functions). ADR-006 §2.7.4.",
hashes.len()
));
};
let Some(hash_bytes) = maybe_hash else {
return Err(format!(
"state.fn_hash: W17-snapshot-resume surface — function_id {fid} \
has no content-addressed hash entry (compiled without \
content-addressed metadata). ADR-006 §2.7.4."
));
};
Ok(TypedReturn::Concrete(
shape_runtime::typed_module_exports::ConcreteReturn::String(hex::encode(hash_bytes)),
))
}
pub(crate) fn state_schema_hash(
args: &[KindedSlot],
ctx: &ModuleContext,
) -> Result<TypedReturn, String> {
let Some(arg) = args.first() else {
return Err(content_surface("state.schema_hash"));
};
let type_name = match arg.kind() {
shape_value::NativeKind::String
| shape_value::NativeKind::Ptr(shape_value::HeapKind::String) => {
let bits = arg.slot().raw();
if bits == 0 {
return Err(format!(
"state.schema_hash: W17-snapshot-resume surface — null \
string bits. ADR-006 §2.7.6."
));
}
unsafe {
let arc = std::sync::Arc::<String>::from_raw(bits as *const String);
let s: String = (*arc).clone();
let _ = std::sync::Arc::into_raw(arc); s
}
}
other => {
return Err(format!(
"state.schema_hash: W17-snapshot-resume surface — first \
argument must be string (got kind={other:?}). ADR-006 §2.7.4."
));
}
};
let Some(schema) = ctx.schemas.get(&type_name) else {
return Err(format!(
"state.schema_hash: unknown type '{type_name}'. ADR-006 §2.7.4."
));
};
let bytes = bincode::serialize(schema).map_err(|e| {
format!(
"state.schema_hash: W17-snapshot-resume surface — bincode \
serialization failed: {e}. ADR-006 §2.7.5.1."
)
})?;
let digest = shape_runtime::hash_bytes(&bytes);
Ok(TypedReturn::Concrete(
shape_runtime::typed_module_exports::ConcreteReturn::String(digest.hex().to_string()),
))
}
pub(crate) fn state_serialize(
args: &[KindedSlot],
_ctx: &ModuleContext,
) -> Result<TypedReturn, String> {
let Some(arg) = args.first() else {
return Err(content_surface("state.serialize"));
};
let _bytes = slot_to_serialized_bytes(arg)?;
Err(format!(
"state.serialize: W17-snapshot-resume surface — body computed \
{} bytes via slot_to_serializable but the Array<int>/Bytes return \
arm needs the W17-marshal-return-arms follow-up at \
project_typed_return. ADR-006 §2.7.4 + §2.7.5.1.",
_bytes.len()
))
}
pub(crate) fn state_deserialize(
_args: &[KindedSlot],
_ctx: &ModuleContext,
) -> Result<TypedReturn, String> {
Err(content_surface("state.deserialize"))
}
pub(crate) fn state_diff(
_args: &[KindedSlot],
_ctx: &ModuleContext,
) -> Result<TypedReturn, String> {
Err(content_surface("state.diff"))
}
pub(crate) fn state_patch(
_args: &[KindedSlot],
_ctx: &ModuleContext,
) -> Result<TypedReturn, String> {
Err(content_surface("state.patch"))
}
#[cfg(test)]
mod tests {
use super::*;
use shape_runtime::type_schema::FieldType;
fn find_schema<'a>(
module: &'a ModuleExports,
name: &str,
) -> &'a shape_runtime::type_schema::TypeSchema {
module
.type_schemas
.iter()
.find(|s| s.name == name)
.unwrap_or_else(|| panic!("schema '{}' not found", name))
}
#[test]
fn test_state_schemas_have_concrete_field_types() {
let module = create_state_module();
let func_ref = find_schema(&module, "FunctionRef");
assert_eq!(
func_ref.get_field("name").unwrap().field_type,
FieldType::String
);
assert_eq!(
func_ref.get_field("hash").unwrap().field_type,
FieldType::String
);
let frame = find_schema(&module, "FrameState");
assert_eq!(
frame.get_field("function_name").unwrap().field_type,
FieldType::String
);
assert_eq!(
frame.get_field("blob_hash").unwrap().field_type,
FieldType::String
);
assert_eq!(frame.get_field("ip").unwrap().field_type, FieldType::I64);
assert_eq!(
frame.get_field("locals").unwrap().field_type,
FieldType::Any
);
assert_eq!(frame.get_field("args").unwrap().field_type, FieldType::Any);
assert_eq!(
frame.get_field("upvalues").unwrap().field_type,
FieldType::Any
);
let vm_state = find_schema(&module, "VmState");
assert_eq!(
vm_state.get_field("instruction_count").unwrap().field_type,
FieldType::I64
);
assert_eq!(
vm_state.get_field("frames").unwrap().field_type,
FieldType::Any
);
assert_eq!(
vm_state.get_field("module_bindings").unwrap().field_type,
FieldType::Any
);
let mod_state = find_schema(&module, "ModuleState");
assert_eq!(
mod_state.get_field("bindings").unwrap().field_type,
FieldType::Any
);
let call = find_schema(&module, "CallPayload");
assert_eq!(
call.get_field("hash").unwrap().field_type,
FieldType::String
);
assert_eq!(call.get_field("args").unwrap().field_type, FieldType::Any);
}
}