Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rendezvous Architecture

This document describes the rendezvous architecture in Aura. It explains peer discovery, descriptor propagation, service-surface advertisement, connectivity selection, channel establishment, and relay-to-direct holepunch upgrades. It aligns with the authority and context model. It scopes all rendezvous behavior to relational contexts.

1. Overview

Rendezvous establishes secure channels between authorities. The RendezvousService exposes prepare_publish_descriptor() and prepare_establish_channel() methods. The service returns guard outcomes that the caller executes through an effect interpreter. Rendezvous operates inside a relational context and uses the context key for encryption. Descriptors appear as facts in the context journal. Propagation uses journal synchronization (aura-sync), not custom flooding.

Rendezvous owns descriptor semantics, publication, validation, and establish bootstrap. It does not own the long-lived mutable descriptor cache. The runtime in aura-agent owns that cache and passes descriptor snapshots into peer-discovery views.

Rendezvous does not establish global identity. All operations are scoped to a ContextId. A context defines which authorities may see descriptors. Only participating authorities have the keys required to decrypt descriptor payloads.

Rendezvous descriptor exchange can run in provisional (A1) or soft-safe (A2) modes to enable rapid connectivity under poor network conditions, but durable channel epochs and membership changes must be finalized via consensus (A3). Soft-safe flows should emit convergence and reversion facts so participants can reason about reversion risk while channels are warming.

Bootstrap discovery is a separate concern. First-run startup may surface same-machine or same-LAN bootstrap candidates before any enrollment or acceptance has completed. Those candidates are not ordinary rendezvous peers. They must not inflate ordinary peer counts or become context-scoped descriptor participants before enrollment or acceptance completes.

Browser runtimes cannot join the native UDP LAN path. Browser-involved startup uses a bootstrap broker path that publishes ephemeral bootstrap descriptors and then hands control back to the existing invitation or device-enrollment flow.

1.1 Secure‑Channel Lifecycle (A1/A2/A3)

  • A1: Provisional Descriptor / handshake facts allow immediate connectivity.
  • A2: Coordinator soft‑safe convergence certs indicate bounded divergence.
  • A3: ChannelEstablished is finalized by consensus and accompanied by a CommitFact.

Rendezvous must treat A1/A2 outputs as provisional until A3 evidence is merged. See Consensus for commit evidence binding.

2. Architecture

The rendezvous crate follows Aura's fact-based architecture:

  1. Guard Chain First: All network sends flow through guard evaluation before execution
  2. Facts Not Flooding: Descriptors are journal facts propagated via aura-sync, not custom flooding
  3. Standard Receipts: Uses the system Receipt type with epoch binding and cost tracking
  4. Session-Typed Protocol: Protocols expressed as MPST choreographies with guard annotations
  5. Unified Transport: Channels established via SecureChannel with Noise IKpsk2

2.1 Module Structure

aura-rendezvous/
├── src/
│   ├── lib.rs           # Public exports
│   ├── facts.rs         # RendezvousFact domain fact type
│   ├── protocol.rs      # MPST choreography definition
│   ├── service.rs       # RendezvousService (main coordinator)
│   ├── descriptor.rs    # Transport selector and builder
│   └── new_channel.rs   # SecureChannel, ChannelManager, Handshaker

3. Connectivity and Service Advertisement

Rendezvous descriptors carry two kinds of information. One surface describes concrete connectivity endpoints. The other surface describes abstract service families such as Establish, Move, and Hold. These surfaces must remain separate.

Connectivity endpoints describe how a peer may be reached. Service advertisements describe what the peer is willing to provide. Runtime policy combines both surfaces with local permit state, health, and trust evidence. Descriptor publication itself does not commit the final route choice.

The implementation still derives split connectivity and service-surface views from legacy TransportHint compatibility data in some descriptor and invitation paths. That compatibility bridge is quarantined. Owner: adaptive_privacy_runtime. Removal condition: descriptor publication and invitation bootstrap no longer need the legacy hint bridge. New code should consume LinkEndpoint, ServiceDescriptor, EstablishPath, MovePath, and HoldDescriptor views rather than treating transport hints as final routing policy.

