Skip to main content
Version: v0.9.0a2
Integrator

Tutorial: Two-Org Federated Network

30 min ยท tutorialNode operator ยท ImplementerEnd-to-end

What you will build

A complete two-organisation Stigmem federation: two nodes, separate Ed25519 identities, mutual org manifests, a capability token granting Org B write access to a shared scope on Org A, a quarantine review cycle, and an end-to-end provenance chain verification.

Audience: node operators and protocol implementers setting up cross-org federation for the first time. Spec references: ยง6 Federation, ยง19 Federation Trust, ยง5.21โ€“5.25 wire format. Time: ~30 minutes on two local machines or two cloud VMs.

By the end you will have:

Two running nodes

That replicate facts between each other.

Capability token

Org A issuing a time-limited capability token to Org B.

Quarantine cycle

A fact written by Org B lands in Org A's quarantine garden (low source-trust) and is then admitted by an Org A moderator.

Verifiable provenance

A provenance chain on the admitted fact that you can verify with a single curl.

Prerequisitesโ€‹

Python 3.11+

With pip.

openssl

Standard on macOS/Linux.

curl + jq

jq for pretty-printing responses.

Two terminals

Called Terminal A and Terminal B.

pip install 'stigmem-node'
stigmem --version
# stigmem-node 1.1.x

Step 1 โ€” Bootstrap node identitiesโ€‹

Each node needs an Ed25519 keypair.

This keypair signs org manifests, capability tokens, and snapshots โ€” it is the cryptographic root of the node's identity.

Terminal A โ€” Org A

mkdir -p ~/stigmem-org-a && cd ~/stigmem-org-a

openssl genpkey -algorithm Ed25519 -out signing.key
openssl pkey -in signing.key -pubout -out signing.pub

export ORG_A_PUB=$(openssl pkey -in signing.key -pubout -outform DER \
| tail -c 32 | base64 | tr '+/' '-_' | tr -d '=')
export ORG_A_KEY_ID=$(openssl pkey -in signing.key -pubout -outform DER \
| tail -c 32 | sha256sum | awk '{print $1}')

Terminal B โ€” Org B (same commands in ~/stigmem-org-b, exporting ORG_B_PUB and ORG_B_KEY_ID).

Step 2 โ€” Start both nodesโ€‹

Terminal A:

cd ~/stigmem-org-a
STIGMEM_NODE_PRIVATE_KEY_FILE=signing.key \
STIGMEM_ENTITY_URI=stigmem://org-a.example \
STIGMEM_PORT=8100 \
STIGMEM_FEDERATION_ENABLED=true \
STIGMEM_TRUST_MODE=strict \
STIGMEM_ADMIN_KEY=secret-admin-a \
stigmem serve &

curl -s http://localhost:8100/.well-known/stigmem | jq .

Terminal B: same with STIGMEM_ENTITY_URI=stigmem://org-b.example, STIGMEM_PORT=8200, STIGMEM_ADMIN_KEY=secret-admin-b.

Step 3 โ€” Publish org manifestsโ€‹

NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
EXPIRES=$(date -u -d '+1 year' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
|| date -u -v+1y +%Y-%m-%dT%H:%M:%SZ)

# Terminal A โ€” publish Org A manifest
curl -s -X PUT http://localhost:8100/v1/federation/manifest \
-H "Authorization: Bearer secret-admin-a" \
-H "Content-Type: application/json" \
-d "{
\"manifest_version\": 1,
\"entity_uri\": \"stigmem://org-a.example\",
\"public_key\": \"$ORG_A_PUB\",
\"key_id\": \"$ORG_A_KEY_ID\",
\"entities\": [\"stigmem://org-a.example\", \"stigmem://org-a.example/agent/assistant\"],
\"rotation_events\": [],
\"issued_at\": \"$NOW\",
\"expires_at\": \"$EXPIRES\",
\"signature\": \"__self_sign__\"
}" | jq .

__self_sign__ is a reference-node shortcut.

