From 90b39942127bf29b575aa021ad2c2cb64b0bbf5e Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Mon, 14 Sep 2026 13:04:02 +0100 Subject: [PATCH 01/10] feat(versioned-types): blobs, persistence naming, and SQL schemas --- Cargo.lock | 16 + Cargo.toml | 1 + crates/flux-versioned-types-macros/src/lib.rs | 6 + .../flux-versioned-types-macros/src/schema.rs | 264 +++++++++++++ crates/flux-versioned-types/Cargo.toml | 7 + crates/flux-versioned-types/src/blob.rs | 371 ++++++++++++++++++ crates/flux-versioned-types/src/lib.rs | 59 ++- crates/flux-versioned-types/src/schema.rs | 97 +++++ 8 files changed, 815 insertions(+), 6 deletions(-) create mode 100644 crates/flux-versioned-types-macros/src/schema.rs create mode 100644 crates/flux-versioned-types/src/blob.rs create mode 100644 crates/flux-versioned-types/src/schema.rs diff --git a/Cargo.lock b/Cargo.lock index 5f967e9..5d0e9e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -761,8 +761,14 @@ version = "0.3.0" dependencies = [ "bincode", "flux", + "flux-timing", + "flux-utils", "flux-versioned-types-macros", "serde", + "tracing", + "type-hash", + "uuid", + "zstd", ] [[package]] @@ -2089,6 +2095,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index 5c5ff5f..f917fc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ thiserror = "1.0.58" tinyvec = "1.10.0" tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +uuid = "1" wincode = "0.5" wincode-derive = "0.4" zstd = "0.13.2" diff --git a/crates/flux-versioned-types-macros/src/lib.rs b/crates/flux-versioned-types-macros/src/lib.rs index 23172b8..2561a7b 100644 --- a/crates/flux-versioned-types-macros/src/lib.rs +++ b/crates/flux-versioned-types-macros/src/lib.rs @@ -3,6 +3,7 @@ use proc_macro::TokenStream; mod evolve; mod evolve_enum; mod rolling; +mod schema; mod shared; #[proc_macro] @@ -19,3 +20,8 @@ pub fn evolve_enum(input: TokenStream) -> TokenStream { pub fn roll_chain_into(input: TokenStream) -> TokenStream { rolling::roll_chain_into(input) } + +#[proc_macro_derive(TelemetrySchema, attributes(serde, telemetry_schema))] +pub fn derive_telemetry_schema(input: TokenStream) -> TokenStream { + schema::derive_telemetry_schema(input) +} diff --git a/crates/flux-versioned-types-macros/src/schema.rs b/crates/flux-versioned-types-macros/src/schema.rs new file mode 100644 index 0000000..ea9cf81 --- /dev/null +++ b/crates/flux-versioned-types-macros/src/schema.rs @@ -0,0 +1,264 @@ +use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{ + Attribute, Data, DeriveInput, Error, Expr, Fields, Result, Token, Type, parse_macro_input, +}; + +pub(crate) fn derive_telemetry_schema(input: TokenStream) -> TokenStream { + match derive(parse_macro_input!(input as DeriveInput)) { + Ok(output) => output.into(), + Err(error) => error.into_compile_error().into(), + } +} + +fn derive(input: DeriveInput) -> Result { + if !input.generics.params.is_empty() { + return Err(Error::new_spanned( + input.generics, + "TelemetrySchema does not support generic types", + )); + } + let name = input.ident; + let explicit = explicit_proxy(&input.attrs)?; + reject_serde_changes(&input.attrs, explicit.is_some(), true)?; + if let Some(proxy) = explicit { + return Ok(quote! { + impl ::flux_versioned_types::TelemetrySchema for #name { + type Proxy = #proxy; + type FlattenedProxy = #proxy; + } + }); + } + let proxy = format_ident!("__TelemetrySchemaProxyFor{}", name); + let flattened = format_ident!("__TelemetrySchemaFlattenedProxyFor{}", name); + let transparent = serde_transparent(&input.attrs)?; + + match input.data { + Data::Struct(data) if transparent => { + let field = match data.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + fields.unnamed.first().unwrap().clone() + } + Fields::Named(fields) if fields.named.len() == 1 => { + fields.named.first().unwrap().clone() + } + fields => { + return Err(Error::new_spanned( + fields, + "serde(transparent) requires exactly one field", + )); + } + }; + let ty = field_proxy_ty(&field)?; + let flattened_ty = field_flattened_proxy_ty(&field)?; + Ok(quote! { + impl ::flux_versioned_types::TelemetrySchema for #name { + type Proxy = #ty; + type FlattenedProxy = #flattened_ty; + } + }) + } + Data::Struct(data) => derive_struct(&name, &proxy, data.fields), + Data::Enum(data) => { + derive_enum(&name, &proxy, &flattened, data.variants.into_iter().collect()) + } + Data::Union(data) => { + Err(Error::new_spanned(data.union_token, "TelemetrySchema does not support unions")) + } + } +} + +fn derive_struct(name: &syn::Ident, proxy: &syn::Ident, fields: Fields) -> Result { + match fields { + Fields::Named(fields) => { + let fields = fields + .named + .iter() + .map(|field| { + let name = &field.ident; + let ty = field_proxy_ty(field)?; + Ok(quote!(pub #name: #ty)) + }) + .collect::>>()?; + Ok(quote! { + #[doc(hidden)] + #[allow(non_camel_case_types, clippy::pub_underscore_fields)] + #[derive(::serde::Deserialize)] + pub struct #proxy { #(#fields,)* } + impl ::flux_versioned_types::TelemetrySchema for #name { type Proxy = #proxy; type FlattenedProxy = #proxy; } + }) + } + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + let ty = field_proxy_ty(fields.unnamed.first().unwrap())?; + Ok(quote! { + #[doc(hidden)] #[allow(non_camel_case_types)] #[derive(::serde::Deserialize)] + pub struct #proxy(pub #ty); + impl ::flux_versioned_types::TelemetrySchema for #name { type Proxy = #proxy; type FlattenedProxy = #proxy; } + }) + } + fields => Err(Error::new_spanned( + fields, + "TelemetrySchema only supports named structs and newtype structs", + )), + } +} + +fn derive_enum( + name: &syn::Ident, + proxy: &syn::Ident, + flattened: &syn::Ident, + variants: Vec, +) -> Result { + let mut enum_variants = Vec::new(); + let mut flat_fields = Vec::new(); + let mut nested_defs = Vec::new(); + for variant in variants { + if explicit_proxy(&variant.attrs)?.is_some() { + return Err(Error::new_spanned( + variant, + "telemetry_schema proxy overrides are not supported on enum variants", + )); + } + reject_serde_changes(&variant.attrs, false, false)?; + let variant_name = variant.ident; + match variant.fields { + Fields::Unit => { + enum_variants.push(quote!(#variant_name)); + flat_fields.push(quote!(pub #variant_name: Option<()>)); + } + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + let ty = field_proxy_ty(fields.unnamed.first().unwrap())?; + enum_variants.push(quote!(#variant_name(#ty))); + flat_fields.push(quote!(pub #variant_name: Option<#ty>)); + } + Fields::Unnamed(fields) => { + let types = + fields.unnamed.iter().map(field_proxy_ty).collect::>>()?; + enum_variants.push(quote!(#variant_name(#(#types),*))); + flat_fields.push(quote!(pub #variant_name: Option<(#(#types),*)>)); + } + Fields::Named(fields) => { + let nested = format_ident!("__TelemetrySchemaProxyFor{}_{}", name, variant_name); + let fields = fields + .named + .iter() + .map(|field| { + let name = &field.ident; + let ty = field_proxy_ty(field)?; + Ok(quote!(pub #name: #ty)) + }) + .collect::>>()?; + enum_variants.push(quote!(#variant_name(#nested))); + flat_fields.push(quote!(pub #variant_name: Option<#nested>)); + nested_defs.push(quote!( + #[doc(hidden)] + #[allow(non_camel_case_types, clippy::pub_underscore_fields)] + #[derive(::serde::Deserialize)] + pub struct #nested { #(#fields,)* } + )); + } + } + } + Ok(quote! { + #(#nested_defs)* + #[doc(hidden)] #[allow(non_camel_case_types)] #[derive(::serde::Deserialize)] pub enum #proxy { #(#enum_variants,)* } + #[doc(hidden)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(::serde::Deserialize)] + pub struct #flattened { #(#flat_fields,)* } + impl ::flux_versioned_types::TelemetrySchema for #name { type Proxy = #proxy; type FlattenedProxy = #flattened; } + }) +} + +fn field_proxy_ty(field: &syn::Field) -> Result { + if let Some(proxy) = explicit_proxy(&field.attrs)? { + return Ok(quote!(#proxy)); + } + reject_serde_changes(&field.attrs, false, false)?; + let ty = &field.ty; + Ok(quote!(<#ty as ::flux_versioned_types::TelemetrySchema>::Proxy)) +} + +fn field_flattened_proxy_ty(field: &syn::Field) -> Result { + if let Some(proxy) = explicit_proxy(&field.attrs)? { + return Ok(quote!(#proxy)); + } + reject_serde_changes(&field.attrs, false, false)?; + let ty = &field.ty; + Ok(quote!(<#ty as ::flux_versioned_types::TelemetrySchema>::FlattenedProxy)) +} + +fn explicit_proxy(attrs: &[Attribute]) -> Result> { + let mut proxy = None; + for attr in attrs { + if !attr.path().is_ident("telemetry_schema") { + continue; + } + attr.parse_nested_meta(|meta| { + if !meta.path.is_ident("proxy") { + return Err(meta.error("expected telemetry_schema(proxy = Type)")); + } + if proxy.is_some() { + return Err(meta.error("duplicate telemetry_schema proxy")); + } + proxy = Some(meta.value()?.parse()?); + Ok(()) + })?; + } + Ok(proxy) +} + +fn serde_transparent(attrs: &[Attribute]) -> Result { + let mut transparent = false; + for attr in attrs { + if !attr.path().is_ident("serde") { + continue; + } + attr.parse_nested_meta(|meta| { + consume_meta_value(&meta)?; + if meta.path.is_ident("transparent") { + transparent = true; + } + Ok(()) + })?; + } + Ok(transparent) +} + +fn reject_serde_changes( + attrs: &[Attribute], + has_proxy: bool, + allow_transparent: bool, +) -> Result<()> { + if has_proxy { + return Ok(()); + } + for attr in attrs { + if !attr.path().is_ident("serde") { + continue; + } + attr.parse_nested_meta(|meta| { + let neutral = meta.path.is_ident("default") || + meta.path.is_ident("alias") || + meta.path.is_ident("other") || + (allow_transparent && meta.path.is_ident("transparent")); + consume_meta_value(&meta)?; + if neutral { + Ok(()) + } else { + Err(meta.error( + "serialization-changing serde attributes require #[telemetry_schema(proxy = Type)]", + )) + } + })?; + } + Ok(()) +} + +fn consume_meta_value(meta: &syn::meta::ParseNestedMeta<'_>) -> Result<()> { + if meta.input.peek(Token![=]) { + let _: Expr = meta.value()?.parse()?; + } + Ok(()) +} diff --git a/crates/flux-versioned-types/Cargo.toml b/crates/flux-versioned-types/Cargo.toml index d9fa8e1..6882f17 100644 --- a/crates/flux-versioned-types/Cargo.toml +++ b/crates/flux-versioned-types/Cargo.toml @@ -12,7 +12,14 @@ categories = ["data-structures", "encoding"] [dependencies] bincode.workspace = true +flux-timing.workspace = true +flux-utils.workspace = true flux-versioned-types-macros.workspace = true +serde.workspace = true +tracing.workspace = true +type-hash.workspace = true +uuid.workspace = true +zstd.workspace = true [dev-dependencies] flux.workspace = true diff --git a/crates/flux-versioned-types/src/blob.rs b/crates/flux-versioned-types/src/blob.rs new file mode 100644 index 0000000..2f46c96 --- /dev/null +++ b/crates/flux-versioned-types/src/blob.rs @@ -0,0 +1,371 @@ +//! Versioned blobs for sending and persisting vectors of evolving types. +//! +//! A [`VersionedBlob`] stores a bincode payload alongside the `TypeHash` of +//! the version that was written, mixed with `123_456` (the same obfuscation +//! the generated `versioned_deserialize_vec` expects), so any reader can +//! detect the version and migrate it. Data and per-item metadata travel as +//! two byte strings; for [`InternalMessage`] vectors the metadata is the +//! portable [`InternalMetadata`] projection of each tracking timestamp. +//! +//! The on-disk format is a magic header (`GTCVBLB`) with the type hash and +//! payload lengths, followed by zstd-compressed data and metadata. There is +//! exactly one format version; readers reject anything else. + +use std::path::{Path, PathBuf}; + +use flux_timing::{IngestionTime, InternalMessage, PublishDelta, TrackingTimestamp}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; +use type_hash::TypeHash; + +use crate::VersionedDeserialize; + +const DISK_MAGIC: [u8; 8] = *b"GTCVBLB\0"; +const DISK_VERSION: u32 = 1; +const DISK_HEADER_LEN: usize = DISK_MAGIC.len() + size_of::() + 3 * size_of::(); +const MAX_DISK_BLOB_LEN: u64 = 16_000_000_000; + +/// A versioned type with a stable on-disk home. +/// +/// `//` holds its `.bin` blobs. +/// `versioned_telemetry!` implements this with the type name as the +/// directory; other types can opt in by hand. +pub trait VersionedPersistable: VersionedDeserialize + TypeHash + Serialize { + const PERSIST_DIR: &'static str; + + fn persist_path(base_dir: &Path, filename: &str) -> PathBuf { + base_dir.join(Self::PERSIST_DIR).join(filename).with_added_extension("bin") + } +} + +/// A self-describing batch of versioned values plus per-item metadata. +/// +/// `Serialize`/`Deserialize` so it can go over the wire as-is; see +/// [`VersionedBlob::write_to`] for the framed on-disk form. +/// +/// ``` +/// use flux::type_hash_derive::type_hash_lock; +/// use flux_versioned_types::{VersionedBlob, VersionedPersistable, versioned_telemetry}; +/// +/// versioned_telemetry!(Bid => +/// #[type_hash_lock(hash = 3778581902668456365)] +/// BidV1 { pub price: u64 } +/// +/// #[type_hash_lock(hash = 8711388280237211383)] +/// BidV2 { +/// add { pub size: u64 = 0 } +/// } +/// ); +/// +/// // Sender (possibly an older build): pack V1 values, ship the blob as-is. +/// let old = vec![BidV1 { price: 10 }]; +/// let blob = VersionedBlob::from_parts::(&old, &Vec::::new()); +/// let wire: Vec = bincode::serialize(&blob)?; +/// +/// // Receiver: the payload migrates to the latest version on decode. +/// let blob: VersionedBlob = bincode::deserialize(&wire)?; +/// let latest: Vec = blob.data_as()?; +/// assert_eq!(latest, vec![BidV2 { price: 10, size: 0 }]); +/// +/// // Persisting is the same blob under the type's own directory: +/// // `/Bid/bids.bin`, framed and compressed. +/// let base = std::env::temp_dir().join("flux-doctest"); +/// blob.write_as::(&base, "bids", 1); +/// let loaded = VersionedBlob::read_as::(&base, "bids").unwrap(); +/// assert_eq!(loaded.data_as::()?.len(), 1); +/// +/// // The persist directory defaults to the type name but can be overridden. +/// versioned_telemetry!(Legacy, persist = "legacy_bids" => +/// #[type_hash_lock(hash = 10197174478225006219)] +/// LegacyV1 { pub price: u64 } +/// ); +/// assert_eq!(::PERSIST_DIR, "legacy_bids"); +/// # Ok::<(), Box>(()) +/// ``` +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct VersionedBlob { + pub type_hash: u64, + pub data: Vec, + pub metadata: Vec, +} + +impl VersionedBlob { + /// Pack serializable data and metadata vectors, tagging them with `T`'s + /// version so [`VersionedBlob::data_as`] can migrate them later. + pub fn from_parts( + data: &Data, + metadata: &Metadata, + ) -> Self { + Self { + type_hash: T::TYPE_HASH ^ 123_456, + data: bincode::serialize(data).expect("failed to serialize"), + metadata: bincode::serialize(metadata).expect("failed to serialize metadata"), + } + } + + /// Pack an [`InternalMessage`] slice, projecting each tracking timestamp + /// to portable [`InternalMetadata`]. + pub fn from_tracked(vals: &[InternalMessage]) -> Self { + Self::from_parts::( + &vals.iter().map(InternalMessage::data).collect::>(), + &vals + .iter() + .map(|m| InternalMetadata::from(m.tracking_timestamp())) + .collect::>(), + ) + } + + /// Decode and migrate the data payload to the latest version. + pub fn data_as(&self) -> bincode::Result> { + T::versioned_deserialize_vec(self.type_hash, &self.data) + } + + /// Decode the metadata payload. + pub fn metadata_as(&self) -> bincode::Result> { + bincode::deserialize(&self.metadata) + } + + /// Decode to [`InternalMessage`]s, reattaching portable timing metadata. + /// Falls back to pre-`tile_id` metadata for payloads written before it. + pub fn to_tracked(&self) -> Option>> { + let meta: Vec = bincode::deserialize(&self.metadata) + .or_else(|_| -> Result<_, bincode::Error> { + let old: Vec = bincode::deserialize(&self.metadata)?; + Ok(old.into_iter().map(InternalMetadata::from).collect()) + }) + .inspect_err(|e| { + tracing::error!("metadata deserialize failed: {e}"); + }) + .ok()?; + let items = self + .data_as() + .inspect_err(|e| { + tracing::error!("data deserialize failed for {}: {e}", std::any::type_name::()); + }) + .ok()?; + Some( + items + .into_iter() + .zip(meta) + .map(|(v, m)| InternalMessage::new(m.to_tracking_timestamp(), v)) + .collect(), + ) + } + + /// Write the framed, compressed on-disk form. Failures are logged; there + /// is no partial file worth reporting because the header is written first + /// and readers validate lengths before allocating. + pub fn write_to(&self, path: &Path, compression_level: i32) { + use std::io::Write; + + let (Ok(data_len), Ok(metadata_len)) = + (u64::try_from(self.data.len()), u64::try_from(self.metadata.len())) + else { + tracing::error!(?path, "persistence payload length does not fit on disk"); + return; + }; + let Some(payload_len) = data_len.checked_add(metadata_len) else { + tracing::error!(?path, data_len, metadata_len, "persistence payload length overflow"); + return; + }; + if payload_len > MAX_DISK_BLOB_LEN { + tracing::error!(?path, payload_len, "persistence payload exceeds maximum length"); + return; + } + let Some(parent) = path.parent() else { + tracing::error!(?path, "couldn't find persistence parent directory"); + return; + }; + if let Err(e) = std::fs::create_dir_all(parent) { + tracing::warn!(?path, "couldn't create persistence directory: {e}"); + return; + } + let mut file = match std::fs::File::create(path) { + Ok(file) => file, + Err(e) => { + tracing::error!(?path, "couldn't open persistence file: {e}"); + return; + } + }; + let header = [ + DISK_MAGIC.as_slice(), + &DISK_VERSION.to_le_bytes(), + &self.type_hash.to_le_bytes(), + &data_len.to_le_bytes(), + &metadata_len.to_le_bytes(), + ]; + for bytes in header { + if let Err(e) = file.write_all(bytes) { + tracing::error!(?path, "couldn't write persistence header: {e}"); + return; + } + } + let mut encoder = match zstd::Encoder::new(file, compression_level) { + Ok(encoder) => encoder, + Err(e) => { + tracing::error!(?path, "couldn't create persistence encoder: {e}"); + return; + } + }; + if let Err(e) = encoder.write_all(&self.data) { + tracing::error!(?path, "couldn't write persistence data: {e}"); + return; + } + if let Err(e) = encoder.write_all(&self.metadata) { + tracing::error!(?path, "couldn't write persistence metadata: {e}"); + return; + } + if let Err(e) = encoder.finish() { + tracing::error!(?path, "couldn't finish persistence file: {e}"); + } + } + + /// Write to `//.bin`. See + /// [`VersionedBlob::write_to`] for failure semantics. + pub fn write_as( + &self, + base_dir: &Path, + filename: &str, + compression_level: i32, + ) { + self.write_to(&T::persist_path(base_dir, filename), compression_level); + } + + /// Read back [`VersionedBlob::write_as`] output. + pub fn read_as(base_dir: &Path, filename: &str) -> Option { + Self::read_from(&T::persist_path(base_dir, filename)) + } + + /// Read back [`VersionedBlob::write_to`] output. Returns `None` for + /// missing files, unknown versions, and corrupt or oversized payloads. + pub fn read_from(path: &Path) -> Option { + use std::io::Read; + + let mut file = std::fs::File::open(path) + .inspect_err(|e| tracing::warn!(?path, "issue opening persistence file: {e}")) + .ok()?; + let mut magic = [0; DISK_MAGIC.len()]; + if let Err(e) = file.read_exact(&mut magic) { + tracing::warn!(?path, "couldn't read persistence header: {e}"); + return None; + } + if magic != DISK_MAGIC { + tracing::warn!(?path, "unknown persistence magic"); + return None; + } + let mut header = [0; DISK_HEADER_LEN - DISK_MAGIC.len()]; + if let Err(e) = file.read_exact(&mut header) { + tracing::warn!(?path, "couldn't read persistence header: {e}"); + return None; + } + let version = u32::from_le_bytes(header[..size_of::()].try_into().unwrap()); + if version != DISK_VERSION { + tracing::warn!(?path, version, "unsupported persistence version"); + return None; + } + let type_hash_start = size_of::(); + let data_len_start = type_hash_start + size_of::(); + let metadata_len_start = data_len_start + size_of::(); + let type_hash = + u64::from_le_bytes(header[type_hash_start..data_len_start].try_into().unwrap()); + let data_len = + u64::from_le_bytes(header[data_len_start..metadata_len_start].try_into().unwrap()); + let metadata_len = u64::from_le_bytes(header[metadata_len_start..].try_into().unwrap()); + let Some(expected_len) = data_len.checked_add(metadata_len) else { + tracing::warn!(?path, data_len, metadata_len, "invalid persistence payload length"); + return None; + }; + if expected_len > MAX_DISK_BLOB_LEN { + tracing::warn!(?path, expected_len, "persistence payload exceeds maximum length"); + return None; + } + let (Ok(expected_len), Ok(data_len)) = + (usize::try_from(expected_len), usize::try_from(data_len)) + else { + tracing::warn!(?path, data_len, metadata_len, "invalid persistence payload length"); + return None; + }; + let Some(read_limit) = expected_len.checked_add(1) else { + tracing::warn!(?path, expected_len, "persistence payload length overflows read limit"); + return None; + }; + let mut bytes = Vec::new(); + if let Err(e) = bytes.try_reserve_exact(read_limit) { + tracing::warn!(?path, expected_len, "couldn't allocate persistence payload: {e}"); + return None; + } + let mut decoder = match zstd::Decoder::new(file) { + Ok(decoder) => decoder.take(read_limit as u64), + Err(e) => { + tracing::warn!(?path, "couldn't create persistence decoder: {e}"); + return None; + } + }; + if let Err(e) = decoder.read_to_end(&mut bytes) { + tracing::warn!(?path, "couldn't decode persistence file: {e}"); + return None; + } + if bytes.len() != expected_len { + tracing::warn!( + ?path, + expected_len, + actual_len = bytes.len(), + "invalid persistence payload length" + ); + return None; + } + let metadata = bytes.split_off(data_len); + Some(Self { type_hash, data: bytes, metadata }) + } +} + +/// Portable timing metadata sent over the wire. Wall-clock `Nanos`, safe +/// across machines with different RDTSC rates. +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[repr(C)] +pub struct InternalMetadata { + pub ingestion_t_real: flux_timing::Nanos, + pub publish_t_real: flux_timing::Nanos, + pub tile_id: u16, +} + +/// Pre-tile-id metadata format. Deserialization fallback for payloads written +/// before `tile_id` was added. +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[repr(C)] +pub struct InternalMetadataV1 { + pub ingestion_t_real: flux_timing::Nanos, + pub publish_t_real: flux_timing::Nanos, +} + +impl From for InternalMetadata { + fn from(v: InternalMetadataV1) -> Self { + Self { ingestion_t_real: v.ingestion_t_real, publish_t_real: v.publish_t_real, tile_id: 0 } + } +} + +impl InternalMetadata { + /// Reconstruct a local `TrackingTimestamp` from portable wall-clock values. + pub fn to_tracking_timestamp(self) -> TrackingTimestamp { + let ingestion = IngestionTime::from(self.ingestion_t_real); + let publish = IngestionTime::from(self.publish_t_real); + + // Needed when ingestion and publish the same, but conversion to RDTSC might + // give some noise to have negative delta + let publish_internal = std::cmp::max(ingestion.internal(), publish.internal()); + TrackingTimestamp { + ingestion_t: ingestion, + publish_delta: PublishDelta::new(self.tile_id) + .from_ingestion_and_publish_t(ingestion.internal(), publish_internal), + } + } +} + +impl From for InternalMetadata { + fn from(t: TrackingTimestamp) -> Self { + Self { + ingestion_t_real: t.ingestion_t().real(), + publish_t_real: t.publish_t(), + tile_id: t.tile_id(), + } + } +} diff --git a/crates/flux-versioned-types/src/lib.rs b/crates/flux-versioned-types/src/lib.rs index 58820ca..f79c051 100644 --- a/crates/flux-versioned-types/src/lib.rs +++ b/crates/flux-versioned-types/src/lib.rs @@ -1,11 +1,20 @@ //! Version-tolerant Rust data types with explicit, compile-time migrations. //! -//! This crate deliberately contains no persistence directories, telemetry -//! registries, or application-specific metadata. It defines evolving types and -//! decodes a stored bincode payload using the type hash of the version that was -//! written. +//! The core is [`VersionedDeserialize`]: evolving types decode a stored +//! bincode payload using the type hash of the version that was written. +//! [`VersionedBlob`] packs such payloads with their hash for sending and +//! persisting, and [`TelemetrySchema`] describes their SQL/Arrow-facing +//! shape. None of this knows about any application's registries or metadata: +//! those stay downstream. -pub use flux_versioned_types_macros::{evolve_enum, evolve_struct, roll_chain_into}; +pub mod blob; +mod schema; + +pub use blob::{InternalMetadata, InternalMetadataV1, VersionedBlob, VersionedPersistable}; +pub use flux_versioned_types_macros::{ + TelemetrySchema, evolve_enum, evolve_struct, roll_chain_into, +}; +pub use schema::TelemetrySchema; /// A type whose historical bincode payloads can be migrated to its latest form. pub trait VersionedDeserialize: Sized { @@ -41,14 +50,52 @@ macro_rules! versioned_struct { }; } -/// Define an evolving enum and its hash-directed decoder. +/// Define an evolving enum, its hash-directed decoder, and its +/// `TelemetrySchema`. #[macro_export] macro_rules! versioned_enum { ($name:ident => $($tokens:tt)*) => { $crate::evolve_enum! { roll_into $name + final_attrs { + #[derive($crate::TelemetrySchema)] + } $($tokens)* } $crate::impl_versioned_deserialize!($name); }; } + +/// Define an evolving telemetry struct. +/// +/// Like [`versioned_struct`], but the latest version also derives +/// [`TelemetrySchema`] so it is queryable, and the type gets a +/// [`VersionedPersistable`] home: the type name by default, or +/// `persist = "dir"` when given. +#[macro_export] +macro_rules! versioned_telemetry { + ($name:ident, persist = $dir:expr => $($tokens:tt)*) => { + $crate::__versioned_telemetry_inner!($name, $dir => $($tokens)*); + }; + ($name:ident => $($tokens:tt)*) => { + $crate::__versioned_telemetry_inner!($name, stringify!($name) => $($tokens)*); + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __versioned_telemetry_inner { + ($name:ident, $dir:expr => $($tokens:tt)*) => { + $crate::evolve_struct! { + roll_into $name + final_attrs { + #[derive($crate::TelemetrySchema)] + } + $($tokens)* + } + $crate::impl_versioned_deserialize!($name); + impl $crate::VersionedPersistable for $name { + const PERSIST_DIR: &'static str = $dir; + } + }; +} diff --git a/crates/flux-versioned-types/src/schema.rs b/crates/flux-versioned-types/src/schema.rs new file mode 100644 index 0000000..fc05370 --- /dev/null +++ b/crates/flux-versioned-types/src/schema.rs @@ -0,0 +1,97 @@ +//! SQL/Arrow-facing shape of a telemetry type. +//! +//! A type's serialized form is not always the shape you want in a table: +//! hashes and addresses read better as hex strings, time newtypes as integers, +//! enums with payloads as flattened variant columns. `TelemetrySchema` +//! maps each Rust type to `Proxy` (nested shape) and `FlattenedProxy` (one +//! optional column per variant, for enums) mirrors, both `Deserialize`-only. +//! The derive generates the mirrors and rejects serialization-changing serde +//! attributes unless the field opts out with an explicit proxy. +//! +//! Foreign types with no blessed mapping (UUIDs, 128-bit integers, hash and +//! address types) use that same escape hatch at the field level; only the +//! standard library and flux's own time and container types are covered here. + +use serde::de::DeserializeOwned; + +pub trait TelemetrySchema { + type Proxy: DeserializeOwned; + type FlattenedProxy: DeserializeOwned; +} + +macro_rules! same_proxy { + ($($type:ty),* $(,)?) => {$( + impl TelemetrySchema for $type { + type Proxy = Self; + type FlattenedProxy = Self; + } + )*}; +} + +same_proxy!((), bool, char, String, i8, i16, i32, i64, u8, u16, u32, u64, f32, f64,); + +impl TelemetrySchema for isize { + type Proxy = i64; + type FlattenedProxy = i64; +} + +impl TelemetrySchema for usize { + type Proxy = u64; + type FlattenedProxy = u64; +} + +impl TelemetrySchema for Option { + type Proxy = Option; + type FlattenedProxy = Option; +} + +impl TelemetrySchema for Vec { + type Proxy = Vec; + type FlattenedProxy = Vec; +} + +impl TelemetrySchema for [T; N] +where + [T::Proxy; N]: DeserializeOwned, + [T::FlattenedProxy; N]: DeserializeOwned, +{ + type Proxy = [T::Proxy; N]; + type FlattenedProxy = [T::FlattenedProxy; N]; +} + +macro_rules! u64_proxy { + ($($type:ty),* $(,)?) => {$( + impl TelemetrySchema for $type { + type Proxy = u64; + type FlattenedProxy = u64; + } + )*}; +} + +u64_proxy!(flux_timing::Nanos, flux_timing::Duration, flux_timing::Instant); + +impl TelemetrySchema for uuid::Uuid { + type Proxy = String; + type FlattenedProxy = String; +} + +impl TelemetrySchema for flux_utils::ArrayVec { + type Proxy = Vec; + type FlattenedProxy = Vec; +} + +impl TelemetrySchema for flux_utils::ArrayStr { + type Proxy = Vec; + type FlattenedProxy = Vec; +} + +macro_rules! tuple_proxy { + ($(($($type:ident),+)),* $(,)?) => {$( + impl<$($type: TelemetrySchema),+> TelemetrySchema for ($($type,)+) { + type Proxy = ($($type::Proxy,)+); + type FlattenedProxy = ($($type::FlattenedProxy,)+); + } + )*}; +} + +tuple_proxy!((A), (A, B), (A, B, C), (A, B, C, D)); From b9898ccaac6cae57c1c2504c3d0c5bc78e5660f9 Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Mon, 14 Sep 2026 14:14:41 +0100 Subject: [PATCH 02/10] fix(versioned-types): require explicit persist for automatic persistable impl --- crates/flux-versioned-types/src/blob.rs | 10 +++++----- crates/flux-versioned-types/src/lib.rs | 17 ++++++++--------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/crates/flux-versioned-types/src/blob.rs b/crates/flux-versioned-types/src/blob.rs index 2f46c96..2a433d6 100644 --- a/crates/flux-versioned-types/src/blob.rs +++ b/crates/flux-versioned-types/src/blob.rs @@ -27,8 +27,8 @@ const MAX_DISK_BLOB_LEN: u64 = 16_000_000_000; /// A versioned type with a stable on-disk home. /// /// `//` holds its `.bin` blobs. -/// `versioned_telemetry!` implements this with the type name as the -/// directory; other types can opt in by hand. +/// `versioned_telemetry!` with `persist = "dir"` implements this; +/// other types can opt in by hand. pub trait VersionedPersistable: VersionedDeserialize + TypeHash + Serialize { const PERSIST_DIR: &'static str; @@ -46,7 +46,7 @@ pub trait VersionedPersistable: VersionedDeserialize + TypeHash + Serialize { /// use flux::type_hash_derive::type_hash_lock; /// use flux_versioned_types::{VersionedBlob, VersionedPersistable, versioned_telemetry}; /// -/// versioned_telemetry!(Bid => +/// versioned_telemetry!(Bid, persist = "bids" => /// #[type_hash_lock(hash = 3778581902668456365)] /// BidV1 { pub price: u64 } /// @@ -66,8 +66,8 @@ pub trait VersionedPersistable: VersionedDeserialize + TypeHash + Serialize { /// let latest: Vec = blob.data_as()?; /// assert_eq!(latest, vec![BidV2 { price: 10, size: 0 }]); /// -/// // Persisting is the same blob under the type's own directory: -/// // `/Bid/bids.bin`, framed and compressed. +/// // Persisting is the same blob under the type's persist directory: +/// // `/bids/bids.bin`, framed and compressed. /// let base = std::env::temp_dir().join("flux-doctest"); /// blob.write_as::(&base, "bids", 1); /// let loaded = VersionedBlob::read_as::(&base, "bids").unwrap(); diff --git a/crates/flux-versioned-types/src/lib.rs b/crates/flux-versioned-types/src/lib.rs index f79c051..e3d688a 100644 --- a/crates/flux-versioned-types/src/lib.rs +++ b/crates/flux-versioned-types/src/lib.rs @@ -69,23 +69,25 @@ macro_rules! versioned_enum { /// Define an evolving telemetry struct. /// /// Like [`versioned_struct`], but the latest version also derives -/// [`TelemetrySchema`] so it is queryable, and the type gets a -/// [`VersionedPersistable`] home: the type name by default, or -/// `persist = "dir"` when given. +/// [`TelemetrySchema`] so it is queryable. With `persist = "dir"` the type +/// also gets a [`VersionedPersistable`] home under that directory. #[macro_export] macro_rules! versioned_telemetry { ($name:ident, persist = $dir:expr => $($tokens:tt)*) => { - $crate::__versioned_telemetry_inner!($name, $dir => $($tokens)*); + $crate::__versioned_telemetry_inner!($name => $($tokens)*); + impl $crate::VersionedPersistable for $name { + const PERSIST_DIR: &'static str = $dir; + } }; ($name:ident => $($tokens:tt)*) => { - $crate::__versioned_telemetry_inner!($name, stringify!($name) => $($tokens)*); + $crate::__versioned_telemetry_inner!($name => $($tokens)*); }; } #[macro_export] #[doc(hidden)] macro_rules! __versioned_telemetry_inner { - ($name:ident, $dir:expr => $($tokens:tt)*) => { + ($name:ident => $($tokens:tt)*) => { $crate::evolve_struct! { roll_into $name final_attrs { @@ -94,8 +96,5 @@ macro_rules! __versioned_telemetry_inner { $($tokens)* } $crate::impl_versioned_deserialize!($name); - impl $crate::VersionedPersistable for $name { - const PERSIST_DIR: &'static str = $dir; - } }; } From 6623f044e70b03f782c3a395fe85c7dde0fd7a08 Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Mon, 14 Sep 2026 14:15:41 +0100 Subject: [PATCH 03/10] feat(versioned-types): persist override for versioned enums --- crates/flux-versioned-types/src/blob.rs | 9 ++++++++- crates/flux-versioned-types/src/lib.rs | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/crates/flux-versioned-types/src/blob.rs b/crates/flux-versioned-types/src/blob.rs index 2a433d6..fbf74de 100644 --- a/crates/flux-versioned-types/src/blob.rs +++ b/crates/flux-versioned-types/src/blob.rs @@ -44,7 +44,7 @@ pub trait VersionedPersistable: VersionedDeserialize + TypeHash + Serialize { /// /// ``` /// use flux::type_hash_derive::type_hash_lock; -/// use flux_versioned_types::{VersionedBlob, VersionedPersistable, versioned_telemetry}; +/// use flux_versioned_types::{VersionedBlob, VersionedPersistable, versioned_enum, versioned_telemetry}; /// /// versioned_telemetry!(Bid, persist = "bids" => /// #[type_hash_lock(hash = 3778581902668456365)] @@ -79,6 +79,13 @@ pub trait VersionedPersistable: VersionedDeserialize + TypeHash + Serialize { /// LegacyV1 { pub price: u64 } /// ); /// assert_eq!(::PERSIST_DIR, "legacy_bids"); +/// +/// // Enums get the same machinery. +/// versioned_enum!(Mode, persist = "modes" => +/// #[type_hash_lock(hash = 2116725509198536217)] +/// ModeV1 { Fast, Slow } +/// ); +/// assert_eq!(::PERSIST_DIR, "modes"); /// # Ok::<(), Box>(()) /// ``` #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/crates/flux-versioned-types/src/lib.rs b/crates/flux-versioned-types/src/lib.rs index e3d688a..c0ba9d5 100644 --- a/crates/flux-versioned-types/src/lib.rs +++ b/crates/flux-versioned-types/src/lib.rs @@ -52,8 +52,25 @@ macro_rules! versioned_struct { /// Define an evolving enum, its hash-directed decoder, and its /// `TelemetrySchema`. +/// +/// With `persist = "dir"` the type also gets a [`VersionedPersistable`] +/// home under that directory. #[macro_export] macro_rules! versioned_enum { + ($name:ident, persist = $dir:expr => $($tokens:tt)*) => { + $crate::__versioned_enum_inner!($name => $($tokens)*); + impl $crate::VersionedPersistable for $name { + const PERSIST_DIR: &'static str = $dir; + } + }; + ($name:ident => $($tokens:tt)*) => { + $crate::__versioned_enum_inner!($name => $($tokens)*); + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! __versioned_enum_inner { ($name:ident => $($tokens:tt)*) => { $crate::evolve_enum! { roll_into $name From 4a2c5ba332f0b682865cb90abe217be0580c38f2 Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Mon, 14 Sep 2026 14:26:14 +0100 Subject: [PATCH 04/10] feat(versioned-types): slotless telemetry bridge envelope --- crates/flux-versioned-types/src/bridge.rs | 149 ++++++++++++++++++++++ crates/flux-versioned-types/src/lib.rs | 5 + 2 files changed, 154 insertions(+) create mode 100644 crates/flux-versioned-types/src/bridge.rs diff --git a/crates/flux-versioned-types/src/bridge.rs b/crates/flux-versioned-types/src/bridge.rs new file mode 100644 index 0000000..8a36ee0 --- /dev/null +++ b/crates/flux-versioned-types/src/bridge.rs @@ -0,0 +1,149 @@ +//! Batched telemetry envelopes for spine-to-receiver transport. +//! +//! A [`TelemetryBridgeMessage`] carries one type's accumulated batch for a +//! slot window: the writer's `type_hash` so the reader can migrate it, a +//! `type_name` routing label, and the flattened `(data, metadata)` byte +//! pair. It deliberately carries no slot: slot boundaries travel as an +//! ordinary sentinel message and per-item timing lives in the metadata +//! payload, so the envelope stays generic transport. +//! +//! Senders compressing on the wire use [`TelemetryBridgeMessageV2`], which +//! records the [`TelemetryBridgePayloadEncoding`] alongside the payloads. + +use std::{ + borrow::Cow, + io::{self, Read}, +}; + +use serde::{Deserialize, Serialize}; + +pub const DEFAULT_TELEMETRY_BRIDGE_ZSTD_LEVEL: i32 = 0; + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[repr(C)] +pub struct TelemetryBridgeMessage { + pub type_hash: u64, + // Not 100% required, but useful to direct handling of the hashes to + // different parts of the code + knowing which hashes belong to which + // type name, e.g. `Builder.Bundle.Ingested`. + pub type_name: Cow<'static, str>, + pub flattened_msg_meta: (Vec, Vec), +} + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[repr(C)] +pub struct TelemetryBridgeMessageV2 { + pub type_hash: u64, + pub type_name: Cow<'static, str>, + pub flattened_msg_meta: (Vec, Vec), + pub payload_encoding: TelemetryBridgePayloadEncoding, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub enum TelemetryBridgePayloadEncoding { + Uncompressed, + Zstd { data_len: u64, metadata_len: u64 }, +} + +impl TelemetryBridgeMessage { + pub fn new(type_hash: u64, type_name: String) -> Self { + Self { type_hash, type_name: Cow::Owned(type_name), flattened_msg_meta: (vec![], vec![]) } + } + + /// Append one item's serialized data and metadata to the batch. + pub fn push(&mut self, mut data: Vec, mut metadata: Vec) { + self.flattened_msg_meta.0.append(&mut data); + self.flattened_msg_meta.1.append(&mut metadata); + } + + /// Compress both payloads, recording their uncompressed lengths so the + /// reader can validate the decoded size exactly. + /// + /// ``` + /// use flux_versioned_types::TelemetryBridgeMessage; + /// + /// let mut msg = TelemetryBridgeMessage::new(7, "test".to_string()); + /// msg.push(b"payload-data".repeat(64), b"metadata".repeat(64)); + /// let compressed = msg.to_zstd_v2(0).unwrap(); + /// let back = compressed.into_uncompressed_message().unwrap(); + /// assert_eq!(back.flattened_msg_meta.0, b"payload-data".repeat(64)); + /// assert_eq!(back.flattened_msg_meta.1, b"metadata".repeat(64)); + /// # Ok::<(), std::io::Error>(()) + /// ``` + pub fn to_zstd_v2(&self, zstd_level: i32) -> io::Result { + let data_len = self.flattened_msg_meta.0.len() as u64; + let metadata_len = self.flattened_msg_meta.1.len() as u64; + Ok(TelemetryBridgeMessageV2 { + type_hash: self.type_hash, + type_name: self.type_name.clone(), + flattened_msg_meta: ( + compress_vec(&self.flattened_msg_meta.0, zstd_level)?, + compress_vec(&self.flattened_msg_meta.1, zstd_level)?, + ), + payload_encoding: TelemetryBridgePayloadEncoding::Zstd { data_len, metadata_len }, + }) + } +} + +impl TelemetryBridgeMessageV2 { + pub fn into_uncompressed_message(self) -> io::Result { + let (data, metadata) = match self.payload_encoding { + TelemetryBridgePayloadEncoding::Uncompressed => self.flattened_msg_meta, + TelemetryBridgePayloadEncoding::Zstd { data_len, metadata_len } => ( + decompress_vec(&self.flattened_msg_meta.0, data_len)?, + decompress_vec(&self.flattened_msg_meta.1, metadata_len)?, + ), + }; + Ok(TelemetryBridgeMessage { + type_hash: self.type_hash, + type_name: self.type_name, + flattened_msg_meta: (data, metadata), + }) + } +} + +impl From for TelemetryBridgeMessageV2 { + fn from(msg: TelemetryBridgeMessage) -> Self { + Self { + type_hash: msg.type_hash, + type_name: msg.type_name, + flattened_msg_meta: msg.flattened_msg_meta, + payload_encoding: TelemetryBridgePayloadEncoding::Uncompressed, + } + } +} + +impl std::fmt::Display for TelemetryBridgeMessage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "TelemetryBridgeMessage(type_hash={}, type_name={}, {}Kb)", + self.type_hash, + self.type_name, + (self.flattened_msg_meta.0.len() + self.flattened_msg_meta.1.len()) / 1000 + ) + } +} + +fn compress_vec(bytes: &[u8], zstd_level: i32) -> io::Result> { + zstd::stream::encode_all(bytes, zstd_level) +} + +fn decompress_vec(bytes: &[u8], expected_len: u64) -> io::Result> { + let read_limit = expected_len.checked_add(1).ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "telemetry bridge payload length overflow") + })?; + let mut reader = zstd::stream::read::Decoder::new(bytes)?.take(read_limit); + let mut out = Vec::new(); + reader.read_to_end(&mut out)?; + if out.len() as u64 != expected_len { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!( + "telemetry bridge payload decoded to {} bytes, expected {expected_len}", + out.len() + ), + )); + } + Ok(out) +} diff --git a/crates/flux-versioned-types/src/lib.rs b/crates/flux-versioned-types/src/lib.rs index c0ba9d5..8d9a1f8 100644 --- a/crates/flux-versioned-types/src/lib.rs +++ b/crates/flux-versioned-types/src/lib.rs @@ -8,9 +8,14 @@ //! those stay downstream. pub mod blob; +pub mod bridge; mod schema; pub use blob::{InternalMetadata, InternalMetadataV1, VersionedBlob, VersionedPersistable}; +pub use bridge::{ + DEFAULT_TELEMETRY_BRIDGE_ZSTD_LEVEL, TelemetryBridgeMessage, TelemetryBridgeMessageV2, + TelemetryBridgePayloadEncoding, +}; pub use flux_versioned_types_macros::{ TelemetrySchema, evolve_enum, evolve_struct, roll_chain_into, }; From 870fd00942dba3b5d7d7f7ffaf534d3769c4061b Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Mon, 14 Sep 2026 14:27:03 +0100 Subject: [PATCH 05/10] refactor(versioned-types): rename telemetry bridge to wire --- crates/flux-versioned-types/src/lib.rs | 9 ++-- .../src/{bridge.rs => wire.rs} | 52 +++++++++---------- 2 files changed, 30 insertions(+), 31 deletions(-) rename crates/flux-versioned-types/src/{bridge.rs => wire.rs} (73%) diff --git a/crates/flux-versioned-types/src/lib.rs b/crates/flux-versioned-types/src/lib.rs index 8d9a1f8..ac6a9c2 100644 --- a/crates/flux-versioned-types/src/lib.rs +++ b/crates/flux-versioned-types/src/lib.rs @@ -8,18 +8,17 @@ //! those stay downstream. pub mod blob; -pub mod bridge; mod schema; +pub mod wire; pub use blob::{InternalMetadata, InternalMetadataV1, VersionedBlob, VersionedPersistable}; -pub use bridge::{ - DEFAULT_TELEMETRY_BRIDGE_ZSTD_LEVEL, TelemetryBridgeMessage, TelemetryBridgeMessageV2, - TelemetryBridgePayloadEncoding, -}; pub use flux_versioned_types_macros::{ TelemetrySchema, evolve_enum, evolve_struct, roll_chain_into, }; pub use schema::TelemetrySchema; +pub use wire::{ + DEFAULT_TELEMETRY_WIRE_ZSTD_LEVEL, TelemetryWire, TelemetryWirePayloadEncoding, TelemetryWireV2, +}; /// A type whose historical bincode payloads can be migrated to its latest form. pub trait VersionedDeserialize: Sized { diff --git a/crates/flux-versioned-types/src/bridge.rs b/crates/flux-versioned-types/src/wire.rs similarity index 73% rename from crates/flux-versioned-types/src/bridge.rs rename to crates/flux-versioned-types/src/wire.rs index 8a36ee0..5f423d8 100644 --- a/crates/flux-versioned-types/src/bridge.rs +++ b/crates/flux-versioned-types/src/wire.rs @@ -1,14 +1,14 @@ //! Batched telemetry envelopes for spine-to-receiver transport. //! -//! A [`TelemetryBridgeMessage`] carries one type's accumulated batch for a +//! A [`TelemetryWire`] carries one type's accumulated batch for a //! slot window: the writer's `type_hash` so the reader can migrate it, a //! `type_name` routing label, and the flattened `(data, metadata)` byte //! pair. It deliberately carries no slot: slot boundaries travel as an //! ordinary sentinel message and per-item timing lives in the metadata //! payload, so the envelope stays generic transport. //! -//! Senders compressing on the wire use [`TelemetryBridgeMessageV2`], which -//! records the [`TelemetryBridgePayloadEncoding`] alongside the payloads. +//! Senders compressing on the wire use [`TelemetryWireV2`], which +//! records the [`TelemetryWirePayloadEncoding`] alongside the payloads. use std::{ borrow::Cow, @@ -17,11 +17,11 @@ use std::{ use serde::{Deserialize, Serialize}; -pub const DEFAULT_TELEMETRY_BRIDGE_ZSTD_LEVEL: i32 = 0; +pub const DEFAULT_TELEMETRY_WIRE_ZSTD_LEVEL: i32 = 0; #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[repr(C)] -pub struct TelemetryBridgeMessage { +pub struct TelemetryWire { pub type_hash: u64, // Not 100% required, but useful to direct handling of the hashes to // different parts of the code + knowing which hashes belong to which @@ -32,20 +32,20 @@ pub struct TelemetryBridgeMessage { #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] #[repr(C)] -pub struct TelemetryBridgeMessageV2 { +pub struct TelemetryWireV2 { pub type_hash: u64, pub type_name: Cow<'static, str>, pub flattened_msg_meta: (Vec, Vec), - pub payload_encoding: TelemetryBridgePayloadEncoding, + pub payload_encoding: TelemetryWirePayloadEncoding, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub enum TelemetryBridgePayloadEncoding { +pub enum TelemetryWirePayloadEncoding { Uncompressed, Zstd { data_len: u64, metadata_len: u64 }, } -impl TelemetryBridgeMessage { +impl TelemetryWire { pub fn new(type_hash: u64, type_name: String) -> Self { Self { type_hash, type_name: Cow::Owned(type_name), flattened_msg_meta: (vec![], vec![]) } } @@ -60,9 +60,9 @@ impl TelemetryBridgeMessage { /// reader can validate the decoded size exactly. /// /// ``` - /// use flux_versioned_types::TelemetryBridgeMessage; + /// use flux_versioned_types::TelemetryWire; /// - /// let mut msg = TelemetryBridgeMessage::new(7, "test".to_string()); + /// let mut msg = TelemetryWire::new(7, "test".to_string()); /// msg.push(b"payload-data".repeat(64), b"metadata".repeat(64)); /// let compressed = msg.to_zstd_v2(0).unwrap(); /// let back = compressed.into_uncompressed_message().unwrap(); @@ -70,31 +70,31 @@ impl TelemetryBridgeMessage { /// assert_eq!(back.flattened_msg_meta.1, b"metadata".repeat(64)); /// # Ok::<(), std::io::Error>(()) /// ``` - pub fn to_zstd_v2(&self, zstd_level: i32) -> io::Result { + pub fn to_zstd_v2(&self, zstd_level: i32) -> io::Result { let data_len = self.flattened_msg_meta.0.len() as u64; let metadata_len = self.flattened_msg_meta.1.len() as u64; - Ok(TelemetryBridgeMessageV2 { + Ok(TelemetryWireV2 { type_hash: self.type_hash, type_name: self.type_name.clone(), flattened_msg_meta: ( compress_vec(&self.flattened_msg_meta.0, zstd_level)?, compress_vec(&self.flattened_msg_meta.1, zstd_level)?, ), - payload_encoding: TelemetryBridgePayloadEncoding::Zstd { data_len, metadata_len }, + payload_encoding: TelemetryWirePayloadEncoding::Zstd { data_len, metadata_len }, }) } } -impl TelemetryBridgeMessageV2 { - pub fn into_uncompressed_message(self) -> io::Result { +impl TelemetryWireV2 { + pub fn into_uncompressed_message(self) -> io::Result { let (data, metadata) = match self.payload_encoding { - TelemetryBridgePayloadEncoding::Uncompressed => self.flattened_msg_meta, - TelemetryBridgePayloadEncoding::Zstd { data_len, metadata_len } => ( + TelemetryWirePayloadEncoding::Uncompressed => self.flattened_msg_meta, + TelemetryWirePayloadEncoding::Zstd { data_len, metadata_len } => ( decompress_vec(&self.flattened_msg_meta.0, data_len)?, decompress_vec(&self.flattened_msg_meta.1, metadata_len)?, ), }; - Ok(TelemetryBridgeMessage { + Ok(TelemetryWire { type_hash: self.type_hash, type_name: self.type_name, flattened_msg_meta: (data, metadata), @@ -102,22 +102,22 @@ impl TelemetryBridgeMessageV2 { } } -impl From for TelemetryBridgeMessageV2 { - fn from(msg: TelemetryBridgeMessage) -> Self { +impl From for TelemetryWireV2 { + fn from(msg: TelemetryWire) -> Self { Self { type_hash: msg.type_hash, type_name: msg.type_name, flattened_msg_meta: msg.flattened_msg_meta, - payload_encoding: TelemetryBridgePayloadEncoding::Uncompressed, + payload_encoding: TelemetryWirePayloadEncoding::Uncompressed, } } } -impl std::fmt::Display for TelemetryBridgeMessage { +impl std::fmt::Display for TelemetryWire { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "TelemetryBridgeMessage(type_hash={}, type_name={}, {}Kb)", + "TelemetryWire(type_hash={}, type_name={}, {}Kb)", self.type_hash, self.type_name, (self.flattened_msg_meta.0.len() + self.flattened_msg_meta.1.len()) / 1000 @@ -131,7 +131,7 @@ fn compress_vec(bytes: &[u8], zstd_level: i32) -> io::Result> { fn decompress_vec(bytes: &[u8], expected_len: u64) -> io::Result> { let read_limit = expected_len.checked_add(1).ok_or_else(|| { - io::Error::new(io::ErrorKind::InvalidData, "telemetry bridge payload length overflow") + io::Error::new(io::ErrorKind::InvalidData, "telemetry wire payload length overflow") })?; let mut reader = zstd::stream::read::Decoder::new(bytes)?.take(read_limit); let mut out = Vec::new(); @@ -140,7 +140,7 @@ fn decompress_vec(bytes: &[u8], expected_len: u64) -> io::Result> { return Err(io::Error::new( io::ErrorKind::InvalidData, format!( - "telemetry bridge payload decoded to {} bytes, expected {expected_len}", + "telemetry wire payload decoded to {} bytes, expected {expected_len}", out.len() ), )); From a67010284cc8b1eae467bcdc4977d566191325d5 Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Mon, 14 Sep 2026 14:28:44 +0100 Subject: [PATCH 06/10] feat(versioned-types): telemetry schemas for alloy primitive types --- Cargo.lock | 1538 ++++++++++++++++++++- Cargo.toml | 1 + crates/flux-versioned-types/Cargo.toml | 1 + crates/flux-versioned-types/src/schema.rs | 24 + 4 files changed, 1549 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d0e9e7..5a85592 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,45 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy-primitives" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5e9dbd49258ac3ab893a481d46be29b58be7f734dcac46cd80b6b13ee36566c" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "fixed-cache", + "foldhash 0.2.0", + "hashbrown 0.17.1", + "indexmap 2.13.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.2", + "rapidhash", + "ruint", + "rustc-hash", + "secp256k1", + "serde", + "sha3", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24671b1f62edcf0f9b62994c7bf72cd621a04a4b99f5020ece1a647b40e2f103" +dependencies = [ + "arrayvec", + "bytes", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -91,6 +130,269 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" +dependencies = [ + "ark-ff-asm 0.6.0", + "ark-ff-macros 0.6.0", + "ark-serialize 0.6.0", + "ark-std 0.6.0", + "digest 0.10.7", + "educe", + "num-bigint", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ff-asm" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ff-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.6.0", + "digest 0.10.7", + "num-bigint", + "serde_with", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -114,6 +416,24 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bincode" version = "1.3.3" @@ -147,6 +467,42 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6712f9c6fd6785b3b270884e57c441c403dc5d7e19ca45368c97c7a1de3000ec" +dependencies = [ + "bitcoin-internals", + "hex-conservative 1.3.0", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + +[[package]] +name = "bitcoin-io" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.3", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -162,6 +518,36 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -171,18 +557,39 @@ dependencies = [ "objc2", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "bytemuck" version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" @@ -341,6 +748,54 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "const-hex" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -358,6 +813,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + [[package]] name = "criterion" version = "0.8.2" @@ -370,7 +843,7 @@ dependencies = [ "ciborium", "clap", "criterion-plot", - "itertools", + "itertools 0.13.0", "num-traits", "oorandom", "page_size", @@ -390,7 +863,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" dependencies = [ "cast", - "itertools", + "itertools 0.13.0", ] [[package]] @@ -458,6 +931,37 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctrlc" version = "3.5.2" @@ -516,6 +1020,121 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common 0.1.6", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + [[package]] name = "directories" version = "5.0.1" @@ -543,17 +1162,88 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" dependencies = [ - "bitflags 2.10.0", - "block2", - "libc", - "objc2", + "bitflags 2.10.0", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" +dependencies = [ + "enum-ordinalize-derive", ] [[package]] -name = "either" -version = "1.15.0" +name = "enum-ordinalize-derive" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] [[package]] name = "equivalent" @@ -577,12 +1267,66 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixed-cache" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fe63500644ef0269fe6b744e7e5dc5c20b5eebf3d881bc2be53f194636f6583" +dependencies = [ + "equivalent", + "rapidhash", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "flux" version = "0.3.0" @@ -708,7 +1452,7 @@ dependencies = [ "bitflags 2.10.0", "crossterm", "flux", - "indexmap", + "indexmap 2.13.0", "ratatui", "serde", "signal-hook", @@ -759,6 +1503,7 @@ dependencies = [ name = "flux-versioned-types" version = "0.3.0" dependencies = [ + "alloy-primitives", "bincode", "flux", "flux-timing", @@ -787,6 +1532,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -882,6 +1639,17 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -931,6 +1699,17 @@ dependencies = [ "spinning_top", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "half" version = "2.7.1" @@ -942,6 +1721,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -956,7 +1741,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -965,6 +1750,15 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", +] + [[package]] name = "heck" version = "0.5.0" @@ -977,6 +1771,39 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3fef046dca3ca91ee1408a8c1b80ab777e80a4d308d1bf4e7adb3fcb047e08" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-conservative" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271e0d19bcb473b6675739a2b536076b24a082316cb5199ad918edce10c599e8" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "httparse" version = "1.10.1" @@ -989,6 +1816,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" +[[package]] +name = "hybrid-array" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" +dependencies = [ + "typenum", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -1019,6 +1855,37 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -1070,6 +1937,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -1085,6 +1961,60 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jiff" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jobserver" version = "0.1.34" @@ -1105,6 +2035,54 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", +] + +[[package]] +name = "keccak-asm" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1117,6 +2095,12 @@ version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libredox" version = "0.1.12" @@ -1245,6 +2229,31 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1252,6 +2261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1313,6 +2323,34 @@ dependencies = [ "winapi", ] +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1348,6 +2386,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" +[[package]] +name = "pest" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d45aeb61b4bf818e12d4205f2466f8c4748f85f4fce0146d1c03d69d753f0ad" +dependencies = [ + "memchr", + "ucd-trie", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1360,6 +2408,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -1398,7 +2456,22 @@ dependencies = [ name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" @@ -1409,6 +2482,17 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -1427,6 +2511,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bitflags 2.10.0", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "unarray", +] + [[package]] name = "quanta" version = "0.12.6" @@ -1457,6 +2556,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1516,6 +2621,24 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" +dependencies = [ + "rustversion", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -1528,7 +2651,7 @@ dependencies = [ "crossterm", "indoc", "instability", - "itertools", + "itertools 0.13.0", "lru", "paste", "strum", @@ -1586,6 +2709,26 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "regex" version = "1.12.3" @@ -1615,12 +2758,91 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "ruint" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e99bff0393163bb25029a6af25d3d8d202ba5b5438a74d1bd8789f5c822970" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "ark-ff 0.6.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-hash" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.28", +] + [[package]] name = "rustix" version = "0.38.44" @@ -1668,12 +2890,94 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.2", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.229" @@ -1717,6 +3021,57 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_with" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "time", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" +dependencies = [ + "digest 0.11.3", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1776,6 +3131,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "slab" version = "0.4.12" @@ -1815,6 +3180,16 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1849,6 +3224,23 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -1871,6 +3263,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.25.0" @@ -1933,6 +3331,36 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -1948,6 +3376,15 @@ name = "tinyvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml_datetime" @@ -1964,7 +3401,7 @@ version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap", + "indexmap 2.13.0", "toml_datetime", "toml_parser", "winnow", @@ -2054,6 +3491,36 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicode-ident" version = "1.0.22" @@ -2072,7 +3539,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "itertools", + "itertools 0.13.0", "unicode-segmentation", "unicode-width 0.1.14", ] @@ -2089,6 +3556,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2111,6 +3584,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -2520,6 +3999,15 @@ version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.8.39" @@ -2540,6 +4028,26 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zmij" version = "1.0.19" diff --git a/Cargo.toml b/Cargo.toml index f917fc9..caa387f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ type-hash = { path = "crates/type-hash" } type-hash-derive = { path = "crates/type-hash-derive" } +alloy-primitives = "1" auto_impl = "1.2.0" bincode = "1.3.3" bitcode = { features = ["serde"], version = "0.6.3" } diff --git a/crates/flux-versioned-types/Cargo.toml b/crates/flux-versioned-types/Cargo.toml index 6882f17..0cb4b01 100644 --- a/crates/flux-versioned-types/Cargo.toml +++ b/crates/flux-versioned-types/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["schema", "versioning", "migration", "serde"] categories = ["data-structures", "encoding"] [dependencies] +alloy-primitives.workspace = true bincode.workspace = true flux-timing.workspace = true flux-utils.workspace = true diff --git a/crates/flux-versioned-types/src/schema.rs b/crates/flux-versioned-types/src/schema.rs index fc05370..48a2fab 100644 --- a/crates/flux-versioned-types/src/schema.rs +++ b/crates/flux-versioned-types/src/schema.rs @@ -75,6 +75,30 @@ impl TelemetrySchema for uuid::Uuid { type FlattenedProxy = String; } +impl TelemetrySchema for alloy_primitives::FixedBytes { + type Proxy = String; + type FlattenedProxy = String; +} + +impl TelemetrySchema + for alloy_primitives::Uint +{ + type Proxy = String; + type FlattenedProxy = String; +} + +impl TelemetrySchema + for alloy_primitives::Signed +{ + type Proxy = String; + type FlattenedProxy = String; +} + +impl TelemetrySchema for alloy_primitives::Address { + type Proxy = String; + type FlattenedProxy = String; +} + impl TelemetrySchema for flux_utils::ArrayVec { type Proxy = Vec; type FlattenedProxy = Vec; From bacdc226a6541a17e51a918715a215fc57c23a5d Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Mon, 14 Sep 2026 14:30:19 +0100 Subject: [PATCH 07/10] feat(versioned-types): string proxies for 128-bit integers --- crates/flux-versioned-types/src/schema.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/flux-versioned-types/src/schema.rs b/crates/flux-versioned-types/src/schema.rs index 48a2fab..6869f04 100644 --- a/crates/flux-versioned-types/src/schema.rs +++ b/crates/flux-versioned-types/src/schema.rs @@ -75,6 +75,17 @@ impl TelemetrySchema for uuid::Uuid { type FlattenedProxy = String; } +// Exact decimal strings: the full range does not fit in Arrow numerics. +impl TelemetrySchema for u128 { + type Proxy = String; + type FlattenedProxy = String; +} + +impl TelemetrySchema for i128 { + type Proxy = String; + type FlattenedProxy = String; +} + impl TelemetrySchema for alloy_primitives::FixedBytes { type Proxy = String; type FlattenedProxy = String; From 2742f2bedc17cf7ff7798707bd8897dbdb3d1569 Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Mon, 14 Sep 2026 15:00:02 +0100 Subject: [PATCH 08/10] feat(versioned-types): strip schema attrs from non-final versions --- .../src/evolve/generate.rs | 38 ++++++++++++++----- .../src/evolve_enum/generate.rs | 22 ++++++++--- .../src/shared/generate.rs | 14 +++++-- .../src/shared/mod.rs | 2 +- crates/flux-versioned-types/src/lib.rs | 4 ++ 5 files changed, 61 insertions(+), 19 deletions(-) diff --git a/crates/flux-versioned-types-macros/src/evolve/generate.rs b/crates/flux-versioned-types-macros/src/evolve/generate.rs index 9aa38e3..642e3a4 100644 --- a/crates/flux-versioned-types-macros/src/evolve/generate.rs +++ b/crates/flux-versioned-types-macros/src/evolve/generate.rs @@ -4,7 +4,7 @@ use rustc_hash::FxHashMap; use syn::{Attribute, Ident, Type, Visibility}; use super::parse::{AddField, EvolutionOp, EvolveInput, EvolveStruct, ModifyField, StructField}; -use crate::shared::is_closure; +use crate::shared::{is_closure, without_schema_attrs}; pub(crate) struct FieldInfo { pub attrs: Vec, @@ -73,12 +73,13 @@ pub(crate) fn generate_struct_def( } pub(crate) fn generate_base_struct(input: &EvolveInput) -> (TokenStream2, Vec) { + let is_final = input.evolutions.is_empty(); let fields: Vec<_> = input .base .items .iter() .map(|f| { - let attrs = &f.attrs; + let attrs = emitted_attrs(&f.attrs, is_final); let vis = &f.vis; let name = &f.name; let ty = &f.ty; @@ -86,17 +87,29 @@ pub(crate) fn generate_base_struct(input: &EvolveInput) -> (TokenStream2, Vec Vec { + if is_final { attrs.to_vec() } else { without_schema_attrs(attrs) } +} + fn generate_evolved_struct_fields( kept_fields: &[&FieldInfo], ctx: &EvolutionContext, + is_final: bool, ) -> Vec { kept_fields .iter() @@ -106,13 +119,13 @@ fn generate_evolved_struct_fields( ctx.modify_map.get(&name_str).map_or_else( || { - let attrs = &f.attrs; + let attrs = emitted_attrs(&f.attrs, is_final); let vis = &f.vis; let ty = &f.ty; quote! { #(#attrs)* #vis #name: #ty } }, |modify| { - let attrs = &modify.attrs; + let attrs = emitted_attrs(&modify.attrs, is_final); let vis = &f.vis; let ty = &modify.new_ty; quote! { #(#attrs)* #vis #name: #ty } @@ -120,7 +133,7 @@ fn generate_evolved_struct_fields( ) }) .chain(ctx.add_fields.iter().map(|f| { - let attrs = &f.attrs; + let attrs = emitted_attrs(&f.attrs, is_final); let vis = &f.vis; let name = &f.name; let ty = &f.ty; @@ -222,15 +235,20 @@ pub(crate) fn generate_evolution( default_attrs: &[Attribute], current_fields: &[FieldInfo], prev_name: &Ident, + is_final: bool, ) -> (TokenStream2, Vec) { let ctx = EvolutionContext::from_evolution(evolution); let kept_fields: Vec<_> = current_fields.iter().filter(|f| !ctx.remove_names.contains(&f.name.to_string())).collect(); - let struct_fields = generate_evolved_struct_fields(&kept_fields, &ctx); - let struct_def = - generate_struct_def(&evolution.name, default_attrs, &evolution.attrs, &struct_fields); + let struct_fields = generate_evolved_struct_fields(&kept_fields, &ctx, is_final); + let struct_def = generate_struct_def( + &evolution.name, + default_attrs, + &emitted_attrs(&evolution.attrs, is_final), + &struct_fields, + ); let into_impl = generate_into_impl(prev_name, &evolution.name, &kept_fields, &ctx); let mut output = struct_def; diff --git a/crates/flux-versioned-types-macros/src/evolve_enum/generate.rs b/crates/flux-versioned-types-macros/src/evolve_enum/generate.rs index a989af8..d3e64c8 100644 --- a/crates/flux-versioned-types-macros/src/evolve_enum/generate.rs +++ b/crates/flux-versioned-types-macros/src/evolve_enum/generate.rs @@ -6,18 +6,23 @@ use syn::{Attribute, Expr, Ident, Index}; use super::parse::{ EnumVariant, EvolveEnum, EvolveEnumInput, EvolveEnumOp, ModifyVariant, RemoveVariant, }; -use crate::shared::is_closure; +use crate::shared::{is_closure, without_schema_attrs}; fn generate_enum_def( name: &Ident, default_attrs: &[Attribute], enum_attrs: &[Attribute], variants: &[EnumVariant], + is_final: bool, ) -> TokenStream2 { + let emit_attrs = |attrs: &[Attribute]| { + if is_final { attrs.to_vec() } else { without_schema_attrs(attrs) } + }; + let enum_attrs = emit_attrs(enum_attrs); let variant_tokens: Vec<_> = variants .iter() .map(|v| { - let vattrs = &v.attrs; + let vattrs = emit_attrs(&v.attrs); let vname = &v.name; let disc = v.discriminant.as_ref().map(|d| quote! { = #d }); if v.is_unit() { @@ -27,7 +32,7 @@ fn generate_enum_def( .fields .iter() .map(|f| { - let fattrs = &f.attrs; + let fattrs = emit_attrs(&f.attrs); let ty = &f.ty; quote! { #(#fattrs)* #ty } }) @@ -214,6 +219,7 @@ pub(crate) fn generate_base_enum(input: &EvolveEnumInput) -> (TokenStream2, Vec< &input.default_attrs, &input.base.attrs, &input.base.items, + input.evolutions.is_empty(), ); (output, input.base.items.clone()) @@ -224,6 +230,7 @@ pub(crate) fn generate_evolution( default_attrs: &[Attribute], current_variants: &[EnumVariant], prev_name: &Ident, + is_final: bool, ) -> (TokenStream2, Vec) { let mut add_variants = Vec::new(); let mut remove_map = FxHashMap::default(); @@ -274,8 +281,13 @@ pub(crate) fn generate_evolution( new_variants.push(av.clone()); } - let enum_def = - generate_enum_def(&evolution.name, default_attrs, &evolution.attrs, &new_variants); + let enum_def = generate_enum_def( + &evolution.name, + default_attrs, + &evolution.attrs, + &new_variants, + is_final, + ); let into_impl = generate_into_impl( prev_name, &evolution.name, diff --git a/crates/flux-versioned-types-macros/src/shared/generate.rs b/crates/flux-versioned-types-macros/src/shared/generate.rs index 36147fd..17e1312 100644 --- a/crates/flux-versioned-types-macros/src/shared/generate.rs +++ b/crates/flux-versioned-types-macros/src/shared/generate.rs @@ -45,10 +45,17 @@ impl Named for EvolveBlock { } } +/// Schema-only attributes describe the final, queryable shape, so they are +/// stripped from every older version's expansion: only the version carrying +/// the schema derive may name them. +pub(crate) fn without_schema_attrs(attrs: &[Attribute]) -> Vec { + attrs.iter().filter(|attr| !attr.path().is_ident("telemetry_schema")).cloned().collect() +} + pub(crate) fn generate_evolving( input: &mut EvolveInputGeneric, generate_base: impl FnOnce(&EvolveInputGeneric) -> (TokenStream2, Vec), - generate_step: impl Fn(&E, &[Attribute], &[Item], &Ident) -> (TokenStream2, Vec), + generate_step: impl Fn(&E, &[Attribute], &[Item], &Ident, bool) -> (TokenStream2, Vec), ) -> TokenStream2 { if input.evolutions.is_empty() { input.base.attrs_mut().extend(input.final_attrs.clone()); @@ -84,9 +91,10 @@ pub(crate) fn generate_evolving( let mut output = base_output; let mut prev_name = input.base.name().clone(); - for evolution in &input.evolutions { + for (index, evolution) in input.evolutions.iter().enumerate() { + let is_final = index + 1 == input.evolutions.len(); let (ev_output, new_items) = - generate_step(evolution, &input.default_attrs, ¤t, &prev_name); + generate_step(evolution, &input.default_attrs, ¤t, &prev_name, is_final); output.extend(ev_output); current = new_items; prev_name = evolution.name().clone(); diff --git a/crates/flux-versioned-types-macros/src/shared/mod.rs b/crates/flux-versioned-types-macros/src/shared/mod.rs index 07fe97f..6f6d93f 100644 --- a/crates/flux-versioned-types-macros/src/shared/mod.rs +++ b/crates/flux-versioned-types-macros/src/shared/mod.rs @@ -2,6 +2,6 @@ mod generate; mod parse; pub(crate) use generate::{ - default_enum_attrs, default_struct_attrs, generate_evolving, is_closure, + default_enum_attrs, default_struct_attrs, generate_evolving, is_closure, without_schema_attrs, }; pub(crate) use parse::{BaseBlock, EvolveBlock, EvolveInputGeneric, ParseEvolveOp}; diff --git a/crates/flux-versioned-types/src/lib.rs b/crates/flux-versioned-types/src/lib.rs index ac6a9c2..448a4a6 100644 --- a/crates/flux-versioned-types/src/lib.rs +++ b/crates/flux-versioned-types/src/lib.rs @@ -92,6 +92,10 @@ macro_rules! __versioned_enum_inner { /// Like [`versioned_struct`], but the latest version also derives /// [`TelemetrySchema`] so it is queryable. With `persist = "dir"` the type /// also gets a [`VersionedPersistable`] home under that directory. +/// +/// Schema-only field attributes (`#[telemetry_schema(..)]`) may be written +/// on any version; they describe the final shape and are stripped from older +/// versions' expansions. #[macro_export] macro_rules! versioned_telemetry { ($name:ident, persist = $dir:expr => $($tokens:tt)*) => { From ddbd44144521b1df2fc07997af0084b8aa935c48 Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Mon, 14 Sep 2026 15:11:21 +0100 Subject: [PATCH 09/10] fix(versioned-types): keep whole-type proxy on non-final versions --- .../src/evolve/generate.rs | 18 +++++------------- .../src/evolve_enum/generate.rs | 1 - .../src/shared/generate.rs | 8 +++++--- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/crates/flux-versioned-types-macros/src/evolve/generate.rs b/crates/flux-versioned-types-macros/src/evolve/generate.rs index 642e3a4..7c069ed 100644 --- a/crates/flux-versioned-types-macros/src/evolve/generate.rs +++ b/crates/flux-versioned-types-macros/src/evolve/generate.rs @@ -87,12 +87,8 @@ pub(crate) fn generate_base_struct(input: &EvolveInput) -> (TokenStream2, Vec (TokenStream2, Vec Vec { if is_final { attrs.to_vec() } else { without_schema_attrs(attrs) } } @@ -243,12 +239,8 @@ pub(crate) fn generate_evolution( current_fields.iter().filter(|f| !ctx.remove_names.contains(&f.name.to_string())).collect(); let struct_fields = generate_evolved_struct_fields(&kept_fields, &ctx, is_final); - let struct_def = generate_struct_def( - &evolution.name, - default_attrs, - &emitted_attrs(&evolution.attrs, is_final), - &struct_fields, - ); + let struct_def = + generate_struct_def(&evolution.name, default_attrs, &evolution.attrs, &struct_fields); let into_impl = generate_into_impl(prev_name, &evolution.name, &kept_fields, &ctx); let mut output = struct_def; diff --git a/crates/flux-versioned-types-macros/src/evolve_enum/generate.rs b/crates/flux-versioned-types-macros/src/evolve_enum/generate.rs index d3e64c8..7f44256 100644 --- a/crates/flux-versioned-types-macros/src/evolve_enum/generate.rs +++ b/crates/flux-versioned-types-macros/src/evolve_enum/generate.rs @@ -18,7 +18,6 @@ fn generate_enum_def( let emit_attrs = |attrs: &[Attribute]| { if is_final { attrs.to_vec() } else { without_schema_attrs(attrs) } }; - let enum_attrs = emit_attrs(enum_attrs); let variant_tokens: Vec<_> = variants .iter() .map(|v| { diff --git a/crates/flux-versioned-types-macros/src/shared/generate.rs b/crates/flux-versioned-types-macros/src/shared/generate.rs index 17e1312..2c0f058 100644 --- a/crates/flux-versioned-types-macros/src/shared/generate.rs +++ b/crates/flux-versioned-types-macros/src/shared/generate.rs @@ -45,9 +45,11 @@ impl Named for EvolveBlock { } } -/// Schema-only attributes describe the final, queryable shape, so they are -/// stripped from every older version's expansion: only the version carrying -/// the schema derive may name them. +/// Schema-only attributes on fields and variants describe the final, +/// queryable shape, so they are stripped from every older version's +/// expansion: only the version carrying the schema derive may name them. +/// Version-level attributes are left alone, so an explicit whole-type proxy +/// keeps working on any version. pub(crate) fn without_schema_attrs(attrs: &[Attribute]) -> Vec { attrs.iter().filter(|attr| !attr.path().is_ident("telemetry_schema")).cloned().collect() } From 1b610cc91572aee7028501679326cb463bbbe159 Mon Sep 17 00:00:00 2001 From: Louis Ponet Date: Mon, 14 Sep 2026 16:17:53 +0100 Subject: [PATCH 10/10] feat(flux-utils): TypeHash for ArrayStr --- crates/flux-utils/src/arrayvec.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/flux-utils/src/arrayvec.rs b/crates/flux-utils/src/arrayvec.rs index 5993664..92b478b 100644 --- a/crates/flux-utils/src/arrayvec.rs +++ b/crates/flux-utils/src/arrayvec.rs @@ -494,6 +494,16 @@ impl TypeHash for ArrayVec { }; } +impl TypeHash for ArrayStr { + const TYPE_HASH: u64 = { + let mut h = 0xcbf2_9ce4_8422_2325u64; + h = fnv1a64_str(h, "ArrayStr"); + h = hash_u64(h, N as u64); + h = hash_layout_of::(h); + h + }; +} + mod serde_impl { use core::fmt;