3.1 Bootstrap Discovery Plane

Aura uses two distinct discovery planes during startup:

  1. Native rendezvous/LAN discovery for native-to-native startup on the same LAN.
  2. Broker-backed bootstrap discovery for any startup shape that involves the browser, including same-machine TUI to Web and same-LAN TUI to Web or Web to Web.

The bootstrap broker publishes ephemeral bootstrap descriptors only. It exists to help first-run instances discover one another and exchange invitation or device-enrollment material through the existing enrollment path. It does not create an ordinary rendezvous relationship on its own.

Bootstrap broker URLs carry only non-secret endpoint identifiers. Bearer credentials and invitation retrieval tokens travel in headers or another explicit credential channel, never in URL query strings, so logs, histories, referrers, and diagnostics do not capture secret material.

Bootstrap delivery identifies the recipient by both authority and device. Two devices belonging to one authority have independent retrieval credentials and pending deliveries; registering either device cannot replace the other's credential or drain its queue. Discovery metadata identifies a destination but does not authorize an enrollment, contact, or device operation.

The broker wire contract is version 2. Registration and candidate records carry the device identifier, delivery records carry the recipient device identifier, and retrieval addresses name both authority and device. Version 1 records and authority-only retrieval paths are not accepted.

Each delivery carries an explicit invitation purpose. The receiving bootstrap owner retains every retrieved delivery until the matching existing workflow accepts custody or returns a typed refusal. Queue order does not determine purpose, and retrieving a batch cannot silently discard its other members. An enrollment prefill is observed input requiring the existing explicit approval and verifier contract. Contact delivery likewise requires canonical signed invitation verification and the contact workflow; neither purpose can substitute for the other.

Broker retrieval acknowledges transport custody only. One-use retrieval credentials do not acknowledge workflow completion, enrolled-device authority, or cancellation cleanup. Any subsequent shortcut delivery retains the original approved enrollment owner and its purpose-bound acknowledgment under the original selected provider and deadline; broker records cannot reconstruct that authority. The issuer selects an external recipient authority and device and a Contact or Guardian purpose before starting an enrollment-with-shortcut workflow. After original enrollment disposal and positive acknowledgment, the issuer creates a fresh signed invitation for that exact destination and purpose under the separate fixed shortcut endpoint, then delivers it through the broker. The enrollment code is not replayed as the shortcut, and broker delivery does not turn enrollment-start success into invitation acceptance.

Pending device-enrollment VM transport

Before adoption, an invitee still runs under its provisional authority while the enrollment VM addresses both roles under the issuer's subject authority. The broker may carry these VM envelopes without promoting a bootstrap candidate to an ordinary rendezvous peer. Each direction needs its own authenticated mailbox scope; possession of a broker bearer credential alone does not authorize an enrollment route.

The invitee's first-frame mailbox registration is authorized by the verified original setup key and exact admitted manifest. The reverse mailbox is registered by the issuer under the retained original enrollment control and approved native threshold policy. Its signing transcript belongs to the original user-approved initiation batch; later generic signing cannot create the route. Its signed registration binds the original invitation, manifest and original setup digests, session, subject, issuer and invitee devices, direction, and expiry. The invitee may send a response only after checking that registration against its independently retained enrollment baseline and proving possession of its admitted setup key. Neither a self-certified descriptor nor a signature copied from the manifest can establish the reverse route. The return registration uses the v2 signed transcript; old v1 records cannot authorize a route after the setup-digest binding was added.

For quorum issuance the verifier uses the authenticated native root signing node (NodeIndex(0)), requires its original threshold of at least two, and matches its group verifier to the separately pinned initiator confirmation verifier in the transferred manifest. Another active inventory key, even one listing the issuer device, cannot register the return route. For an original K1 issuance, the same retained physical SingleSigner root selected for the signed manifest signs a distinct return transcript before the enrollment code is exported. Its package and epoch must match the independently pinned root and initiator confirmation verifier. The registration records the signing mode; K1 and quorum proofs cannot substitute for one another. Both modes check expiry through the selected physical-time provider. Each Bob-to-Alice VM envelope has a separate setup-key authorization over its exact message ID and serialized envelope digest, bound to the verified issuer return registration and original session. The broker cannot promote this authorization to membership or treat a first-frame proof as a reverse route.