The reference node accepts the literal __self_sign__ for the signature field when STIGMEM_NODE_PRIVATE_KEY_FILE is configured. For production deployments, compute the JCS-canonical signature externally.

Repeat in Terminal B with ORG_B_PUB / ORG_B_KEY_ID.

Register as federation peersโ€‹

# Terminal A: tell Org A about Org B
curl -s -X POST http://localhost:8100/v1/federation/peers \
-H "Authorization: Bearer secret-admin-a" \
-H "Content-Type: application/json" \
-d '{
"peer_url": "http://localhost:8200",
"peer_entity_uri": "stigmem://org-b.example"
}' | jq .

# Terminal B: tell Org B about Org A
curl -s -X POST http://localhost:8200/v1/federation/peers \
-H "Authorization: Bearer secret-admin-b" \
-H "Content-Type: application/json" \
-d '{
"peer_url": "http://localhost:8100",
"peer_entity_uri": "stigmem://org-a.example"
}' | jq .

Step 4 โ€” Create a quarantine garden on Org Aโ€‹

With STIGMEM_TRUST_MODE=strict, Org A will quarantine facts from Org B until trust is established.

Create a quarantine garden first so quarantined facts have a home.

# Terminal A โ€” create the quarantine garden
QUARANTINE_ID=$(curl -s -X POST http://localhost:8100/v1/gardens \
-H "Authorization: Bearer secret-admin-a" \
-H "Content-Type: application/json" \
-d '{
"slug": "inbound-quarantine",
"name": "Inbound quarantine",
"scope": "company",
"quarantine": true
}' | jq -r '.id')

# Designate it as the node-level quarantine sink
curl -s -X PATCH http://localhost:8100/v1/admin/config \
-H "Authorization: Bearer secret-admin-a" \
-H "Content-Type: application/json" \
-d "{\"quarantine_garden_id\": \"$QUARANTINE_ID\"}" | jq .

Step 5 โ€” Issue a capability token from Org A to Org Bโ€‹

TOKEN_RESPONSE=$(curl -s -X POST http://localhost:8100/v1/federation/capability-tokens \
-H "Authorization: Bearer secret-admin-a" \
-H "Content-Type: application/json" \
-d '{
"issuer": "stigmem://org-a.example",
"subject": "stigmem://org-b.example/agent/bot",
"verb": "write",
"object": "stigmem://org-a.example/scope/shared",
"ttl_seconds": 86400
}')

TOKEN_ID=$(echo "$TOKEN_RESPONSE" | jq -r '.token_id')
TOKEN_JSON=$(echo "$TOKEN_RESPONSE" | jq -r '.token_json')

Verify the token (from Terminal B's perspective):

curl -s -X POST http://localhost:8100/v1/federation/capability-tokens/verify \
-H "Content-Type: application/json" \
-d "{\"token_json\": $TOKEN_JSON}" | jq .
# โ†’ { "valid": true }

The six-step verification (ยง19.3.3) checks token version, expiry, issuer manifest, signature, subject delegation, and revocation status.

Step 6 โ€” Write a fact from Org B; observe quarantineโ€‹

# Terminal B โ€” write a fact to Org A using the capability token
curl -s -X POST http://localhost:8100/v1/facts \
-H "Authorization: Bearer $TOKEN_JSON" \
-H "Content-Type: application/json" \
-d '{
"entity": "stigmem://org-a.example/project/atlas",
"relation": "memory:status",
"value": { "type": "string", "v": "active" },
"source": "stigmem://org-b.example/agent/bot",
"confidence": 0.85,
"scope": "company"
}' | jq .

Expected response:

{
"fact_id": "<uuid>",
"quarantine_status": "pending",
"quarantine_garden_id": "<quarantine-garden-uuid>",
"quarantine_reason": "auto-quarantine: source_trust below threshold (t=0.15)"
}

The fact is stored but isolated.

It will not appear in normal GET /v1/facts queries until a moderator admits it.

Confirm the fact is in quarantine:

# Terminal A โ€” list pending quarantine items
curl -s "http://localhost:8100/v1/quarantine?quarantine_status=pending" \
-H "Authorization: Bearer secret-admin-a" | jq .

