Skip to content
EXTENDED DOCUMENTATION

Production Rust service topology

This document is the deployment authority map for the Rust migration. It is deliberately stricter than the parity inventory: keeping a Node implementation for comparison does not make it a production authority.

Authority matrix

PlaneRuntime artifactDeployment statusFail-closed boundary
Nitro frontier / host
text
enclave_host
from
text
deploy/nitro/Dockerfile.enclave
Rust authoritative measured EIFUnimplemented request types return an explicit refusal; there is no Node fallback. NeoX
text
/feed/sign
uses the measured
text
SIGNER_ROLE_NEOX_UPDATER_KEY_ID
for EIP-1559 submission, while NeoX fulfillment proofs use the separate measured
text
SIGNER_ROLE_NEOX_VERIFIER_KEY_ID
for semantic EIP-191 signing.
Custody signer
text
signer_service
from
text
deploy/nitro/Dockerfile.signer
Rust authoritative measured EIFBoots sealed, accepts only the attested provisioning whitelist, and returns 503 until provisioning succeeds.
Relayer
text
neo-os-relayer
from
text
deploy/nitro/Dockerfile.relayer
Rust authoritative production serviceNeo N3 and NeoX request/feed lanes require custody signing, successful HALT/receipt confirmation, and durable success before advancing state.
text
requests_only
,
text
feed_only
, and
text
combined
are supported. The measured host can execute one already-claimed
text
/automation/execute
; the durable relayer scheduler remains unimplemented and production configuration keeps that lane disabled.
Historical worker / relayer
text
workers/nitro-worker
,
text
workers/morpheus-relayer
Node compatibility onlyNot published by the production image workflow, not assigned
text
latest
, and not boot-enabled. Compose requires explicitly pinned
text
MORPHEUS_COMPAT_*
images; systemd requires
text
ENABLE_NODE_COMPAT
.
Historical chain writers
text
deploy/feed-pusher/*
(including
text
*-testnet*
),
text
deploy/evm/neox-fulfiller.mjs
Node compatibility onlyEvery discovered writer/top-up/fulfiller unit — mainnet, testnet, or a future suffix — requires its lane
text
ENABLE_NODE_*_COMPAT
marker, has no
text
[Install]
, and
text
Conflicts=
/
text
Before=
both Rust relayer units. The NeoX fulfiller additionally exits 78 whenever
text
NODE_ENV=production
. A TestNet Node pusher is the same class of artifact as the mainnet Node pusher: it is not a production writer.
Edge feed / automationCloudflare control planeNon-authoritative ingress
text
/feeds/tick
returns 410 because Rust owns feed publication;
text
/automation/execute
returns 503 without creating a workflow while Rust automation remains fail-closed. Legacy bindings/classes are recovery metadata, not fallback authority.

The frozen JavaScript parity baseline stays in the repository. It is test input and rollback evidence, not an executable fallback inside a Rust service.

Published images

text
.github/workflows/publish-images.yml
builds only these service Dockerfiles:

  • text
    deploy/nitro/Dockerfile.enclave
  • text
    deploy/nitro/Dockerfile.signer
  • text
    deploy/nitro/Dockerfile.relayer

The workflow emits immutable

text
sha-<full-commit>
tags only. It emits no branch tag,
text
latest
, legacy alias, Node worker image, or Node relayer image. Nitro operators still rebuild and re-measure the host and signer EIFs with the exact network/custody trust anchors; a registry rootfs tag is not a PCR approval.

The frontier measurement binds all six custody identities:

  • text
    SIGNER_ROLE_ORACLE_VERIFIER_KEY_ID
  • text
    SIGNER_ROLE_UPDATER_KEY_ID
  • text
    SIGNER_ROLE_RELAYER_KEY_ID
  • text
    SIGNER_ROLE_WORKER_KEY_ID
  • text
    SIGNER_ROLE_NEOX_UPDATER_KEY_ID
  • text
    SIGNER_ROLE_NEOX_VERIFIER_KEY_ID

The two NeoX identities are deliberately non-interchangeable: the updater owns EIP-1559 transaction submission, while the verifier owns semantic EIP-191 fulfillment proofs. The build refuses to measure an image if the ids are equal.

Changing any identity requires rebuilding the EIF, recording the new measurement manifest, and updating the KMS/attestation policy as one release.

Host layout

The production host runs the measured frontier and custody EIFs through:

  • text
    morpheus-nitro-signer.service
  • text
    morpheus-nitro-custody.service
  • text
    morpheus-nitro-provision.service
    and its credential-rotation timer

The launch/provision/build scripts use Bash,

text
curl
,
text
jq
,
text
nitro-cli
, and
text
socat
; they do not require Node. Provisioning projects only allowlisted fields, keeps credentials out of command-line arguments, and leaves existing in-enclave credentials untouched when IMDSv2 rotation fails.

The Rust services still consume

text
NODE_ENV=production
as a legacy-named deployment-mode contract: core, compute, and host use it to disable ephemeral keys and unsafe overrides. Its presence in a Rust unit or image is therefore a fail-closed safety input, not a Node runtime dependency. A future migration may replace the name with a Rust-native deployment-mode key only after every consumer has moved atomically.

text
morpheus-nitro-egress.service
is a constrained host networking sidecar. Its CONNECT allowlist is not a compute, custody, signing, transaction-building, or broadcast authority. Replacing that helper is independent of the service authority cutover and must not broaden the allowlist.

The Rust relayer is installed at

text
/opt/neoos/services/bin/neo-os-relayer
. Before enabling its production unit:

  1. Copy
    text
    deploy/nitro/neo-os-relayer.env.example
    to
    text
    /opt/morpheus/nitro/neo-os-relayer.env
    and replace every placeholder.
  2. Run
    text
    neo-os-relayer config:validate
    as the service user.
  3. Keep
    text
    MORPHEUS_AUTOMATION_ENABLED=false
    ;
    text
    config:validate
    and
    text
    ensure_runtime_ready
    refuse the service if the unavailable automation commit protocol is requested.
    text
    NODE_ENV=production
    cannot opt in. Non-production may set
    text
    MORPHEUS_ALLOW_UNAVAILABLE_AUTOMATION=true
    only as an explicit development allow.
  4. Enable exactly one of
    text
    morpheus-relayer.service
    or
    text
    morpheus-relayer-nitro.service
    ; the units conflict to prevent duplicate processing.
  5. Keep every Node feed writer stopped — including
    text
    morpheus-feed-pusher-testnet*
    — and remove all
    text
    ENABLE_NODE_*_COMPAT
    markers. Those units have no
    text
    [Install]
    target, so they cannot be
    text
    systemctl enable --now
    'd. They
    text
    Conflicts=
    /
    text
    Before=
    both Rust relayer units as a second line of defense. Rust is the only production writer on mainnet and TestNet.

The relayer transport currently sends a bearer token to custody. Configure a dedicated bearer caller for

text
SIGNER_SERVICE_TOKEN
; do not substitute a
text
workload-ed25519
caller, which intentionally has no token and is not yet a relayer authentication transport. Keep that caller distinct from
text
MORPHEUS_RUNTIME_TOKEN
, which authenticates the relayer to the frontier host.

The Docker alternative is

text
docker-compose.rust.yml
. It requires an immutable
text
neo-os-services-relayer:sha-...
image and runs the same configuration preflight as its health check. Every other
text
docker-compose*.yml
file under
text
deploy/nitro
is labelled Node compatibility and refuses to resolve without pinned compatibility images.

Release gates

Run:

bash
npm run check:production-topology
npm run check:rust-migration
npm run check:cargo-lock-provenance
npm run format:rust:check
cargo clippy --workspace --all-targets --locked -- -D warnings
cargo test --workspace --all-targets --locked

text
check:production-topology
prevents production workflow/systemd/Compose drift back to Node authority, discovers every Node feed writer under
text
deploy/
, rejects boot-enabled compatibility chain writers on any network suffix, checks the Cloudflare feed/automation refusals, verifies both NeoX custody anchors are measured, and keeps automation disabled in the production template. The request/feed cutover is backed by real custody-to-broadcast, HALT/receipt confirmation, durable state, restart, and failure-injection tests; changing a label or image name alone would not have been a cutover.

Feed writer discovery gate

Overview

A hardcoded mainnet unit list is not an authority map. The topology gate walks

text
deploy/
for every Node chain-writer
text
.service
/
text
.timer
and checks each one. A future
text
morpheus-feed-pusher-<suffix>
file is in scope the moment it lands; omitting TestNet from an allowlist is a failed audit, not a pass.

Writer units are names matching

text
morpheus-feed-pusher*
,
text
morpheus-feed-topup*
, or
text
morpheus-neox-fulfiller*
with a
text
.service
or
text
.timer
suffix. Freshness monitors and DB prune units are not writers and are not in this set.

API Reference

text
scripts/check-rust-production-topology.mjs
exports:

SymbolContract
text
isNodeFeedWriterUnitName(fileName)
True only for the writer name classes above
text
compatibilityMarkerForFeedWriter(fileName)
Lane marker:
text
ENABLE_NODE_FEED_PUSHER_COMPAT
,
text
ENABLE_NODE_FEE_TOPUP_COMPAT
, or
text
ENABLE_NODE_NEOX_FULFILLER_COMPAT
text
discoverNodeFeedWriterUnits(root)
Recursive scan of
text
deploy/
for writer units
text
auditNodeFeedWriterTopology(root)
Writer authority, compatibility marker, mutual exclusion, and enable posture
text
checkNodeFeedCompatUnit(name, source, marker)
Compatibility-only label,
text
ConditionPathExists
marker,
text
Conflicts=
/
text
Before=
both Rust relayers, no
text
[Install]
text
checkRustFeedAuthorityUnit(name, source, writerUnitNames)
Rust relayer
text
Conflicts=
every discovered writer basename
text
checkWriterEnablePostureInDocs(name, source, writerStems)
Operator docs must not
text
systemctl enable --now
a discovered writer

Each Node writer must fail closed unless the operator creates the marker and starts (never enables) the unit while both Rust relayers are stopped. CI does not invoke

text
systemctl
; the gate is a strict unit-file parse.

Usage Examples

bash
npm run check:production-topology
node --test scripts/check-rust-production-topology.test.mjs

Regression fixtures live under

text
scripts/fixtures/feed-writer-topology/
:

  • text
    missed-testnet-writer/
    — compliant mainnet units plus a boot-enabled TestNet writer. A mainnet-only allowlist would pass; discovery must fail.
  • text
    compliant-testnet-writer/
    — TestNet units with marker, mutual exclusion, and no
    text
    [Install]
    . Discovery must pass.

Design decisions

  1. Rust is the only production writer. Node feed-pusher / top-up / fulfiller units, including TestNet, are disabled compatibility artifacts.
  2. Discover, do not enumerate. Hardcoding mainnet names produced a false pass while
    text
    morpheus-feed-pusher-testnet.service
    remained enableable.
  3. Fail closed on enable posture. No
    text
    [Install]
    section, an explicit
    text
    ENABLE_NODE_*_COMPAT
    path marker, and
    text
    Conflicts=
    /
    text
    Before=
    both
    text
    morpheus-relayer.service
    and
    text
    morpheus-relayer-nitro.service
    .
  4. Docs are in the same gate. An
    text
    enable --now
    instruction for a discovered writer is an audit failure even if the unit file is correct.

Test Coverage

CaseExpected
Repository audit
text
auditProductionTopology()
returns
text
[]
Discovery includes TestNet
text
morpheus-feed-pusher-testnet.*
is found under
text
deploy/
Mainnet-only allowlistFixture with a boot-enabled TestNet writer fails only when discovery is used
Compliant TestNet unitMarker + Conflicts/Before + no
text
[Install]
passes
Rust authority vs discovered writersMissing
text
Conflicts=
on a discovered TestNet unit fails
Docs enable posture
text
systemctl enable --now
of a writer stem fails the audit
CURRENT DESIGNUPDATED FOR DUAL-CVM ARCHITECTURE
NeoOS Services