Broker enqueue, take, and acknowledgment use separate scoped credentials. The broker enforces the signed scope, bounded capacity, and expiry using its selected physical-time provider. A recipient durably retains an exact message-ID/envelope record under the protected enrollment admission before acknowledging delivery, deduplicates redelivery, and replays that record on same-profile reopen. VM ingress remains responsible for final session, role, message, and cryptographic verification. Terminal acknowledgment or verified failure retires the scoped mailbox and replay record; an ambiguous send or take cannot issue a new enrollment operation or discard the original frame.

Bootstrap and stale-node re-entry remain distributed surfaces. Aura does not define a singleton bootstrap registry. Runtime code may combine remembered direct contacts, neighborhood discovery-board publications, bounded bootstrap introductions, and broker-backed ephemeral startup descriptors. aura-rendezvous owns schema validation for bootstrap contact hints and neighborhood re-entry hints, but it does not own final route choice or a global bootstrap cache.

Operationally, browser and native first-run startup may become bootstrap-visible only after the first native account runtime exists and begins hosting the broker automatically. For example, in a TUI plus Web startup, the user may create the web account first and the TUI account second. Once the TUI runtime is live, both instances should surface one another as bootstrap candidates without extra user setup steps.

3.2 Holepunching and Upgrade Policy

Aura uses a relay-first, direct-upgrade model for NAT traversal:

  1. Start on relay as soon as both peers have a valid descriptor path.
  2. Exchange direct/reflexive candidates from descriptor facts.
  3. Launch bounded direct upgrade attempts (holepunch) in the background.
  4. Promote to direct when a recoverable direct path succeeds. Otherwise remain on relay.

Retry state is tracked with typed generations (CandidateGeneration, NetworkGeneration) and bounded backoff (AttemptBudget, BackoffWindow) in PeerConnectionActor. Generation changes reset retry budgets, which avoids stale retry loops after interface/NAT changes.

The runtime drives one such actor per (context, peer) in its service registry (PeerPathState). Every transport send toward the peer, and every channel preparation, asks that state which establish path to use: the relay when the peer advertises one, otherwise its first dialable direct endpoint. While on the relay, a send may also carry a bounded direct probe (a TCP connect to a direct candidate, nothing sent); a reachable listener promotes the state to direct, and a failed direct send degrades it back to the relay and carries the send over the relay. The candidate generation advances when the peer's usable path set changes; the network generation advances when the peer's own relay session is re-established. The current selection, with both generations and the attempts used, is the SelectedPeerPath observation exposed through the runtime bridge and surfaced as the peer_path_selected runtime event.

Recoverability is evaluated from local binding/interface provenance, not from reflexive addresses alone. This prevents treating stale external mappings as viable direct paths.

Operationally:

  • Relay path is the safety baseline.
  • Direct holepunch is an optimization path.
  • Network changes can trigger a fresh upgrade cycle without dropping relay connectivity.

3.3 Relay Transport

A relay-capable node (serve_relay in the agent network configuration) accepts relay registrations on its ordinary transport listener. A peer configured with a relay_address keeps one outbound TCP connection to that node (RelaySessionService): it sends a register control frame naming its authority and device, receives a registered acknowledgment that names the relay's authority, then keeps the session alive with periodic keepalive frames that the relay acknowledges. A missing acknowledgment or a broken socket ends the session; the peer reconnects with bounded backoff and advances its network generation.

