Skip to content

state_exec: data-channel bytes as a raw std::string (coherent with ?data + the HTTP cache) - #469

Merged
transfix merged 1 commit into
masterfrom
feat/state-exec-bytes-data-bridge
Sep 29, 2026
Merged

transfix merged 1 commit into
masterfrom
feat/state-exec-bytes-data-bridge

Conversation

@transfix

Copy link
Copy Markdown
Owner

Precursor to the §13.9 HTTP cache (PR2). A state_exec correctness fix: make the DSL bytes type and the ?data URI channel agree on ONE on-node representation, so a byte blob is coherent whether it's written by a DSL program, the state://…?data resolver, or the (upcoming) HTTP cache.

The problem

The per-node data() channel is a boost::any, and two conventions were in play that didn't interoperate:

  • state_exec (state-data-get/-set) stored a value_t.
  • the ?data URI channel (uri_state.cpp) and the planned §13.9 HTTP cache store a raw std::string.

So a byte blob written by one was invisible-as-bytes to the other: a raw 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. Yet types.h is explicit that HTTP octet-stream bodies belong in the bytes track ("opaque OCTETS … backed by std::string purely as a byte container").

The fix

Make the raw std::string the canonical on-node representation of the DSL bytes type (intrinsics.cpp):

  • state-data-get: a raw std::string payload → 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 → stored as boost::any(std::string) (the 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.

Why it's safe

Nothing else any_cast<value_t>s off data() (only state-data-get, intrinsics.cpp:239), and data() 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-set bytes has no other consumer. Raw std::string on data() already existed via ?data/state_store, so this reuses an established representation rather than introducing one.

Coherence, both directions (tested)

  • ?data/cache → state_exec: a raw std::string blob on data() reads as bytes (StateDataGetReadsRawStringBlobAsBytes).
  • state_exec → ?data: a DSL bytes value lands as a raw std::string on the node — exactly what uri_state.cpp's ?data any_cast<std::string> reads — verified by direct node inspection (StateDataSetBytesStoredAsRawStringForDataChannel); the existing AriadneStateUri.StoreAndResolveDataChannel covers the ?data round-trip of that representation.
  • Byte transparency through an embedded NUL; string vs bytes tracks stay distinct (StateDataStringAndBytesTracksAreDistinct).

Full state_exec_intrinsics_test (154) + state_exec_integration_test (85) green locally; the existing text-string/list state-data round-trip tests are unchanged.

…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant