Railway is the preferred hosted path for ZERO paper mode. It gives operators a publicly reachable runtime without introducing ZERO-hosted custody or a private control plane.
For marketplace publishing, use railway-template.md as the template overview, variable map, volume checklist, and deploy-button source of truth.
If the operator later connects the Railway Runtime to the ZERO app/control plane, use the Vibe Deploy proof chain. That ceremony starts with a claim token and first heartbeat; it is how ZERO proves ownership and liveness before paper acceptance, authority approval, live lease, or Builder Code state can be trusted.
This deployment is still paper-only:
- no private keys;
- no signing;
- no order placement;
POST /executerecords simulated fills only;X-Zero-Mode: liveandPOST /live/*fail closed because no live executor is configured;- live Hyperliquid mids are read-only when enabled.
railway.tomlselects the Dockerfile build,/healthhealthcheck, and restart policy./app/scripts/railway_start.shlistens on Railway's injectedPORT.- The default journal path is
/data/decisions.jsonl. ZERO_HYPERLIQUID_LIVE_PRICES=trueis the default so paper mode uses live read-only Hyperliquid mids.
- Create a Railway project from this GitHub repository.
- Add a persistent volume mounted at
/data. - Confirm the service variables:
ZERO_JOURNAL_PATH=/data/decisions.jsonl
ZERO_HYPERLIQUID_LIVE_PRICES=true
Railway injects PORT; do not hardcode it. The container binds
0.0.0.0:${PORT} and exposes /health.
Optional ZERO Intelligence API reference variables:
ZERO_INTELLIGENCE_API_TOKEN=...
ZERO_INTELLIGENCE_API_PLAN=team_fund
ZERO_INTELLIGENCE_API_ACCOUNT_ID=acct_...
ZERO_INTELLIGENCE_WEBHOOK_SIGNING_KEY=...
ZERO_INTELLIGENCE_STORE_PATH=/data/zero/intelligence.jsonl
Do not reuse production tokens in public demos. These variables only exercise the hosted-compatible contract surface on your own Railway service. The store path enables append-only aggregate snapshot and usage persistence; it must be mounted on a durable Railway volume if you want history to survive restarts.
Do not add Hyperliquid private keys or Builder Code fee settings to the public Railway template. Authority and revenue approvals belong to the later receipt-backed ceremony, not the default paper Runtime.
railway link
railway upAfter deployment:
curl -fsS "$ZERO_RAILWAY_URL/health"
curl -fsS "$ZERO_RAILWAY_URL/market/quote?symbol=BTC"
scripts/railway_doctor.py "$ZERO_RAILWAY_URL"
scripts/deployment_evidence.sh "$ZERO_RAILWAY_URL"The quote response should show:
{
"symbol": "BTC",
"source": "hyperliquid:allMids",
"live": true
}zero --api "$ZERO_RAILWAY_URL" doctor
zero --api "$ZERO_RAILWAY_URL" run quote BTC
zero --api "$ZERO_RAILWAY_URL" run statusRisk-increasing commands remain locally gated by the CLI. The public Railway runtime still treats execution as paper simulation.
Use the Railway doctor before sharing a deployment, after every deploy, and during incident response:
scripts/railway_doctor.py "$ZERO_RAILWAY_URL"
scripts/railway_doctor.py "$ZERO_RAILWAY_URL" --json
scripts/railway_doctor.py "$ZERO_RAILWAY_URL" \
--token "$ZERO_INTELLIGENCE_API_TOKEN" \
--expect-paperThe doctor checks /health, /v2/status, /metrics, /market/quote,
/immune, /live/preflight, /live/cockpit, public ZERO Network packets,
delayed ZERO Intelligence packets, hosted-compatible /v1/intelligence/*
headers, and protected-scope fail-closed behavior. With a token, it also
verifies that protected history scope accepts the configured bearer token
without leaking the token, trace IDs, private keys, or raw runtime data.
Warnings are allowed for local ephemeral test services. A public Railway demo
should use a mounted /data volume so durable_journal reports ok.
Before sharing a public demo, promoting a Railway deploy, or closing an incident, capture a redacted evidence folder:
scripts/deployment_evidence.sh "$ZERO_RAILWAY_URL"
scripts/deployment_evidence.sh "$ZERO_RAILWAY_URL" \
--token "$ZERO_INTELLIGENCE_API_TOKEN" \
--railway-logs \
--signing-key "$ZERO_DEPLOYMENT_EVIDENCE_SIGNING_KEY" \
--signer "$ZERO_DEPLOYMENT_OWNER"By default the collector writes to
artifacts/deployment-evidence/<timestamp>/. The folder contains:
doctor.json;- redacted
/health,/v2/status,/metrics,/audit/export,/immune,/live/preflight,/live/cockpit,/live/certification,/deployment/claim,/deployment/heartbeat,/network/profile,/intelligence/snapshot, and/v1/intelligence/snapshotspackets; - optional
railway_logs.txtwhen the Railway CLI is installed, linked, and authenticated; manifest.jsonwith git context, doctor summary, file inventory, and redaction policy;SHA256SUMSfor the captured files;- optional
EVIDENCE_SIGNATURE.jsonwhen an evidence signing key is supplied.
The collector redacts supplied tokens, authorization values, private/signing key patterns, trace IDs, and smoke idempotency keys. Treat the output as operator-review evidence, not as a substitute for private journal custody.
Verify the folder before sharing it:
scripts/deployment_evidence_verify.py artifacts/deployment-evidence/<timestamp>
scripts/deployment_evidence_verify.py artifacts/deployment-evidence/<timestamp> \
--signing-key "$ZERO_DEPLOYMENT_EVIDENCE_SIGNING_KEY" \
--require-signatureThe signature is an operator-owned HMAC-SHA256 promotion record over
manifest.json and SHA256SUMS. It proves the captured pack was not modified
after local signing; it is not a public identity system or a replacement for a
future external signing service.
For a public identity proof, bind the deployment claim and heartbeat to an operator public key:
curl -fsS "$ZERO_RAILWAY_URL/audit/export?limit=1" > audit.json
python3 - <<'PY'
import json
with open("audit.json", encoding="utf-8") as fh:
audit = json.load(fh)
with open("deployment_claim.json", "w", encoding="utf-8") as fh:
json.dump(audit["deployment_claim"], fh)
with open("deployment_heartbeat.json", "w", encoding="utf-8") as fh:
json.dump(audit["deployment_heartbeat"], fh)
PY
scripts/deployment_identity_evidence.py create \
deployment_claim.json \
deployment_heartbeat.json \
--private-key operator-signing-key.pem \
--public-key operator-signing-public.pem \
--signer "$ZERO_DEPLOYMENT_OWNER"
scripts/deployment_identity_evidence.py verify \
artifacts/deployment-identity/<timestamp> \
--require-signatureBefore a public demo, keep one evidence pack for the current deployment and one for the previous known-good deployment. Rehearse the rollback plan without mutating Railway:
scripts/deployment_rollback_rehearsal.py \
artifacts/deployment-evidence/current \
--previous-bundle artifacts/deployment-evidence/previous \
--signing-key "$ZERO_DEPLOYMENT_EVIDENCE_SIGNING_KEY" \
--require-signatureThe rehearsal writes rollback_rehearsal.json, SHA256SUMS, and optional
ROLLBACK_REHEARSAL_SIGNATURE.json. It verifies both evidence packs, checks
that the public paper service remains live_mode=refused, confirms the
rollback target is health-checkable, and records the exact post-rollback proof
steps. The script is intentionally plan-only; it never calls Railway and never
changes a deployment.
The paper journal is append-only JSONL. With the volume mounted at /data, a
restart replays prior paper decisions before the API serves traffic. Replayed
state restores decisions, fills, open positions, rejections, and idempotency
keys. Inspect the replay status through:
curl -fsS "$ZERO_RAILWAY_URL/health"
zero --api "$ZERO_RAILWAY_URL" run statusThe journal itself remains available through:
curl -fsS "$ZERO_RAILWAY_URL/journal?limit=50"Audit and runtime counters are available without secrets:
curl -fsS "$ZERO_RAILWAY_URL/metrics"
curl -fsS "$ZERO_RAILWAY_URL/audit/export?limit=100"
curl -fsS "$ZERO_RAILWAY_URL/network/profile"
curl -fsS "$ZERO_RAILWAY_URL/network/leaderboard"
curl -fsS "$ZERO_RAILWAY_URL/intelligence/snapshot"
curl -fsS "$ZERO_RAILWAY_URL/intelligence/catalog"
curl -fsS "$ZERO_RAILWAY_URL/intelligence/commercial"
curl -fsS "$ZERO_RAILWAY_URL/intelligence/model-gateway"
curl -fsS "$ZERO_RAILWAY_URL/intelligence/model-gateway/health"
curl -fsS "$ZERO_RAILWAY_URL/intelligence/model-gateway/audit"
curl -fsS "$ZERO_RAILWAY_URL/v1/intelligence/snapshots"
curl -fsS "$ZERO_RAILWAY_URL/live/preflight"
curl -fsS "$ZERO_RAILWAY_URL/live/cockpit"Every HTTP response carries X-Zero-Trace-Id. Paper decisions created through
POST /execute write that trace ID into the journal and audit export.
ZERO Network profile and leaderboard endpoints are public-safe aggregate proof surfaces. They exclude raw decisions, trace IDs, idempotency keys, wallet addresses, exchange order IDs, private notes, strategy source labels, and per-trade symbols. Publishing remains opt-in and local unless you configure a publish path.
ZERO Intelligence snapshot, catalog, and commercial-contract endpoints are also
public-safe aggregate contracts. The snapshot is delayed public intelligence.
The catalog points to /intelligence/commercial, which describes the
growth-mode free hosted API boundary for realtime access plus the future
commercial boundary for higher limits, history, cohorts, webhooks, exports,
redistribution, usage events, rate limits, and reliability commitments.
The /v1/intelligence/* reference endpoints are hosted-compatible:
curl -fsS "$ZERO_RAILWAY_URL/v1/intelligence/snapshots"
curl -fsS -H "Authorization: Bearer $ZERO_INTELLIGENCE_API_TOKEN" \
"$ZERO_RAILWAY_URL/v1/intelligence/history?limit=10"
curl -fsS -H "Authorization: Bearer $ZERO_INTELLIGENCE_API_TOKEN" \
-H "content-type: application/json" \
-d '{"url":"https://example.com/zero","event_types":["snapshot.accepted"]}' \
"$ZERO_RAILWAY_URL/v1/intelligence/webhooks"Delayed snapshots work without a token. Realtime, history, cohort, benchmark,
webhook, and export scopes require a bearer token when
ZERO_INTELLIGENCE_API_TOKEN is set. Responses include x-zero-ratelimit-*
headers. Webhook responses include an HMAC-SHA256 signature fixture but never
return signing key material.
/live/preflight is intentionally non-secret. It reports whether a
self-custodial Hyperliquid live mode would be allowed to start, but this Railway
paper deployment should keep ready=false and live_mode=refused. POST /live/kill, /live/pause, and /live/flatten should return
ok=false, reason="live executor not configured" on public paper services; do
not put private exchange keys into the public paper service.
/live/certification is also safe on Railway. It runs a dry-run fake-exchange
harness and should report mode=dry_run, passed=true, and
summary.orders_placed_live=0.
/live/cockpit is safe on Railway. It should report ready=false,
risk_increasing_allowed=false, and a next_action that explains which local
live-control prerequisite is missing.
/immune is safe on Railway too. Public paper services should normally report
risk_increasing_allowed=false because local live custody is absent; that is a
correct refusal state for paper deployments.
If a deployment starts without a volume, the API still runs, but the journal is ephemeral and will be lost on restart. Do not use an ephemeral journal for operator demos or public behavior verification.
- If
/healthfails, check that the service listens on Railway'sPORT. - If
/market/quotefails in live mode, Hyperliquid public market data is unavailable or the requested symbol is not present inallMids. - If journal history disappears after redeploy, the
/datavolume is missing or mounted to the wrong path. - If zero-downtime deploys show brief downtime, check whether the service has an attached volume. Railway does not run two active deployments against the same mounted volume.
For incident handling, use incident-runbooks.md. The
Railway-specific P1 runbook requires /health, /v2/status, /metrics,
/immune, /network/profile, and /intelligence/snapshot to recover before
promotion.