The acknowledgment carries the relay's own descriptor, which the peer caches like any other descriptor. While registered, the peer's published descriptor carries a WebSocketRelay transport hint naming only the relay authority; addresses never enter a relay hint. A sender whose path state selects that hint resolves the relay authority to a dialable address from the relay's descriptor (cached from its own registration acknowledgment, or propagated like any descriptor), dials the relay and writes the frame unchanged; the relay (RelayForwarderActor) reads only the frame's routing header and writes it down the destination's session connection (the matching device when the frame is device-addressed, every registered device otherwise). The frame keeps the sender's guard-chain receipt, so the receiver's LAN integrity check is the same as for a direct frame. Control frames never enter the runtime inbox, and a node that does not serve as a relay drops frames addressed to other authorities.

Registration binds an authority id to a connection without proving the registrant holds that authority's keys. Payloads are end-to-end protected, so a false registration can only divert or drop frames; the relay forwards only to registered destinations and only from accepted connections, and later registrations for the same authority and device replace earlier ones. Authenticated registration against the registrant's descriptor key is a follow-up.

4. Data Structures

4.1 Domain Facts

Rendezvous uses domain facts in the relational context journal. Facts are propagated via journal synchronization.

#![allow(unused)]
fn main() {
/// Rendezvous domain facts stored in context journals
pub enum RendezvousFact {
    /// Transport descriptor advertisement
    Descriptor(RendezvousDescriptor),

    /// Channel established acknowledgment
    ChannelEstablished {
        initiator: AuthorityId,
        responder: AuthorityId,
        channel_id: [u8; 32],
        epoch: u64,
    },

    /// Descriptor revocation
    DescriptorRevoked {
        authority_id: AuthorityId,
        nonce: [u8; 32],
    },
}
}

4.2 Rendezvous Descriptors

#![allow(unused)]
fn main() {
/// Rendezvous descriptor for peer discovery
pub struct RendezvousDescriptor {
    /// Authority publishing this descriptor
    pub authority_id: AuthorityId,
    /// Context this descriptor is for
    pub context_id: ContextId,
    /// Legacy connectivity hints used to derive split views
    pub transport_hints: Vec<TransportHint>,
    /// Handshake PSK commitment (hash of PSK derived from context)
    pub handshake_psk_commitment: [u8; 32],
    /// Public key for Noise IK handshake (Ed25519 public key)
    pub public_key: [u8; 32],
    /// Validity window start (ms since epoch)
    pub valid_from: u64,
    /// Validity window end (ms since epoch)
    pub valid_until: u64,
    /// Nonce for uniqueness
    pub nonce: [u8; 32],
    /// What the peer wants to be called (optional, for UI purposes)
    pub nickname_suggestion: Option<String>,
}
}

A WebSocketRelay hint names only the relay authority. The relay's own descriptor, not the hint, says where the relay is reached, so a relay can move without every peer republishing.

RendezvousDescriptor is the authoritative shared object in the journal. Callers derive LinkEndpoint and ServiceDescriptor views from it. This keeps the fact schema stable during migration while preventing route policy from hardening into the fact format.

Bootstrap records follow the same rule. Shared bootstrap contact hints and neighborhood re-entry hints are typed, expiring, replay-bounded records. They are valid only inside their scoped contexts. They do not encode final route policy, trust tier, or canonical provider ranking.

4.3 Split Connectivity and Service Surfaces

#![allow(unused)]
fn main() {
pub struct LinkEndpoint {
    pub protocol: LinkProtocol,
    pub address: Option<String>,
    pub relay_authority: Option<AuthorityId>,
}

pub struct ServiceDescriptor {
    pub header: ServiceDescriptorHeader,
    pub kind: ServiceDescriptorKind,
}

pub struct EstablishPath {
    pub route: Route,
}

pub struct MovePath {
    pub route: Route,
}
}

LinkEndpoint answers how a peer may be reached. ServiceDescriptor answers what service family is being advertised. Runtime-owned selection state combines these views with local policy and social inputs. The selected provider should observe only the generic service action, not the social reason it was chosen.

EstablishPath and MovePath are the explicit path objects consumed by current bootstrap and movement flows. They are derived from descriptor views plus runtime-local policy, but they remain socially neutral: home, neighborhood, guardian, friend, and similar provider roles must not appear in the path schema itself.

