Skip to content

Substratum personal triangle manifest (v1)

This document defines substratum-triangle.json, the versioned JSON artifact for one device in the Personal Device Triangle (libp2p identity, bootstrap multiaddrs, pinning peer IDs, swarm and JWT root secrets). It replaces ad-hoc triangle .env files for installer and gateway personal layouts.

File locations

ContextPath
macOS (installer local)~/.substratum/substratum-triangle.json
Windows (installer local)%ProgramData%\Substratum\substratum-triangle.json
Export ZIP (per device){sanitized_display_name}.substratum-triangle.json
Gateway discoveryRelease builds: ./substratum-triangle.json in the process cwd, optional path from SUBSTRATUM_TRIANGLE_CONFIG, then (if those are absent) installer default locations when a file exists: macOS ~/.substratum/substratum-triangle.json, Windows %ProgramData%\Substratum\substratum-triangle.json. Debug builds ignore manifest files for the triangle slice (env only).

Gateway loading

The gateway is a two-layer config:

  1. Operational (env + optional release JSON): DATABASE_URL, HOST, PORT, PUBLIC_BASE_URL, CORS_ALLOWED_ORIGINS, storage (S3_* or FlatFS), RUST_LOG, etc. Before reading operational env, bootstrap_dotenv attempts an optional cwd .env (non-fatal if missing). Release may also read substratum-gateway.json and merge with env (env wins). Compose/systemd still inject operational env as today.

  2. Triangle slice (env or manifest by profile): libp2p identity, bootstrap multiaddrs, pinning targets, swarm/JWT secrets. TriangleConfigFactory chooses: release resolves a manifest path (SUBSTRATUM_TRIANGLE_CONFIG, cwd substratum-triangle.json, then installer defaults on macOS/Windows when present), then env; debug uses env only for the triangle so a stray manifest does not override local dev.

substratum-triangle.json does not include DB or HTTP bind settings — use substratum-gateway.json (release) or env for those. See substratum-gateway-application-v1.md, apps/gateway/AGENTS.md, and Config::load.

JSON shape (schema_version 1)

Top-level object:

FieldTypeRequiredDescription
schema_versionintegeryesMust be 1 for this revision.
kindstringyesMust be substratum.personal_triangle.device_v1.
deviceobjectnoMetadata for operators and UIs.
device.display_namestringnoHuman-readable device label.
device.peer_idstringnolibp2p PeerId string for this device.
device.storageobjectnoPer-device storage policy (installer Step 3; runtime enforcement is a follow-up).
device.storage.data_rootstringyes (when device.storage is present)Absolute or conventional path for node data on this device.
device.storage.max_bytesintegernoHard cap in bytes (decimal JSON integer; keep within host JSON safe integer range).
device.storage.warn_bytesintegernoSoft warning threshold in bytes; when set, should be ≤ max_bytes if both are present.
libp2p_identity_key_b64stringyesBase64-encoded protobuf libp2p keypair (same encoding as env LIBP2P_IDENTITY_KEY).
bootstrap_nodesstring[]yesMultiaddr strings (e.g. /ip4/…/tcp/4001/p2p/…).
pinning_targetsstring[]yeslibp2p PeerId strings for replication targets.
swarm_master_secret_hexstringyesHex-encoded secret bytes (installer uses 32 random bytes → 64 hex chars).
jwt_secret_hexstringyesHex-encoded secret bytes (same pattern as swarm).

Encoding notes

  • *_hex fields are parsed as hex; the gateway normalizes them to lowercase hex strings internally.
  • Treat the file like a secret: restrict permissions on Unix (e.g. 0600) where the installer writes it.

Example

json
{
  "schema_version": 1,
  "kind": "substratum.personal_triangle.device_v1",
  "device": {
    "display_name": "My MacBook",
    "peer_id": "12D3KooW…",
    "storage": {
      "data_root": "/var/lib/substratum",
      "max_bytes": 34359738368,
      "warn_bytes": 25769803776
    }
  },
  "libp2p_identity_key_b64": "…",
  "bootstrap_nodes": ["/ip4/100.64.0.2/tcp/4001/p2p/12D3KooW…"],
  "pinning_targets": ["12D3KooW…"],
  "swarm_master_secret_hex": "010203…",
  "jwt_secret_hex": "aabbcc…"
}