Live Substrate Event Streams¶
FranklinClient.subscribe(subject) is an async iterator yielding newly-appended substrate rows. Polling fallback works without NATS; full NATS bus path activates when ~/.gaiaftcl/nats.toml or GAIAFTCL_NATS_URL is configured.
Subjects¶
| NATS subject | Substrate table | When it fires |
|---|---|---|
gaiaftcl.substrate.franklin_heartbeat.sealed |
franklin_heartbeat_history |
Per heartbeat tick (60s default) |
gaiaftcl.qc020.franklin.window_filtering.sealed |
franklin_window_filtering |
Per substrate measurement window-close |
gaiaftcl.qc020.substrate.joint_variation_evidence.sealed |
qc020_joint_variation_evidence |
Per substrate measurement |
gaiaftcl.substrate.healing_action.sealed |
substrate_healing_actions |
Per heal-cycle action |
Wildcard matching follows NATS conventions (* for one token, > for tail).
Async iterator pattern¶
import asyncio
from gaiaftcl import FranklinClient
async def watch_heartbeats():
with FranklinClient.connect() as franklin:
async for event in franklin.subscribe(
"gaiaftcl.substrate.franklin_heartbeat.sealed"):
row = event["row"]
print(f"{row['tick_at_iso']} "
f"obs={row['observations_count']} "
f"sov_auth={row['commissions_franklin_authorized']}")
asyncio.run(watch_heartbeats())
Output (one line per Franklin tick, ~60s cadence):
2026-06-01T16:27:30Z obs=654 sov_auth=0
2026-06-01T16:28:30Z obs=655 sov_auth=0
2026-06-01T16:29:30Z obs=656 sov_auth=0
Watching substrate measurements¶
V178 fires per substrate measurement (~3–4 measurements per second under joint variation):
import asyncio
from gaiaftcl import FranklinClient
async def watch_v178():
with FranklinClient.connect() as franklin:
async for event in franklin.subscribe(
"gaiaftcl.qc020.substrate.joint_variation_evidence.sealed"):
row = event["row"]
if row["leading_zero_nibble_count"] > 1:
print(f"interesting: lz={row['leading_zero_nibble_count']} "
f"extranonce={row['cursor_extranonce']}")
asyncio.run(watch_v178())
Verifying every event as it arrives¶
import asyncio
from gaiaftcl import FranklinClient
from gaiaftcl.federation.cosignature import verify_signature_quintet
async def watch_and_verify():
with FranklinClient.connect() as franklin:
async for event in franklin.subscribe(
"gaiaftcl.substrate.franklin_heartbeat.sealed"):
row = event["row"]
ok = verify_signature_quintet(
row["canonical_witness"],
row["witness_hash_sha256"],
row["signature_quintet"],
)
mark = "✓" if ok else "✗"
print(f"{mark} {row['tick_at_iso']} {row['heartbeat_id']}")
asyncio.run(watch_and_verify())
Polling fallback¶
When NATS is unavailable, the client polls the substrate's SQLite at 1-second cadence and yields newly-appended rows by row_id ordering. Behavior is equivalent to the NATS path for read-only consumers.
Polling watermarks reset at subscribe() call time — only events newer than the call yield. To replay historical events, query the substrate read methods (heartbeat_history, research_telemetry, etc.) and process the returned list.
NATS bus configuration¶
~/.gaiaftcl/nats.toml:
Or GAIAFTCL_NATS_URL environment variable.
When NATS is configured, the client connects on first subscribe() call; subsequent subscriptions reuse the connection.
Substrate cadence is yours to consume. Every event verifies; every event is replay-bit-exact.
Federation-cosigned
This page's source is sealed in the GaiaFTCL federation manifest — page SHA-256 527762dad39e56ea…, manifest witness a090592e0609adc8…, signed 2026-06-02T18:58:22Z by cell gaiaftcl-mac-cell. Verify with gaiaftcl wiki sign --all and compare wiki-all-signatures.json.