aeo-graph-explorer
HTTP graph-query service over AEO Protocol crawls. Ingests aeo-crawler JSON-Lines, builds an in-memory typed graph, exposes neighbours / shortest-path / find-by-claim. Layer 5 of the AEO Reference Stack — the piece that turns a crawl into a queryable view.
1. SDKs aeo-sdk-python / -typescript / -rust / -go / -swift
2. CLI aeo-cli
3. Crawler aeo-crawler produces JSONL
4. Validator aeo-validator-service always-on validation + drift
5. Explorer aeo-graph-explorer-rs <- this repo
Why
aeo-crawler BFS-walks the AEO graph from a seed URL and dumps one node per JSON line. That's a perfect pipeline output and a frustrating query interface. This service:
- Ingests the JSONL once and indexes it.
- Exposes the things crawl consumers actually want — list every entity, fetch one entity's full doc, expand a node's neighbourhood, find the shortest citation chain between two entities, scan claims by predicate / value.
- Rebuilds atomically — a new
POST /ingestdoesn't block ongoing reads.
No database. A typical AEO crawl is a few thousand nodes — the right tool for "give me a queryable view of a recent crawl" is an in-memory graph, not Postgres.
Endpoints
| Method | Path | What it does |
|---|---|---|
| GET | / |
Service info + endpoint list. |
| GET | /healthz |
Liveness probe. |
| GET | /nodes |
List every entity in the graph (summary view). |
| GET | /nodes/{id} |
Fetch one entity's full AEO body. |
| GET | /nodes/{id}/neighbors |
Outbound + inbound neighbours, with edge kinds. |
| GET | /shortest-path?from=&to= |
A* search; returns { found, length, hops[] }. |
| GET | /find-by-claim?predicate=&value= |
Linear claim scan; at least one of the two parameters is required. |
| POST | /ingest |
Load JSONL and rebuild the graph atomically. Body is the raw JSONL — Content-Type: application/json is fine. |
| GET | /stats |
{ nodes, edges }. |
URL-encoded entity IDs are supported (https%3A%2F%2Facme.example%2F%23org).
Run it
Set PORT / HOST env vars to override.
Quick start
# Stand the server up.
# Ingest a crawl (the bundled example has three entities).
# What's in the graph?
# -> {"nodes": 3, "edges": 2}
# Walk the neighbourhood of AcmeTutor.
|
# Anybody else in the AI-tutoring industry?
|
Edge inference
When POST /ingest runs, the service walks each node's body and wires edges based on two well-known fields:
| Body field | Edge kind | Direction |
|---|---|---|
peers[].id |
DeclaresPeer |
from → to |
authority.primary_sources[] (URL matches another node id) |
CitesAuthority |
from → to |
Edges to nodes that don't appear in the loaded crawl are dropped silently. This keeps the graph self-contained — you can always re-ingest later with a bigger crawl to fill in the gaps.
Composes with
- aeo-crawler — produces the JSONL this service ingests.
- aeo-validator-service — call
POST /watchesfor every entity returned by/nodesto set up drift tracking across the whole graph. - incident-correlation-rs — when an incident lands, ask
/find-by-claimfor entities declaring the affected predicate, then seed the correlator with the result.
Bench
Bundled bench ingests a synthetic 2000-node chain so you can spot regressions in the parse + wire-edges pass.
Tests
CI matrix: stable, beta, 1.86.0 (MSRV). End-to-end HTTP tests run via tower::ServiceExt — no real network.
License
MIT. See LICENSE.