Skip to main content
Version: v0.9.0a2

Federation Handshake

4 min readNode operator · Protocol implementerSpec-05-Federation-Trust

What this page is

How two Stigmem nodes run by different organizations establish bilateral trust, exchange capability advertisements, and begin replicating facts — a three-phase protocol with cryptographic guarantees and no central authority.

The problem

Two Stigmem nodes run by different organizations want to share knowledge. But sharing means trusting: trusting that the other node won't inject malicious facts, won't escalate scope boundaries, won't replay old tokens, and won't forge provenance. You need a handshake protocol that establishes bilateral trust with cryptographic guarantees — without requiring a central authority.

Naive approaches and why they fail

Approach
Failure mode
Why it doesn't work
Shared API key
blast radius
Both nodes use the same credential. If one is compromised, both are. No way to restrict what the peer can do (read vs. write, which scopes) or to revoke access without rotating the key on both sides. No replay protection.
OAuth / JWT with central IdP
single point of failure
Requires a trusted third party to issue tokens. In a federated network where each node is independently operated, there may be no shared IdP. Even if there is, the central authority becomes a single point of failure and a trust bottleneck.
Mutual TLS only
missing authorization
mTLS authenticates the transport but doesn't express authorization. Knowing who the peer is doesn't tell you what they're allowed to do.

Our model

Stigmem's federation handshake is a three-phase protocol: peer declaration, verification, and capability negotiation. The registration contract is defined in Spec-05-Federation-Trust.

Peer declaration

Node A sends a signed PeerDeclaration to Node B:

{
"node_url": "https://node-a.example.com",
"node_id": "stigmem://node-a.example.com",
"federation_pubkey": "<base64url Ed25519 public key>",
"allowed_scopes": ["public"],
"declaration_sig": "<Ed25519 sig over canonical JSON>",
"signed_at": "2026-05-01T00:00:00Z"
}

The allowed_scopes array is the authorization grant: Node A is willing to share public-scoped facts with Node B. The signature proves that Node A (holder of the private key) issued this declaration.

Verification

Node B fetches Node A's /.well-known/stigmem to retrieve the published federation_pubkey. It verifies declaration_sig against that key. If the key in the declaration doesn't match the published key, the peer is rejected — this prevents a third party from forging declarations.

Mutual federation requires both sides to complete this handshake.

Replication does not begin until both peers are "active".

Capability negotiation

After verification, nodes exchange capability advertisements:

{
"relations_understood": ["memory:", "intent:", "roadmap:"],
"federation_mode": "pull",
"pull_interval_s": 30,
"contradiction_overrides": [
{ "relation": "roadmap:status", "policy": "latest" }
]
}

This tells each side what relations the peer understands, preventing silent contradiction storms on semantically opaque relations.

Replication

Once active, the subscriber pulls facts from the publisher using short-lived Ed25519-signed peer tokens.

PeerToken {
iss: "stigmem://node-a.example.com",
sub: "stigmem://node-b.example.com",
exp: <iat + 3600s max>,
nonce: <UUID>,
scopes: ["public"]
}

Tokens have a 1-hour maximum lifetime and carry a nonce for replay protection. The receiving node verifies the signature, checks the nonce cache, and validates the scopes claim against the PeerDeclaration. The pull loop runs every 30 seconds by default, using an HLC-based cursor for incremental replication.

Scope enforcement

Scope boundaries are enforced per-hop with a two-factor check: fact.scope ∈ allowed_scopes(PeerDeclaration) ∩ token.scopes. A fact is only federated if both the declaration and the token permit it.

Fact scope
Federatable?
Conditions
local
Never
team
Never (unless explicit operator override)
company
Only if PeerDeclaration includes "company"
Re-federation to third nodes is blocked by default (Spec-05-Federation-Trust scope-propagation invariants).
public
Yes, to any active peer

Why this is non-obvious

Bilateral, not unilateral

Both nodes must independently register and verify. Node A's declaration to Node B doesn't grant Node B any access to Node A — B must also send a declaration, and A must verify it.

Company-scoped facts don't cascade

A company-scoped fact shared with Peer B is not automatically shareable by B with Peer C. The originating node's grant is non-transitive. Prevents a relay node with broader permissions from leaking internal knowledge.

Capability negotiation is required

Without it, a peer might replicate relations it doesn't understand (e.g., paperclip: lifecycle facts), leading to contradiction storms on semantically opaque data.

Pull-based default is deliberate

Push would be lower latency, but pull is operationally simpler: the subscriber controls cadence, backpressure is built in (429 → exponential backoff), and there's no need for the publisher to maintain push delivery state.

What it costs

Operational overhead

Each federation relationship requires a key exchange, mutual registration, and ongoing monitoring. For N nodes in a full mesh, that's N×(N-1) peer declarations.

Replication lag

Pull-based replication introduces a lag proportional to pull_interval_s (default 30 seconds). Relay nodes in multi-hop topologies can compound this lag.

Key rotation coordination

When a node rotates its federation keypair, it must keep the old key active for 24 hours. Peers re-fetch /.well-known/stigmem, but a race window exists during the transition.

No gossip protocol

Pairwise peer declarations, not gossip. Adding a new node to a 10-node network requires 10 separate handshakes. Linear, not logarithmic — acceptable for small-to-medium federations.

References