# Symmetric Sync Protocol Architecture

This document details the architecture of the `ibgib` Symmetric Sync Protocol. It describes the "Saga" model, the state machine, and the data structures used to ensure robust, audit-trail-based synchronization.

## 1. Core Principles

### 1.1 The Saga Model
Synchronization is not a stateless request/response. It is a **Saga**: a durable, linear timeline of immutable frames that represents the conversation between two peers.
*   **Audit Trail**: The Saga Timeline (`SyncIbGib`) records every step (`Init`, `Ack`, `Delta`, `Commit`).
*   **Symmetry**: Both peers (Alice and Bob) run the exact same `SyncSagaCoordinator`. There is no "Client" or "Server" code, only "Initiator" and "Reactor".

### 1.2 Smart Diff (Partial Updates)
To optimize bandwidth, the protocol exchanges "Knowledge Maps" to calculate the precise difference between peers.
*   **Knowledge Map**: A summary of what a peer knows (Timelines, Tips, and Ancestors).
*   **Space-Side Dependency Graph**: The `Space.getDependencyGraph` capability allows the "Network" layer to request *only* the new nodes in a graph, skipping everything the receiver already has.

## 2. Protocol Workflow (The State Machine)

The `SyncSagaCoordinator` drives a Finite State Machine (FSM) via the `handleSagaFrame` method.

### 2.1 The Ping-Pong Flow

1.  **Start (Initiator)**:
    *   Initiator (Alice) calls `sync`.
    *   Generates `Init` frame containing her `KnowledgeMap` and `Mode` (Push/Pull/Sync).
    *   Sends `Init` to Bob.

2.  **Gap Analysis (`handleInitFrame`)**:
    *   Reactor (Bob) receives `Init`.
    *   Bob compares Alice's `KnowledgeMap` against his own local space.
    *   **Identifies Gaps**:
        *   **Delta Request**: "Alice has `X`, I don't. Send me `X`."
        *   **Push Offer**: "I have `Y` (newer than Alice's). Do you want `Y`?"
    *   Bob sends `Ack` frame containing `deltaReqAddrs` and `pushOfferAddrs`.

3.  **Payload Generation (`handleAckFrame`)**:
    *   Alice receives `Ack`.
    *   **Process Requests**: Iterates `deltaReqAddrs` (what Bob wants).
    *   **Smart Diff**: Uses Bob's `KnowledgeMap` (sent in Ack) to exclude all shared history.
    *   **Payload**: Generates a bundle of new ibGibs.
    *   Alice sends `Delta` frame containing the payload.

4.  **Convergence (`handleDeltaFrame`)**:
    *   Bob receives `Delta`.
    *   **Verifies**: Checks content-address hashes.
    *   **Merges**: Puts data into his local space.
    *   Bob sends `Commit` frame.

5.  **Completion (`handleCommitFrame`)**:
    *   Alice receives `Commit`.
    *   Saga ends.

## 3. Data Structures

### 3.1 SyncSagaMessage
All frames are `SyncIbGib`s carrying specific data payloads.

*   `sagaId`: Unique UUID for the duration of this exchange.
*   `stage`: `init` | `ack` | `delta` | `commit` | `conflict`.
*   `knowledgeMap`: `{ [tjpAddr]: [tipAddr, ...ancestors] }`.

### 3.2 Constants vs Timelines
The protocol supports two types of data:
*   **Timelines**: Mutable data with a "Temporal Junction Point" (TJP). Syncing involves finding the "Latest" tip.
*   **Stones (Constants)**: Immutable data without a history (e.g., Code lists, Configs). Syncing involves simple existence checks.

## 4. Security & Identity

See [Security](./security.md) documentation.

### 4.1 Ephemeral Session Domain-Binding
To prevent an ephemeral session keystone `S` from acting as a "blank check" that could authorize operations on arbitrary timelines, the protocol implements strict domain-binding:
*   **Genesis Binding**: The first frame of the session keystone ($S^{Stjp}$) must explicitly contain a `targetAddrs` array inside its `frameDetails` mapping to the exact addresses of the ibgibs being synchronized.
*   **Verification Boundary**: During the initial turn-based synchronization (e.g., `handleInitFrame`), the validating peer (Receiver) must extract this `targetAddrs` array from $S^{Stjp}$'s genesis `frameDetails` and verify it contains the synchronizing domain's target address, rejecting the handshake on any mismatch.

## 5. Sync Peer Lifetime

A `SyncPeer` is **single-saga scoped**. One peer instance services exactly one `sync(...)` call from creation through saga completion. The coordinator's `newTestPeer()` factory pattern (or equivalent in production) must create a fresh peer per saga.

**Rationale**: The session keystone `S` is derived from `KDF(senderSecret, sagaId)`. Since `sagaId` is unique per saga, `S` is inherently saga-bound. A peer that outlived its saga would hold stale identity state, violating the security model.

> [!WARNING]
> ## 5. Sync Storage & Persistence Rules (Critical Security)
>
> Knowing **when** to put **what** ibgib frames into **which** space is a high-priority security concern. The sync algorithm must balance maintaining a robust audit trail with protecting durable spaces from unauthorized pollution. Always follow these rules:
>
> ### Rule 1: Saga/Control Audit Trail (Immediate Persistence)
> We need an audit trail of the sync saga itself (including context and authentication info).
> * The initial session keystone (or any valid evolution performed locally) and saga control ibgibs are trusted because *we* just generated them.
> * Persist these immediately in the **local durable space** (and possibly the local temporary space if needed for transit) so they are available for validation and auditing.
>
> ### Rule 2: Domain IbGibs (Deferred Persistence)
> We do NOT persist *any* domain ibgibs (e.g., user payloads) into the durable space before the `commit` phase of the sync process.
> * Domain ibgibs must be routed to the **tempSpace** during the `delta` or `ack` phases.
> * The `commit` phase acts as the transaction boundary. By waiting for the commit, we avoid polluting durable spaces on error, preventing the need for complex rollbacks.
>
> ### Rule 3: Validate First, Store Second
> We never chance storing intrinsically invalid ibgibs.
> * **First step:** Intrinsically validate ibgibs (hashes, basic structure).
> * **Second step:** Perform AuthN/AuthZ checks (using the session keystone).
> * **Final step:** Once verified, saga "control" ibgibs (which only contain "soft" links to domain ibgibs) can be safely persisted to durable spaces.

## 6. Transport & Framing (Naive WebSocket Implementation)

> [!NOTE]
> ### TODO: Robust WebSocket Framing & TCP Reassembly
> The current custom WebSocket integration on the node server (`ws-helper.mts`) relies on a naive framing implementation. It assumes each socket `data` event emitted by Node's TCP stream contains exactly one complete WebSocket frame.
> 
> * **TCP Fragmentation Risk**: When payload sizes grow (e.g. sending larger cryptographic proofs or domain datasets), TCP will split the frame across multiple packets or buffers. The naive decoder will decode a partial frame, causing parse errors (e.g., `Unterminated string in JSON`).
> * **Future Action**: Replace the custom, naive WebSocket framing code with a robust stateful accumulator that aggregates incoming TCP chunks until the entire frame length (indicated in the frame header) is present in memory, or transition to a fully tested, production-grade library (such as the NPM `ws` package).
