Skip to main content

Architecture and boundaries

The integration rule is simple: Rust owns deterministic protocol semantics; the host owns effects. A platform facade converts between idiomatic values and the core without implementing a second cryptographic or protocol path.

ConcernSoftchat ownsYour application owns
Identityvalidation, public-key derivation, signing, NIP-44secure storage, unlock policy, authority acquisition
Eventscanonical IDs, signatures, raw tags, typed projectionsdatabase schema, indexing, retention
Relayframe codecs, state transitions, action plansWebSocket, timers, connectivity, retry scheduling
Persistencebounded ingestion batches and result validationtransactions, migrations, durable storage
Noiseexact handshake/transport state and chunkingsocket bytes, connection lifecycle
Syncbounded Negentropy snapshots and reconciliationquerying and changing the database
Attachmentsmetadata validation, streaming transform, NIP-98 planfiles, staging, HTTP, redirects, background transfer
Diagnosticsstable counters, redacted failures, build fingerprinttelemetry transport, sampling, user consent

Trust transitionsโ€‹

  1. Validate bytes or JSON before treating them as a protocol value.
  2. Authenticate every NIP-59 layer before exposing its rumor.
  3. Persist an inbound relay event in one host transaction.
  4. Call confirmIngested only after that transaction commits.
  5. Stage decrypted media privately until finish authenticates its tag.

Receiving bytes is not persistence. Sending bytes is not delivery. A WebSocket callback must therefore never mark an event as safely stored or acknowledged without the corresponding state transition.

Data ownershipโ€‹

  • Identities, Noise transports, Negentropy clients, relay sessions, and attachment streams hold native state.
  • Events, rumors, filters, projections, request plans, diagnostics, and encrypted messages are ordinary immutable language-owned values.
  • Generated UniFFI/JNA, UniFFI Swift/C, and raw wasm-bindgen APIs are internal and may change without notice.

LocalIdentity, RelaySession, NoiseTransport, and stream objects own state. Use typed newtypes for keys and event identifiers at trust boundaries.

Bounds and failure policyโ€‹

Inputs are bounded before expensive work. The portable signed-event size limit is 512 KiB; timestamp, tag, batch, frame, Noise, reconciliation, plaintext, ciphertext, and attachment limits are enforced by the relevant constructor or operation. Treat a rejected limit as a permanent input failure, not a reason to retry with more memory.

Errors are stable and redacted. Never branch on display text or upstream exception names. See errors and diagnostics.