Skip to repository content
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T00:56:18.715Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitRevision diff
8a07c5bf → c886698bdiff --git a/crates/omega_effectd/src/sarah_conversation.rs b/crates/omega_effectd/src/sarah_conversation.rs
index 38253b8534..c4c1951d34 100644
--- a/crates/omega_effectd/src/sarah_conversation.rs
+++ b/crates/omega_effectd/src/sarah_conversation.rs
@@ -6254,11 +6254,75 @@ mod tests {
62546254 /// Publish one event through a real adapter, meeting the NIP-42 challenge
62556255 /// lazily exactly as `publish_with_auth` does.
62566256 #[cfg(test)]
6257+ /// Sarah's half of the conversation, shaped the way the host actually reads it.
6258+ ///
6259+ /// The host subscribes to gift wraps addressed to its own custody key and
6260+ /// unwraps the kind-14 rumor inside into a `message` record — the identical
6261+ /// path the owner's own sends take. A bare kind 14 published straight to
6262+ /// the relay is never requested by any filter and never arrives, and a
6263+ /// kind 44300 is a `Turn`, which must carry NIP-44 ciphertext of JSON and
6264+ /// is not a conversation message at all.
6265+ ///
6266+ /// `conversation_tags` is the production tag builder, so the rumor names
6267+ /// exactly the owner and Sarah and satisfies `require_conversation_recipients`
6268+ /// rather than approximating it here.
6269+ fn sarah_conversation_wrap(
6270+ sarah_keys: &Keys,
6271+ owner_public_key: &PublicKey,
6272+ owner_public_key_hex: &str,
6273+ conversation_ref: &str,
6274+ text: &str,
6275+ ) -> nostr::Event {
6276+ let tags = conversation_tags(
6277+ conversation_ref,
6278+ owner_public_key_hex,
6279+ &sarah_keys.public_key().to_hex(),
6280+ )
6281+ .expect("production conversation tags");
6282+ let mut rumor = EventBuilder::new(Kind::PrivateDirectMessage, text)
6283+ .tags(tags)
6284+ .build(sarah_keys.public_key());
6285+ rumor.ensure_id();
6286+ smol::block_on(EventBuilder::gift_wrap(
6287+ sarah_keys,
6288+ owner_public_key,
6289+ rumor,
6290+ [],
6291+ ))
6292+ .expect("gift wrap Sarah's message to the host")
6293+ }
6294+
6295+ /// Publish, and survive a relay that closed an idle socket.
6296+ ///
6297+ /// The harness holds one Sarah connection for the life of the run and used
6298+ /// to publish straight down it. A reply sent minutes after seeding arrived
6299+ /// on a socket the relay had already reset, and the harness reported it as
6300+ /// Sarah failing to answer — indistinguishable, from the outside, from the
6301+ /// host never producing a reply. A one-message proof would have failed on
6302+ /// transport before reaching any of the record contracts.
62576303 fn device_proof_publish(
62586304 relay: &mut crate::nostr_websocket_relay::WebSocketRelayAdapter,
62596305 auth_url: &str,
62606306 keys: &Keys,
62616307 record: &Event,
6308+ ) -> Result<(), SarahConversationError> {
6309+ match device_proof_publish_authenticated(relay, auth_url, keys, record) {
6310+ Ok(()) => Ok(()),
6311+ Err(error) => {
6312+ eprintln!("device-proof: sarah publish failed ({error}); reconnecting");
6313+ relay.connect()?;
6314+ // A new socket is a new NIP-42 session, so the auth path runs
6315+ // again rather than assuming the old challenge still holds.
6316+ device_proof_publish_authenticated(relay, auth_url, keys, record)
6317+ }
6318+ }
6319+ }
6320+
6321+ fn device_proof_publish_authenticated(
6322+ relay: &mut crate::nostr_websocket_relay::WebSocketRelayAdapter,
6323+ auth_url: &str,
6324+ keys: &Keys,
6325+ record: &Event,
62626326 ) -> Result<(), SarahConversationError> {
62636327 match relay.publish(record) {
62646328 Err(SarahConversationError::IdentityRequired) => {
@@ -6436,6 +6500,7 @@ mod tests {
64366500
64376501 let signer = SigningIdentity::from_keys(host_keys.clone());
64386502 let owner_public_key_hex = signer.public_key_hex.clone();
6503+ let owner_public_key = PublicKey::from_hex(&owner_public_key_hex).expect("owner key");
64396504 let sarah_public_key_hex = sarah_keys.public_key().to_hex();
64406505 let mut config = SarahConversationConfig::mock_fixture();
64416506 config.identity.owner_public_key_hex = owner_public_key_hex.clone();
@@ -6467,26 +6532,31 @@ mod tests {
64676532 if seed {
64686533 let sarah_relay = sarah_publisher.as_mut().expect("sarah adapter");
64696534 let now = unix_now();
6470- let greeting = EventBuilder::new(
6471- Kind::Custom(SARAH_TURN_RECORD_KIND),
6535+ let greeting = sarah_conversation_wrap(
6536+ &sarah_keys,
6537+ &owner_public_key,
6538+ &owner_public_key_hex,
6539+ &conversation_ref,
64726540 "Sarah here. This reply crossed a real relay from a real Omega host.",
6473- )
6474- .tag(Tag::parse(["conversation", conversation_ref.as_str()]).expect("conversation tag"))
6475- .custom_created_at(nostr::Timestamp::from(now))
6476- .sign_with_keys(&sarah_keys)
6477- .expect("signed Sarah message");
6541+ );
64786542 device_proof_publish(sarah_relay, &auth_url, &sarah_keys, &greeting)
64796543 .expect("publish the Sarah greeting");
64806544 eprintln!("device-proof: seeded Sarah message {}", greeting.id.to_hex());
64816545
64826546 // One encrypted engram, so "inspect memory" has a real source.
6483- let owner_public_key = PublicKey::from_hex(&owner_public_key_hex).expect("owner key");
6484- let engram_plaintext =
6485- "Chris prefers evidence-bound reports over confident summaries.";
6547+ // An engram body is a JSON object with a `mem/…` slug and a value,
6548+ // not a sentence. Seeded as prose, the host received it, refused
6549+ // the body, and quarantined it — so "inspect memory" had no real
6550+ // source and the quarantine count was measuring this bug.
6551+ let engram_plaintext = serde_json::json!({
6552+ "slug": "mem/owner_reporting_preference",
6553+ "value": "Chris prefers evidence-bound reports over confident summaries.",
6554+ })
6555+ .to_string();
64866556 let engram_ciphertext = nip44::encrypt(
64876557 sarah_keys.secret_key(),
64886558 &owner_public_key,
6489- engram_plaintext,
6559+ engram_plaintext.as_str(),
64906560 nip44::Version::default(),
64916561 )
64926562 .expect("nip44 encrypt the engram");
@@ -6607,25 +6677,22 @@ mod tests {
66076677 // admitted OpenAgents turn service: it produces a real
66086678 // signed Sarah record on a real relay, and is not evidence
66096679 // that the turn service produced it.
6610- // A Sarah turn is kind 44300. This harness published kind
6611- // 14 — a bare NIP-17 rumor — which the host never
6612- // subscribes to and would refuse anyway, so every reply it
6613- // ever "sent" was discarded in transit and the reply arm of
6614- // this proof had never actually run.
6615- let reply = EventBuilder::new(
6616- Kind::Custom(SARAH_TURN_RECORD_KIND),
6617- format!(
6680+ // A conversation message reaches the host the same way the
6681+ // owner's own does: a NIP-59 gift wrap addressed to the
6682+ // host's custody key, carrying a kind-14 rumor. This
6683+ // harness published a bare kind 14 straight to the relay,
6684+ // which the host never subscribes to, so every reply it
6685+ // reported sending was discarded in transit and the reply
6686+ // arm of this proof had never once run.
6687+ let reply = sarah_conversation_wrap(
6688+ &sarah_keys,
6689+ &owner_public_key,
6690+ &owner_public_key_hex,
6691+ &conversation_ref,
6692+ &format!(
66186693 "Received. Answering {turn_ref} from a real Omega host over a real relay."
66196694 ),
6620- )
6621- .tag(
6622- Tag::parse(["conversation", conversation_ref.as_str()])
6623- .expect("conversation tag"),
6624- )
6625- .tag(Tag::parse(["turn", turn_ref.as_str()]).expect("turn tag"))
6626- .custom_created_at(nostr::Timestamp::from(unix_now()))
6627- .sign_with_keys(&sarah_keys)
6628- .expect("signed Sarah reply");
6695+ );
66296696 match device_proof_publish(sarah_relay, &auth_url, &sarah_keys, &reply) {
66306697 Ok(()) => eprintln!(
66316698 "device-proof: SARAH REPLIED {} · {turn_ref}",