Anonymous path establishment uses the same Establish family boundary. aura-rendezvous validates path objects and descriptor inputs. aura-agent owns reusable anonymous path lifecycle, route choice, and path reuse policy.

5. MPST Choreographies

Rendezvous protocols are defined as MPST choreographies with guard annotations.

5.1 Direct Exchange Protocol

#![allow(unused)]
fn main() {
tell! {
    #[namespace = "rendezvous_exchange"]
    protocol RendezvousExchange {
        roles: Initiator, Responder;

        // Initiator publishes descriptor (fact insertion, propagates via sync)
        Initiator[guard_capability = "rendezvous:publish",
                  journal_facts = "descriptor_offered"]
        -> Responder: DescriptorOffer(RendezvousDescriptor);

        // Responder publishes response descriptor
        Responder[guard_capability = "rendezvous:publish",
                  journal_facts = "descriptor_answered"]
        -> Initiator: DescriptorAnswer(RendezvousDescriptor);

        // Direct channel establishment
        Initiator[guard_capability = "rendezvous:connect",
        -> Responder: HandshakeInit(NoiseHandshake);

        Responder[guard_capability = "rendezvous:connect",
                  journal_facts = "channel_established"]
        -> Initiator: HandshakeComplete(NoiseHandshake);
    }
}
}

5.2 Relayed Protocol

#![allow(unused)]
fn main() {
tell! {
    #[namespace = "relayed_rendezvous"]
    protocol RelayedRendezvous {
        roles: Initiator, Relay, Responder;

        Initiator[guard_capability = "rendezvous:relay",]
        -> Relay: RelayRequest(RelayEnvelope);

        Relay[guard_capability = "relay:forward",
              leak = "neighbor:1"]
        -> Responder: RelayForward(RelayEnvelope);

        Responder[guard_capability = "rendezvous:relay"]
        -> Relay: RelayResponse(RelayEnvelope);

        Relay[guard_capability = "relay:forward",
              leak = "neighbor:1"]
        -> Initiator: RelayComplete(RelayEnvelope);
    }
}
}

6. Descriptor Propagation

Descriptors propagate via journal synchronization. This replaces custom flooding.

  1. Authority creates a RendezvousFact::Descriptor fact
  2. Guard chain evaluates the publication request
  3. On success, fact is inserted into the context journal
  4. Journal sync (aura-sync) propagates facts to context participants
  5. Peers query journal for peer descriptors

This model provides:

  • Deduplication: Journal sync handles duplicate facts naturally
  • Ordering: Facts have causal ordering via journal timestamps
  • Authorization: Guard chain validates before insertion
  • Consistency: Same propagation mechanism as other domain facts

6.1 aura-sync Integration

The aura-sync crate provides a RendezvousAdapter that bridges peer discovery with runtime-owned descriptor snapshots:

#![allow(unused)]
fn main() {
use aura_sync::infrastructure::RendezvousAdapter;

// Create adapter using the local authority identity.
let adapter = RendezvousAdapter::new(local_authority);

// Query peer info from a runtime-owned descriptor snapshot.
if let Some(peer_info) = adapter.get_peer_info(&descriptors, context_id, peer, now_ms) {
    if peer_info.has_direct_transport() {
        // Use direct connection
    }
}

// Check which peers need descriptor refresh
let stale_peers = adapter.peers_needing_refresh(&descriptors, context_id, now_ms);
}

The adapter is a pure view helper. It does not own or mutate the cache. The runtime cache stays in aura-agent.

6.2 Social Inputs and Route Selection

Rendezvous may consume socially rooted provider inputs, but it does not own social topology or trust evaluation. The Neighborhood Plane and Web of Trust Plane produce permit and candidate inputs. The runtime combines those inputs with descriptor views and local policy.

This separation is required for privacy. Shared descriptor facts must not expose route classes such as "friend relay" or "neighborhood hold". The selected provider should observe only the generic service action. The current descriptor view therefore advertises generic Hold surfaces for deferred-delivery and cache-replica retention, while selector issuance, holder rotation, and retrieval policy remain runtime-local. See Social Architecture for the plane split.

7. Protocol Flow

The rendezvous sequence uses the context between two authorities.

sequenceDiagram
    participant A as Authority A
    participant J as Context Journal
    participant B as Authority B

    A->>A: Build descriptor
    A->>A: Evaluate guards
    A->>J: Insert descriptor fact
    J-->>B: Sync descriptor fact
    B->>B: Query descriptor
    B->>A: Select transport
    A->>B: Noise IKpsk2 handshake
    B->>J: Record ChannelEstablished fact

8. Guard Chain Integration

All rendezvous operations flow through the guard chain.

8.1 Guard Capabilities

#![allow(unused)]
fn main() {
pub mod guards {
    pub const CAP_RENDEZVOUS_PUBLISH: &str = "rendezvous:publish";
    pub const CAP_RENDEZVOUS_CONNECT: &str = "rendezvous:connect";
    pub const CAP_RENDEZVOUS_RELAY: &str = "rendezvous:relay";
}
}

8.2 Flow Costs

#![allow(unused)]
fn main() {
pub const DESCRIPTOR_PUBLISH_COST: u32 = 1;
pub const CONNECT_DIRECT_COST: u32 = 2;
pub const CONNECT_RELAY_COST: u32 = 3;
pub const RELAY_FORWARD_COST: u32 = 1;
}

8.3 Guard Evaluation

The service prepares operations and returns GuardOutcome containing effect commands. The caller executes these commands.

#![allow(unused)]
fn main() {
// 1. Prepare snapshot of current state
let snapshot = GuardSnapshot {
    authority_id: alice,
    context_id: context,
    flow_budget_remaining: 100,
    capabilities: vec!["rendezvous:publish".into()],
    epoch: 1,
};

// 2. Prepare publication (pure, sync)
let outcome = service.prepare_publish_descriptor(
    &snapshot, context, transport_hints, now_ms
);

// 3. Check decision and execute effects
if outcome.decision.is_allowed() {
    for cmd in outcome.effects {
        execute_effect_command(cmd).await?;
    }
}
}

9. Secure Channel Establishment

After receiving a valid descriptor, the initiator selects a transport. Both sides run Noise IKpsk2 using a context-derived PSK. Successful handshake yields a SecureChannel.

9.1 Channel Structure

#![allow(unused)]
fn main() {
pub struct SecureChannel {
    /// Unique channel identifier
    channel_id: [u8; 32],
    /// Context this channel belongs to
    context_id: ContextId,
    /// Local authority
    local: AuthorityId,
    /// Remote peer
    remote: AuthorityId,
    /// Current epoch (for key rotation)
    epoch: u64,
    /// Channel state
    state: ChannelState,
    /// Agreement mode (A1/A2/A3) for the channel lifecycle
    agreement_mode: AgreementMode,
    /// Whether reversion is still possible
    reversion_risk: bool,
    /// Whether the channel needs key rotation
    needs_rotation: bool,
    /// Bytes sent on this channel (for flow budget tracking)
    bytes_sent: u64,
    /// Bytes received on this channel
    bytes_received: u64,
}

pub enum ChannelState {
    Establishing,
    Active,
    Rotating,
    Closed,
    Error(String),
}
}

9.2 Channel Manager

The ChannelManager tracks active channels:

#![allow(unused)]
fn main() {
let mut manager = ChannelManager::new();

// Register a new channel
manager.register(channel);

// Find channel by context and peer
if let Some(ch) = manager.find_by_context_peer(context, peer) {
    if ch.is_active() {
        // Use channel
    }
}

// Advance epoch and mark channels for rotation
manager.advance_epoch(new_epoch);
}

9.3 Handshake Flow

The Handshaker state machine handles Noise IKpsk2:

#![allow(unused)]
fn main() {
// Initiator side
let mut initiator = Handshaker::new(HandshakeConfig {
    local: alice,
    remote: bob,
    context_id: context,
    psk: derived_psk,
    timeout_ms: 5000,
});

let init_msg = initiator.create_init_message(epoch)?;
// ... send init_msg to responder ...
initiator.process_response(&response_msg)?;
let result = initiator.complete(epoch, true)?;
let channel = initiator.build_channel(&result)?;
}

9.4 Key Rotation

Channels support epoch-based key rotation. When the epoch advances, channels rekey using the new context-derived PSK.

#![allow(unused)]
fn main() {
impl SecureChannel {
    pub fn needs_epoch_rotation(&self, current_epoch: u64) -> bool {
        self.epoch < current_epoch
    }

    pub fn rotate(&mut self, new_epoch: u64) -> AuraResult<()> {
        // Rekey the channel
        self.state = ChannelState::Rotating;
        self.epoch = new_epoch;
        self.needs_rotation = false;
        self.state = ChannelState::Active;
        Ok(())
    }
}
}

10. Service Interface

The rendezvous service coordinates descriptor publication and channel establishment.

#![allow(unused)]
fn main() {
impl RendezvousService {
    /// Create a new rendezvous service
    pub fn new(authority_id: AuthorityId, config: RendezvousConfig) -> Self;

    /// Prepare to publish descriptor to context journal
    pub fn prepare_publish_descriptor(
        &self,
        snapshot: &GuardSnapshot,
        context_id: ContextId,
        transport_hints: Vec<TransportHint>,
        now_ms: u64,
    ) -> GuardOutcome;

    /// Prepare to establish channel with peer
    pub fn prepare_establish_channel(
        &self,
        snapshot: &GuardSnapshot,
        context_id: ContextId,
        peer: AuthorityId,
        psk: &[u8; 32],
    ) -> AuraResult<GuardOutcome>;

    /// Prepare to handle incoming handshake
    pub fn prepare_handle_handshake(
        &self,
        snapshot: &GuardSnapshot,
        context_id: ContextId,
        initiator: AuthorityId,
        handshake: NoiseHandshake,
        psk: &[u8; 32],
    ) -> GuardOutcome;

    /// Cache a peer's descriptor (from journal sync)
    pub fn cache_descriptor(&mut self, descriptor: RendezvousDescriptor);

    /// Get a cached descriptor
    pub fn get_cached_descriptor(
        &self,
        context_id: ContextId,
        peer: AuthorityId,
    ) -> Option<&RendezvousDescriptor>;

    /// Check if our descriptor needs refresh
    pub fn needs_refresh(
        &self,
        context_id: ContextId,
        now_ms: u64,
        refresh_window_ms: u64,
    ) -> bool;
}
}

11. Effect Commands

The service returns GuardOutcome with effect commands to execute:

#![allow(unused)]
fn main() {
pub enum EffectCommand {
    /// Append fact to journal
    JournalAppend { fact: RendezvousFact },
    /// Charge flow budget
    ChargeFlowBudget { cost: FlowCost },
    /// Send handshake init message
    SendHandshake { peer: AuthorityId, message: HandshakeInit },
    /// Send handshake response
    SendHandshakeResponse { peer: AuthorityId, message: HandshakeComplete },
    /// Record operation receipt
    RecordReceipt { operation: String, peer: AuthorityId },
}
}

12. Failure Modes and Privacy

Failures occur during guard evaluation, descriptor validation, or transport establishment. These failures are local. No network packets reveal capability or budget failures.

Context isolation prevents unauthorized authorities from reading descriptors. Transport hints do not reveal authority structure. Relay identifiers reveal only the relay authority. Descriptor contents remain encrypted for transit.

13. Summary

Rendezvous provides encrypted peer discovery and channel establishment scoped to relational contexts. Descriptors propagate through journal synchronization with guard chain enforcement. Secure channels use Noise IKpsk2 and QUIC. All behavior remains private to the context and reveals no structural information. The architecture uses standard Aura primitives: domain facts, guard chains, MPST choreographies, and effect interpretation.

Required Contact response state

A verified Contact response is bound to the retained imported invitation, its issuer point and the exact acknowledged acceptance digest. The response owner carries that complete state through materialization. Missing, malformed, oversized or mismatched required backing metadata cannot be replaced by a cached Pending state. Invalid signatures remain distinct from failures of the cryptographic provider.