state_exec: data-channel bytes as a raw std::string (coherent with ?data + the HTTP cache) - #469
Merged
Merged
Conversation
…tring (coherent with ?data) The per-node data() channel (a boost::any) had two non-interoperating conventions: state-data-get/-set stored a value_t, while the ?data URI channel (uri_state.cpp) and the planned §13.9 HTTP cache store a raw std::string. A byte blob written by one was invisible-as-bytes to the other -- a std::string parked by ?data / the cache came back from state-data-get as an opaque data_object, and a `bytes` value set from the DSL (stored as a value_t) was not byte-serializable via state://...?data. Make the raw std::string THE on-node representation of the DSL `bytes` type (types.h: `bytes` is opaque octets backed by std::string, the sanctioned home for HTTP octet-stream bodies), so one byte blob is coherent across the resolver and state_exec: - state-data-get: a raw std::string payload returns make_bytes(...) (the `bytes` track) instead of an opaque data_object. A value_t still round-trips via the value_t path; a foreign C++ type still comes back as a data_object. - state-data-set: a `bytes` value is stored as boost::any(std::string) -- the same representation ?data reads -- instead of boost::any(value_t). Every other value_t (text strings, lists, dicts, scalars) is stored as-is and round-trips unchanged. The text `string` track and the `bytes` track stay distinct. Nothing else any_casts value_t off data() (only state-data-get), and data() replication (set_data) is a documented no-op, so the on-node representation change is safe. Precursor to the §13.9 HTTP cache: the cache parks response bodies as byte blobs on data(), and this makes those bodies show up as `bytes` in state_exec and serve byte-exact via state://...?data. Tests (state_exec_intrinsics_test): a raw std::string blob reads as bytes; a DSL bytes value lands as a raw std::string (readable via ?data) and round-trips byte-exact through an embedded NUL; the string and bytes tracks stay distinct. Full intrinsics (154) + integration (85) suites green.
This was referenced Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Precursor to the §13.9 HTTP cache (PR2). A
state_execcorrectness fix: make the DSLbytestype and the?dataURI channel agree on ONE on-node representation, so a byte blob is coherent whether it's written by a DSL program, thestate://…?dataresolver, or the (upcoming) HTTP cache.The problem
The per-node
data()channel is aboost::any, and two conventions were in play that didn't interoperate:state-data-get/-set) stored avalue_t.?dataURI channel (uri_state.cpp) and the planned §13.9 HTTP cache store a rawstd::string.So a byte blob written by one was invisible-as-bytes to the other: a raw
std::stringparked by?data/the cache came back fromstate-data-getas an opaquedata_object, and abytesvalue set from the DSL (stored as avalue_t) was not byte-serializable viastate://…?data. Yettypes.his explicit that HTTP octet-stream bodies belong in thebytestrack ("opaque OCTETS … backed by std::string purely as a byte container").The fix
Make the raw
std::stringthe canonical on-node representation of the DSLbytestype (intrinsics.cpp):state-data-get: a rawstd::stringpayload →make_bytes(...)(thebytestrack), instead of an opaquedata_object. Avalue_tstill round-trips via thevalue_tpath; a foreign C++ type still comes back as adata_object.state-data-set: abytesvalue → stored asboost::any(std::string)(the representation?datareads), instead ofboost::any(value_t). Every othervalue_t(text strings, lists, dicts, scalars) is stored as-is and round-trips unchanged.The text
stringtrack and thebytestrack stay distinct.Why it's safe
Nothing else
any_cast<value_t>s offdata()(onlystate-data-get, intrinsics.cpp:239), anddata()replication (state_mutation_op::set_data) is a documented no-op (state_sync_adapter.cpp:293), so changing the on-node representation of a DSL-setbyteshas no other consumer. Rawstd::stringondata()already existed via?data/state_store, so this reuses an established representation rather than introducing one.Coherence, both directions (tested)
std::stringblob ondata()reads asbytes(StateDataGetReadsRawStringBlobAsBytes).bytesvalue lands as a rawstd::stringon the node — exactly whaturi_state.cpp's?dataany_cast<std::string>reads — verified by direct node inspection (StateDataSetBytesStoredAsRawStringForDataChannel); the existingAriadneStateUri.StoreAndResolveDataChannelcovers the?dataround-trip of that representation.stringvsbytestracks stay distinct (StateDataStringAndBytesTracksAreDistinct).Full
state_exec_intrinsics_test(154) +state_exec_integration_test(85) green locally; the existing text-string/liststate-dataround-trip tests are unchanged.