From 8372e696b8f22cc12e535866412a5be14cad6203 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 21 Aug 2026 01:32:38 +0300 Subject: [PATCH 1/6] feat(vendor): add tinymemory submodule and tinymemory-bus dependency Add the tinymemory git submodule and register its tinymemory-bus crate as a path dependency in Cargo.toml. This provides the wire vocabulary for the TinyMemory TinyBus module, allowing the host to link only the payload types and member names without pulling in the engine, storage, or async runtime. Auto-committed-on: dragonfly Co-authored-by: Medulla --- .gitmodules | 3 +++ Cargo.toml | 22 ++++++++++++++++++++++ vendor/tinymemory | 1 + 3 files changed, 26 insertions(+) create mode 160000 vendor/tinymemory diff --git a/.gitmodules b/.gitmodules index 9281cb0b03..44c99fb4a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -23,3 +23,6 @@ path = vendor/tinybus url = https://github.com/tinyhumansai/tinybus.git branch = main +[submodule "vendor/tinymemory"] + path = vendor/tinymemory + url = https://github.com/tinyhumansai/tinymemory diff --git a/Cargo.toml b/Cargo.toml index 7d66a5357b..4816e3e506 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -154,6 +154,28 @@ tinycortex = { version = "0.1", features = [ # this with the `path = "api"` dependency the engine crate already declares. tinycortex-api = { path = "vendor/tinycortex/api" } tinychannels = { version = "0.1", features = ["relay-websocket"] } +# tinymemory-bus — the wire vocabulary for the TinyMemory TinyBus module. +# +# TinyMemory ships as a loadable module (a `cdylib`), so the host can load the +# binary but cannot `use` anything out of it: the payload types and the member +# names have to arrive as an ordinary library. This is that library, and it is +# the *only* piece of TinyMemory the host links — no engine, no storage, no +# traits, no async runtime. Seven pure-Rust dependencies. +# +# Vendored as a git submodule beside the other tiny* crates so module work can +# change the contract in-tree, test it against OpenHuman immediately, and PR the +# diff upstream from the submodule. After cloning: +# `git submodule update --init vendor/tinymemory` (worktrees included). +# +# No `[patch."https://github.com/tinyhumansai/tinymemory"]` entry yet, and that +# is deliberate rather than forgotten. The vendored TinyCortex pin still defines +# the memory contract itself in `tinycortex-api`; it is only *newer* TinyCortex +# revisions that re-export `tinymemory-api` and pull it in by git. Adding the +# patch now would make cargo warn about a patch that matches nothing in the +# graph. Add it in the same change that bumps the TinyCortex pin — without it, +# the git copy and this path copy resolve as two distinct crates and +# `MemoryEntry` from one is not `MemoryEntry` from the other. +tinymemory-bus = { path = "vendor/tinymemory/crates/tinymemory-bus" } # tinybus — the message bus. Owns what `src/core/event_bus/` used to: the typed # pub/sub surface (`EventBus`, `EventHandler`, `SubscriptionHandle`), the # in-process zero-serialization request registry (`NativeRegistry`), and peer diff --git a/vendor/tinymemory b/vendor/tinymemory new file mode 160000 index 0000000000..8612196071 --- /dev/null +++ b/vendor/tinymemory @@ -0,0 +1 @@ +Subproject commit 86121960719687e9d0033b208ae8df71bf75f785 From 378cffd1413fb9cf048411c2652a8469feb8f412 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 21 Aug 2026 01:33:26 +0300 Subject: [PATCH 2/6] fix(driver): restore missing module file The module file for the memory driver was missing from the repository, causing compilation failures. This change restores the file to its expected state, ensuring the driver module can be properly built and used. Auto-committed-on: dragonfly Co-authored-by: Medulla --- src/openhuman/memory/driver/module/mod.rs | 266 ++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 src/openhuman/memory/driver/module/mod.rs diff --git a/src/openhuman/memory/driver/module/mod.rs b/src/openhuman/memory/driver/module/mod.rs new file mode 100644 index 0000000000..00772458c2 --- /dev/null +++ b/src/openhuman/memory/driver/module/mod.rs @@ -0,0 +1,266 @@ +//! Client seam for the TinyMemory driver when it is loaded as a `TinyBus` +//! module rather than compiled in. +//! +//! TinyMemory ships as a `cdylib` exporting one object, +//! [`OBJECT_PATH`](tinymemory_bus::names::OBJECT_PATH), with 89 members on it. +//! The host loads that binary and calls into it; it cannot `use` anything out +//! of it, so the vocabulary — the member names, the payload types, and the +//! error-name table — arrives from the `tinymemory-bus` library instead. +//! +//! This module is the piece in between: it turns a member name plus typed +//! arguments into a `TinyBus` call, and turns the reply — or the failure — back +//! into something the memory layer can act on. +//! +//! # Why the error mapping is the interesting part +//! +//! A `TinyBus` failure is a name and a prose message. The name is the contract: +//! [`tinymemory_bus::wire`] holds the table mapping it back to a +//! [`MemoryError`], and the *module uses the same table in the other +//! direction*. That is what keeps the two ends from drifting into disagreeing +//! about what a name means — the case that matters being `PathEscape`, which +//! reports a sandbox escape and must not be silently reclassified as a +//! caller's malformed argument. +//! +//! Everything that is not a `MethodFailed` never reached the driver at all: a +//! timeout, an unowned name, a transport fault. Those are mapped to the +//! [`MemoryError`] variants that say so rather than being flattened into +//! `Other`, because a host retries an [`Unreachable`](MemoryError::Unreachable) +//! and does not retry an [`Invalid`](MemoryError::Invalid). +//! +//! # Scope +//! +//! [`MemoryModule::call`] is public and reaches **every** member: pass a name +//! from [`tinymemory_bus::names::methods`] and the positional arguments as a +//! tuple. The typed wrappers below cover the driver-level members and the +//! mandatory core family, which is what a host needs to bind a driver and +//! prove it answers. The remaining families are one `call` each and are added +//! as the driver seam grows into them. + +use serde::de::DeserializeOwned; +use serde::Serialize; +use tinybus::{Connection, Error as BusError, Proxy}; +use tinymemory_bus::error::MemoryError; +use tinymemory_bus::names::{methods, BUS_NAME, OBJECT_PATH}; +use tinymemory_bus::types::{MemoryCategory, MemoryEntry, MemoryTaint, NamespaceSummary}; +use tinymemory_bus::{capabilities::Capabilities, health::MemoryHealth, wire}; + +/// A bound TinyMemory module object. +/// +/// Addresses one store. The root object is the one at +/// [`OBJECT_PATH`](tinymemory_bus::names::OBJECT_PATH); `OpenStore` answers +/// with the path of a *sibling* store under the same workspace, exporting this +/// identical interface, which [`MemoryModule::at`] binds. +#[derive(Debug, Clone)] +pub struct MemoryModule { + proxy: Proxy, +} + +impl MemoryModule { + /// Bind the module's root object on `connection`. + /// + /// # Errors + /// + /// [`MemoryError::Invalid`] if the well-known name or object path is + /// rejected by `TinyBus` — unreachable in practice, since both are + /// constants from the contract, but the constructor is fallible rather than + /// panicking on a value it did not choose. + pub fn new(connection: &Connection) -> Result { + Self::at(connection, OBJECT_PATH) + } + + /// Bind a specific object path on `connection` — an `OpenStore` result. + /// + /// # Errors + /// + /// [`MemoryError::Invalid`] if `object_path` is not a valid `TinyBus` + /// object path. Unlike [`new`](Self::new) this is reachable: the path comes + /// back over the wire. + pub fn at(connection: &Connection, object_path: &str) -> Result { + let proxy = connection + .proxy(BUS_NAME, object_path, BUS_NAME) + .map_err(|error| MemoryError::Invalid(error.to_string()))?; + Ok(Self { proxy }) + } + + /// Call any member by name, with `args` as its positional arguments. + /// + /// Pass a tuple for several arguments, a bare value for one, and `()` for + /// none — the encoding `#[tinybus::interface]` decodes on the far side. + /// Names come from [`tinymemory_bus::names::methods`]; spelling one by hand + /// is what that module exists to avoid. + /// + /// # Errors + /// + /// The driver's own [`MemoryError`], recovered through + /// [`tinymemory_bus::wire::from_wire`], when the call reached the module and + /// failed there. Otherwise the transport failure, mapped by + /// [`map_bus_error`]. + pub async fn call( + &self, + member: &str, + args: impl Serialize + Send, + ) -> Result { + self.proxy.call(member, args).await.map_err(map_bus_error) + } + + /// The driver id this module reports. + /// + /// # Errors + /// + /// See [`call`](Self::call). + pub async fn driver_id(&self) -> Result { + self.call(methods::DRIVER_ID, ()).await + } + + /// The capability families this driver advertises. + /// + /// # Errors + /// + /// See [`call`](Self::call). + pub async fn capabilities(&self) -> Result { + self.call(methods::CAPABILITIES, ()).await + } + + /// The driver's liveness. + /// + /// # Errors + /// + /// See [`call`](Self::call). + pub async fn health(&self) -> Result { + self.call(methods::HEALTH, ()).await + } + + /// Ask the module to shut its store down. + /// + /// # Errors + /// + /// See [`call`](Self::call). + pub async fn shutdown(&self) -> Result<(), MemoryError> { + self.call(methods::SHUTDOWN, ()).await + } + + /// Open a sibling store under `memory_subdir`, returning its object path. + /// + /// Bind the result with [`at`](Self::at). Asking twice for the same subdir + /// returns the same path rather than opening the database twice. + /// + /// # Errors + /// + /// See [`call`](Self::call). + pub async fn open_store(&self, memory_subdir: &str) -> Result { + self.call(methods::OPEN_STORE, (memory_subdir,)).await + } + + /// Upsert the entry at `(namespace, key)`. + /// + /// `taint` is required rather than defaulted, mirroring the contract: a + /// caller that could omit provenance would be able to launder + /// externally-sourced content into internal-trust content. + /// + /// # Errors + /// + /// See [`call`](Self::call). + pub async fn store( + &self, + namespace: &str, + key: &str, + content: &str, + category: MemoryCategory, + session_id: Option<&str>, + taint: MemoryTaint, + ) -> Result<(), MemoryError> { + self.call( + methods::STORE, + (namespace, key, content, category, session_id, taint), + ) + .await + } + + /// Fetch the entry at an exact `(namespace, key)`. + /// + /// A miss is `Ok(None)`, not an error — the contract's rule, preserved + /// across the wire. + /// + /// # Errors + /// + /// See [`call`](Self::call). + pub async fn get( + &self, + namespace: &str, + key: &str, + ) -> Result, MemoryError> { + self.call(methods::GET, (namespace, key)).await + } + + /// Delete the entry at `(namespace, key)`, reporting whether it existed. + /// + /// # Errors + /// + /// See [`call`](Self::call). + pub async fn forget(&self, namespace: &str, key: &str) -> Result { + self.call(methods::FORGET, (namespace, key)).await + } + + /// List entries, narrowing by namespace, category and session. + /// + /// # Errors + /// + /// See [`call`](Self::call) — and note that this member has no limit and no + /// cursor, so the module refuses an oversized response with + /// [`MemoryError::BudgetExceeded`] rather than truncating it. A short list + /// would be indistinguishable from a complete one. + pub async fn list( + &self, + namespace: Option<&str>, + category: Option, + session_id: Option<&str>, + ) -> Result, MemoryError> { + self.call(methods::LIST, (namespace, category, session_id)) + .await + } + + /// Enumerate namespaces with their aggregate counts. + /// + /// # Errors + /// + /// See [`call`](Self::call). + pub async fn namespaces(&self) -> Result, MemoryError> { + self.call(methods::NAMESPACES, ()).await + } +} + +/// Turn a `TinyBus` failure into the memory error it stands for. +/// +/// [`BusError::MethodFailed`] is the one that reached the driver: its name is +/// the contract, and [`wire::from_wire`] is the same table the module mapped +/// *out* through, so the variant survives the round trip. A name this build +/// does not recognise becomes [`MemoryError::Other`] and never +/// [`MemoryError::Invalid`] — a module newer than the host may name an error +/// this table has no variant for, and telling a caller its input was wrong when +/// it was not sends it into a rewrite loop over something already correct. +/// +/// Every other variant never reached the driver, so it is a transport fact and +/// is reported as one. +pub(crate) fn map_bus_error(error: BusError) -> MemoryError { + match error { + BusError::MethodFailed { name, message } => wire::from_wire(&name, &message), + BusError::Timeout { .. } => MemoryError::Timeout(error.to_string()), + // The module is not running, or has not claimed its name yet. Both are + // "try again once it is up", which is what `Unreachable` tells a caller. + BusError::NameHasNoOwner(_) | BusError::Transport(_) | BusError::Io(_) => { + MemoryError::Unreachable(error.to_string()) + } + // A member or object this build believes in and the module does not. + // That is a contract mismatch, not a caller mistake, so it is not + // `Invalid`: the argument was fine, the peer is the wrong version. + BusError::UnknownMethod { .. } + | BusError::UnknownObject { .. } + | BusError::UnknownInterface { .. } + | BusError::IncompatibleVersion { .. } => MemoryError::Backend(error.to_string()), + other => MemoryError::Other(anyhow::anyhow!(other.to_string())), + } +} + +#[cfg(test)] +#[path = "mod_tests.rs"] +mod tests; From e7ca70e86db208ffb06897924ad249e1a87d6b7b Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 21 Aug 2026 01:34:01 +0300 Subject: [PATCH 3/6] docs(driver): document the new module driver alongside embedded Add a doc comment for the newly exposed `module` submodule, explaining that it communicates with TinyMemory over TinyBus rather than through the in-process tinycortex engine. The note clarifies that the two drivers are not yet interchangeable because they implement different API contracts, and that convergence will happen once TinyCortex re-exports the tinymemory-api. Auto-committed-on: dragonfly Co-authored-by: Medulla --- src/openhuman/memory/driver/mod.rs | 15 ++- .../memory/driver/module/mod_tests.rs | 104 ++++++++++++++++++ 2 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 src/openhuman/memory/driver/module/mod_tests.rs diff --git a/src/openhuman/memory/driver/mod.rs b/src/openhuman/memory/driver/mod.rs index d91ca01a80..0b5f67f28f 100644 --- a/src/openhuman/memory/driver/mod.rs +++ b/src/openhuman/memory/driver/mod.rs @@ -1,8 +1,16 @@ //! Memory-driver implementations of the [`tinycortex_api`] contract. //! -//! One subdirectory per driver. Today there is exactly one — [`embedded`], -//! which wraps the in-process tinycortex engine — plus the reference -//! `NullMemoryProvider` that ships inside the contract crate itself. +//! One subdirectory per driver. [`embedded`] wraps the in-process tinycortex +//! engine, and [`module`] talks to TinyMemory when it is loaded as a `TinyBus` +//! module instead of compiled in — plus the reference `NullMemoryProvider` that +//! ships inside the contract crate itself. +//! +//! The two are not interchangeable yet: [`embedded`] implements the +//! `tinycortex_api` contract this build pins, while [`module`] speaks the +//! `tinymemory-bus` vocabulary the loadable module was built against. They +//! converge when the TinyCortex pin moves to a revision that re-exports +//! `tinymemory-api` — until then [`module`] is the client seam, not a +//! `MemoryProvider` impl. //! //! Drivers live *under* `memory/` rather than in a sibling top-level directory //! so the "one directory equals one feature gate" family rule holds: a memory @@ -10,3 +18,4 @@ //! would be meaningless. pub mod embedded; +pub mod module; diff --git a/src/openhuman/memory/driver/module/mod_tests.rs b/src/openhuman/memory/driver/module/mod_tests.rs new file mode 100644 index 0000000000..4040746f50 --- /dev/null +++ b/src/openhuman/memory/driver/module/mod_tests.rs @@ -0,0 +1,104 @@ +//! Tests for the TinyMemory module client seam. +//! +//! The behaviour worth pinning here is the error mapping. A `TinyBus` failure +//! arrives as a name plus prose, and which [`MemoryError`] it becomes decides +//! whether the caller retries, rewrites its input, or gives up — so a +//! misclassification is not cosmetic. The mapping is also the one piece of this +//! module that can be exercised without a live bus. + +use tinybus::Error as BusError; +use tinymemory_bus::error::MemoryError; +use tinymemory_bus::wire; + +use super::map_bus_error; + +/// A `MethodFailed` as the module emits one. +fn failed(name: &str) -> BusError { + BusError::MethodFailed { + name: name.to_string(), + message: "prose for a human".to_string(), + } +} + +#[test] +fn a_driver_error_survives_the_round_trip_under_its_own_name() { + // The module maps out through `wire::wire_name`; this maps back through + // `wire::from_wire`. Same table, so the variant has to come back intact. + for (name, matches) in [ + (wire::NOT_FOUND, matches!(map_bus_error(failed(wire::NOT_FOUND)), MemoryError::NotFound(_))), + (wire::INVALID, matches!(map_bus_error(failed(wire::INVALID)), MemoryError::Invalid(_))), + ( + wire::BUDGET_EXCEEDED, + matches!(map_bus_error(failed(wire::BUDGET_EXCEEDED)), MemoryError::BudgetExceeded(_)), + ), + ( + wire::UNAUTHORIZED, + matches!(map_bus_error(failed(wire::UNAUTHORIZED)), MemoryError::Unauthorized(_)), + ), + ] { + assert!(matches, "{name} did not map back to its own variant"); + } +} + +#[test] +fn a_path_escape_is_not_flattened_into_an_invalid() { + // The one that matters: `PathEscape` reports a symlink or traversal that + // left the workspace sandbox. Reclassifying it as a malformed argument + // would turn a security-relevant refusal into a caller mistake. + let mapped = map_bus_error(failed(wire::PATH_ESCAPE)); + assert!( + matches!(mapped, MemoryError::PathEscape(_)), + "expected PathEscape, got {mapped:?}" + ); +} + +#[test] +fn an_unrecognised_error_name_is_opaque_rather_than_a_caller_mistake() { + // A module built from a newer contract may name an error this build has no + // variant for. Answering `Invalid` would tell the caller its input was + // wrong when it was not. + let mapped = map_bus_error(failed("ai.tinyhumans.tinymemory.Error.FromTheFuture")); + assert!( + matches!(mapped, MemoryError::Other(_)), + "expected Other, got {mapped:?}" + ); +} + +#[test] +fn a_transport_failure_is_reported_as_unreachable() { + // Never reached the driver, so it is not the driver's error. `Unreachable` + // is the variant a caller retries on. + let mapped = map_bus_error(BusError::Transport("socket closed".to_string())); + assert!( + matches!(mapped, MemoryError::Unreachable(_)), + "expected Unreachable, got {mapped:?}" + ); +} + +#[test] +fn an_unknown_member_is_a_backend_mismatch_not_an_invalid_argument() { + // The host believes in a member the module does not serve: a version skew. + // The arguments were fine, so `Invalid` would point at the wrong thing. + let mapped = map_bus_error(BusError::UnknownMethod { + interface: tinybus::name::InterfaceName::new(tinymemory_bus::names::BUS_NAME) + .expect("the contract's own interface name parses"), + member: tinybus::name::MemberName::new("FromTheFuture") + .expect("a PascalCase member name parses"), + }); + assert!( + matches!(mapped, MemoryError::Backend(_)), + "expected Backend, got {mapped:?}" + ); +} + +#[test] +fn the_message_of_a_mapped_error_carries_no_payload() { + // Neither end may put a namespace key, an entry's content or a recall query + // into an error string. This asserts the seam adds nothing of its own — the + // message is exactly the prose the module sent. + let mapped = map_bus_error(failed(wire::NOT_FOUND)); + assert!( + mapped.to_string().contains("prose for a human"), + "the module's message should survive: {mapped}" + ); +} From 2e52e896c6a5bc94e337dde8535dad02f076b820 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 21 Aug 2026 01:37:59 +0300 Subject: [PATCH 4/6] chore(deps): add tinymemory-bus dependency The Cargo.lock file is updated to include the new tinymemory-bus crate at version 0.1.0, which is now a dependency of the project. This change ensures the lock file reflects the addition of this crate and its dependencies to the workspace. Auto-committed-on: dragonfly Co-authored-by: Medulla --- Cargo.lock | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index c25e84fa65..25de77e6d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4727,6 +4727,7 @@ dependencies = [ "tinyflows", "tinyhumans-sdk", "tinyjuice", + "tinymemory-bus", "tinyplace", "tokio", "tokio-stream", @@ -7382,6 +7383,19 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "tinymemory-bus" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "serde", + "serde_json", + "sha2 0.11.0", + "thiserror 2.0.18", + "uuid 1.23.1", +] + [[package]] name = "tinyplace" version = "2.0.4" From 974a5a9a897cb1bca86ac74b94c549579cbc2457 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 21 Aug 2026 01:44:48 +0300 Subject: [PATCH 5/6] chore(driver): reformat test assertions for readability Reformatted the inline `matches!` macro calls in the driver error round-trip test to use multi-line layout, improving code readability without changing any test logic or behavior. Auto-committed-on: dragonfly Co-authored-by: Medulla --- .../memory/driver/module/mod_tests.rs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/openhuman/memory/driver/module/mod_tests.rs b/src/openhuman/memory/driver/module/mod_tests.rs index 4040746f50..afc66fd1f5 100644 --- a/src/openhuman/memory/driver/module/mod_tests.rs +++ b/src/openhuman/memory/driver/module/mod_tests.rs @@ -25,15 +25,33 @@ fn a_driver_error_survives_the_round_trip_under_its_own_name() { // The module maps out through `wire::wire_name`; this maps back through // `wire::from_wire`. Same table, so the variant has to come back intact. for (name, matches) in [ - (wire::NOT_FOUND, matches!(map_bus_error(failed(wire::NOT_FOUND)), MemoryError::NotFound(_))), - (wire::INVALID, matches!(map_bus_error(failed(wire::INVALID)), MemoryError::Invalid(_))), + ( + wire::NOT_FOUND, + matches!( + map_bus_error(failed(wire::NOT_FOUND)), + MemoryError::NotFound(_) + ), + ), + ( + wire::INVALID, + matches!( + map_bus_error(failed(wire::INVALID)), + MemoryError::Invalid(_) + ), + ), ( wire::BUDGET_EXCEEDED, - matches!(map_bus_error(failed(wire::BUDGET_EXCEEDED)), MemoryError::BudgetExceeded(_)), + matches!( + map_bus_error(failed(wire::BUDGET_EXCEEDED)), + MemoryError::BudgetExceeded(_) + ), ), ( wire::UNAUTHORIZED, - matches!(map_bus_error(failed(wire::UNAUTHORIZED)), MemoryError::Unauthorized(_)), + matches!( + map_bus_error(failed(wire::UNAUTHORIZED)), + MemoryError::Unauthorized(_) + ), ), ] { assert!(matches, "{name} did not map back to its own variant"); From 2c374fae2a57fbff61dbd8ed5ca5bca4f9aa5ac6 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Fri, 21 Aug 2026 02:03:02 +0300 Subject: [PATCH 6/6] chore(tinymemory): pin submodule to v1.1.0 release tag Updated the tinymemory submodule to the v1.1.0 release commit and documented in Cargo.toml that the submodule is pinned to this release tag rather than a main branch commit. This ensures the vendored crate matches the same version used to publish the per-platform module archives and their checksum.toml, guaranteeing that the library the host links against is the one those archives were built from. Auto-committed-on: dragonfly Co-authored-by: Medulla --- Cargo.toml | 6 +++++- vendor/tinymemory | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4816e3e506..37c7a0604a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -164,7 +164,11 @@ tinychannels = { version = "0.1", features = ["relay-websocket"] } # # Vendored as a git submodule beside the other tiny* crates so module work can # change the contract in-tree, test it against OpenHuman immediately, and PR the -# diff upstream from the submodule. After cloning: +# diff upstream from the submodule. Pinned at the `v1.1.0` release tag — the +# release that introduced this crate — rather than at a main commit, because a +# TinyMemory release is also what publishes the per-platform module archives and +# their `checksum.toml`, and the library the host links has to be the one those +# archives were built from. After cloning: # `git submodule update --init vendor/tinymemory` (worktrees included). # # No `[patch."https://github.com/tinyhumansai/tinymemory"]` entry yet, and that diff --git a/vendor/tinymemory b/vendor/tinymemory index 8612196071..1d501fb535 160000 --- a/vendor/tinymemory +++ b/vendor/tinymemory @@ -1 +1 @@ -Subproject commit 86121960719687e9d0033b208ae8df71bf75f785 +Subproject commit 1d501fb5350e1ada367bb9bb6bb862a4ab2a9379