The Sphere reference node is the local HTTP runtime for early Sphere Core experiments. It accepts verified event batches, accepts typed commands, stores events by chain, and replays each chain into a queryable graph projection.
It is intentionally a trusted-development service today. Do not expose it directly to untrusted networks. See Runtime Security Boundary before remote or multi-user deployment.
Install dependencies from the repository root:
corepack enable
corepack prepare pnpm@9.15.9 --activate
pnpm installStart the node with ephemeral memory storage:
pnpm --filter @sphere/node startThe default listener is 127.0.0.1:3080. Check health and runtime metadata:
curl -s http://127.0.0.1:3080/health
curl -s http://127.0.0.1:3080/node/infoExpected metadata shape:
{
"name": "sphere-reference-node",
"schemaVersion": "0.1.0",
"storage": "memory"
}Configure the runtime with environment variables:
SPHERE_NODE_HOST: listen host. Defaults to127.0.0.1.SPHERE_NODE_PORT: listen port. Defaults to3080. Must be an integer from0through65535.SPHERE_NODE_DB: optional SQLite database path. Omit or set to an empty value for memory storage.SPHERE_NODE_BEARER_TOKEN: optional trusted-development bearer token for/chains/*endpoints.
To bind beyond localhost for a trusted development deployment, set SPHERE_NODE_HOST explicitly (for example 0.0.0.0) and put the node behind the network and transport protections described in Runtime Security Boundary.
Run on localhost with SQLite persistence:
SPHERE_NODE_HOST=127.0.0.1 \
SPHERE_NODE_PORT=3080 \
SPHERE_NODE_DB=./sphere-events.sqlite \
pnpm --filter @sphere/node startRun with the development bearer-token gate enabled:
SPHERE_NODE_BEARER_TOKEN=TOKEN_VALUE pnpm --filter @sphere/node startThen include a matching authorization header on chain endpoints. The header value uses the HTTP bearer-token scheme with the token configured in SPHERE_NODE_BEARER_TOKEN.
curl -s http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/events \
-H 'authorization: Bearer TOKEN_VALUE'The endpoint examples below omit the header for readability. If the bearer-token gate is enabled, add the same authorization header to every /chains/* request.
/health and /node/info remain unauthenticated even when the bearer token is configured.
Memory storage is the default. It is useful for unit tests, demos, and short local sessions.
Behavior:
- creates an empty event store for each runtime process;
- loses all chain state when the process exits;
- reports
storage: "memory"from/node/info; - shares the same append and read invariants as SQLite through the event-store conformance suite.
SQLite storage is enabled by setting SPHERE_NODE_DB to a file path.
Behavior:
- creates tables and indexes on startup;
- persists complete event JSON by
chainIdandsequence; - verifies candidate event chains before insertion;
- inserts batches transactionally;
- rejects invalid, tampered, non-contiguous, duplicate-id, or mixed-chain batches without partial writes;
- replays graph projections from disk after restart;
- reports
storage: "sqlite"from/node/info.
The runtime closes Fastify and closeable event stores during SIGINT and SIGTERM shutdown.
With memory storage, restart means a new empty node.
With SQLite storage, restart means the new runtime instance reuses the same event file:
- The next accepted command continues from the stored chain tip.
- The generated event sequence is previous sequence plus one.
- The generated
previousHashis the previous event hash. - Graph queries replay stored events from disk.
- Forward and reverse edge lookups replay the same relationship state.
- Identity lookups replay by
platformandplatformId. - Ranged event reads still work after restart.
- Empty chains return empty projection diagnostics.
This pattern is covered by the node runtime SQLite restart smoke test in apps/node/test/runtime.test.ts.
The full endpoint contract, including request/response shapes and the error catalog, lives in Reference Node API.
Public runtime endpoints:
GET /health
GET /node/info
POST /chains/:chainId/events
POST /chains/:chainId/commands
GET /chains/:chainId/events
GET /chains/:chainId/graph/entities
GET /chains/:chainId/graph/entities/:entityId
GET /chains/:chainId/graph/edges/from/:entityId
GET /chains/:chainId/graph/edges/to/:entityId
GET /chains/:chainId/graph/identity/:platform/:platformId
GET /chains/:chainId/graph/diagnostics
All /chains/* endpoints require an authorization header containing the configured bearer token when the runtime is started with SPHERE_NODE_BEARER_TOKEN.
Use this when a client has already constructed hash-linked events:
curl -s http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/events \
-H 'content-type: application/json' \
-d '{ "events": [] }'Response shape:
{
"appended": 0,
"chainId": "019e42ae-9c00-7000-8000-000000000000",
"latestSequence": null
}Invalid request bodies return 400 with invalid_events_body. Events whose embedded chainId does not match the URL return 400 with chain_id_mismatch. Store verification failures return 400 with event_store_append_failed, plus the store error code and message.
Use this when the client wants the node to derive the next event sequence and previous hash from the stored chain tip:
curl -s http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/commands \
-H 'content-type: application/json' \
-d '{
"command": {
"id": "019e42ae-9c00-7000-8000-000000000100",
"actorId": "019e42ae-9c00-7000-8000-000000000001",
"action": "entity.update",
"resourceType": "entity",
"resourceId": "019e42ae-9c00-7000-8000-000000000002",
"payload": { "entity": { "name": "Ada Commanded" } },
"reason": null,
"createdAt": "2026-05-28T00:00:00.000Z",
"schemaVersion": "0.1.0"
}
}'Response shape:
{
"accepted": true,
"chainId": "019e42ae-9c00-7000-8000-000000000000",
"event": {
"sequence": 2,
"previousHash": "<previous event hash>"
}
}Invalid command bodies return 400 with invalid_command_body. Commands that are schema-valid but fail built-in policy return 400 with command_policy_failed and policy errors. Append races or store verification failures return 400 with event_store_append_failed.
See Command Policy for built-in and app-specific command boundaries. See Events and Actions for event payload and projection semantics.
Fetch all events for a chain:
curl -s http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/eventsFetch a ranged page:
curl -s 'http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/events?afterSequence=10&limit=100'Range semantics:
afterSequenceis exclusive.limitmust be a positive integer when supplied.- Ranged responses include
pageInfo. pageInfo.nextAfterSequenceis the cursor for the next request.- Empty ranged pages keep
nextAfterSequenceat the requestedafterSequence, ornullwhen onlylimitwas supplied. - Invalid query parameters return
400withinvalid_event_range.
The node replays the stored event chain on graph-query endpoints and returns current projection state.
List live entities:
curl -s http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/graph/entitiesRead one live entity:
curl -s http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/graph/entities/019e42ae-9c00-7000-8000-000000000002Read outgoing and incoming edges:
curl -s http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/graph/edges/from/019e42ae-9c00-7000-8000-000000000002
curl -s http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/graph/edges/to/019e42ae-9c00-7000-8000-000000000002Resolve an identity link:
curl -s http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/graph/identity/discord/1234567890Read projection diagnostics:
curl -s http://127.0.0.1:3080/chains/019e42ae-9c00-7000-8000-000000000000/graph/diagnosticsEntity and identity single-resource lookups return 404 with entity_not_found or identity_link_not_found when no projected record exists.
- Reference Node API
- Events and Actions
- Command Policy
- Authorization Roadmap
- Runtime Security Boundary
- Treat chain ids and event ids as protocol ids, not database row ids.
- Prefer command submission for normal clients. It lets the node own chain-tip sequencing.
- Prefer direct event submission for tests, fixtures, migration imports, and low-level protocol validation.
- Keep SQLite files out of git unless they are deliberate fixtures.
- Use deterministic ids and timestamps in tests and docs snippets.
- Re-run
pnpm --filter @sphere/node test,pnpm test, andpnpm typecheckafter changing runtime behavior.