Step 7 โ€” Admit the factโ€‹

# Terminal A โ€” promote the quarantined fact
FACT_ID="<fact_id from the write response>"

curl -s -X POST "http://localhost:8100/v1/gardens/$QUARANTINE_ID/promote" \
-H "Authorization: Bearer secret-admin-a" \
-H "Content-Type: application/json" \
-d "{
\"fact_id\": \"$FACT_ID\",
\"target_garden_id\": null,
\"reason\": \"Org B status update verified; source has valid capability token.\"
}" | jq .

target_garden_id: null promotes the fact to the main fact store (outside any garden boundary). Pass a garden UUID to promote into a specific garden instead.

To reject the fact (sets confidence = 0, retains for audit):

curl -s -X POST "http://localhost:8100/v1/gardens/$QUARANTINE_ID/reject" \
-H "Authorization: Bearer secret-admin-a" \
-H "Content-Type: application/json" \
-d "{\"fact_id\": \"$FACT_ID\", \"reason\": \"Source unverified.\"}" | jq .

Step 8 โ€” Verify the provenance chainโ€‹

# Terminal A โ€” retrieve the admitted fact and inspect provenance
curl -s "http://localhost:8100/v1/facts/$FACT_ID" \
-H "Authorization: Bearer secret-admin-a" | jq '{
fact_id: .id,
entity: .entity,
relation: .relation,
value: .value,
source: .source,
source_trust: .source_trust,
derived_from: .derived_from,
attestation_chain: .attestation_chain,
quarantine_status: .quarantine_status
}'

Expected (abbreviated):

{
"fact_id": "<uuid>",
"entity": "stigmem://org-a.example/project/atlas",
"relation": "memory:status",
"value": { "type": "string", "v": "active" },
"source": "stigmem://org-b.example/agent/bot",
"source_trust": 0.15,
"derived_from": [],
"attestation_chain": ["<base64url Ed25519 sig by org-b.example>"],
"quarantine_status": "promoted"
}

The attestation_chain contains Org B's Ed25519 signature over the fact body.

The source_trust snapshot records the trust score at write time; effective confidence at recall time uses the live recomputed score.

Step 9 โ€” Revoke the capability tokenโ€‹

# Terminal A โ€” revoke the token
curl -s -X POST "http://localhost:8100/v1/federation/capability-tokens/$TOKEN_ID/revoke" \
-H "Authorization: Bearer secret-admin-a" \
-H "Content-Type: application/json" \
-d '{"reason": "Tutorial complete; token no longer needed."}' | jq .

After revocation, a fresh verify call returns {"valid": false, "reason": "revoked"}.

What you builtโ€‹

Capability
Mechanism
How it works
Distinct node identities
Ed25519 keypair
Manifests anchor the key to an org URI.
Org manifests + transparency log
PUT /v1/federation/manifest
Inclusion proof returned and stored.
Peer trust
/v1/federation/peers
Federation pulls scoped facts using signed peer tokens.
Capability token
24h write grant
Org A issued; Org B presented on every write.
Quarantine path
source_trust < threshold
Org B's first write landed in the quarantine garden.
Moderator admission
/gardens/{id}/promote
Org A's admin promoted the quarantined fact.
Provenance chain
Ed25519 attestation
Promoted fact carries Org B's signature; verifiable any time.
Token revocation
/capability-tokens/{id}/revoke
Subsequent verify calls return valid: false.

Next stepsโ€‹

4-node topology

4-Node Federation guide โ€” N-node backpressure, relay configuration, scope propagation invariants (ยง6.7โ€“6.8).

Key rotation

Rotate Org A's signing key without federation downtime โ€” see the Federation Trust guide.

Trust mode tuning

Lower STIGMEM_QUARANTINE_TRUST_THRESHOLD to admit more facts automatically from known peers.

Encrypted storage

Add STIGMEM_AT_REST_ENCRYPTION=on before going to production.

Signed snapshots

Back up both nodes โ€” see the Backends guide.