Last verified: 2026-02-13 | Commit scope: bc0fb41
Domain Objects
Node
Location:internal/models/node.go
Field Ownership: All state mutations flow through
NodeHealthMachine (internal/health/machine.go), which enforces transitions, uses optimistic locking, and emits events via health_event_outbox.
State values:
state:provisioning,syncing,healthy,degraded,down,maintenance,terminating,terminated,failedapplication_health:unknown,ok,degraded,critical
DisplayState() computes a combined state for API responses — infrastructure states take precedence, then application_health, then sync_status.
Invariants:
- Node belongs to exactly one subscription
- Infrastructure fields (
provider,host_id,host_ip) are internal-only - Terminal states (
terminated,failed) have no outgoing transitions - Terminal transitions auto-reset
sync_statusandapplication_healthtounknown - Migration cooldown prevents rapid re-triggering (default: 1h)
Node State Machine
Transition table (defined inmodels.validTransitions):
API:
CanTransitionTo(target), IsTerminal(), ValidSourceStates(target). Force updates via ForceUpdateState() bypass validation for compensation.
See also: Health and Incidents for how transitions are triggered.
Subscription
Location:internal/models/subscription.go
User-provided:
chain_profile_id, node_type, duration. System-resolved: provider, region, instance_type (from chain profile + duration mapping). API responses hide system-resolved fields.
Subscription State Machine
Invariants:
- Infrastructure fields resolved at creation time (not user-provided)
pending_paymentsubscriptions deleted after TTL (default 30m)- Expiration triggers 24h grace period before termination
- Abandoned
pending_paymentsubscriptions havepayment_id = null
See also: Workflows — Subscription Lifecycle for execution flow.
Incident
Location:internal/models/incident.go
Categories:
Incident Status Lifecycle:
Dedup: Partial unique index on
(node_id, category) WHERE status NOT IN ('resolved', 'auto_resolved'). Upsert increments occurrence_count, updates last_seen_at.
No foreign keys: Incidents are historical records that survive node/subscription deletion.
See also: Health and Incidents for the full incident pipeline.
User
Location:internal/models/user.go
Identity combinations:
Invariants: Wallet address stored checksummed (EIP-55), lookups case-insensitive (lowercase index). Email optional and unique when provided.
external_auth_id unique when not null. Created via Clerk webhook.
API Key
Location:internal/models/apikey.go, internal/models/apikey_scope.go
Scopes:
nodes:read, nodes:write, subscriptions:read, subscriptions:write, chains:read, keys:export, payments:read, payments:write, api-keys:manage, * (wildcard)
Business rules: At least one scope required. Max 50 active keys per user. Min 1h expiration. Soft-deleted for audit trail. Rotation is atomic (new key + old revoked in one transaction). Plaintext returned only on creation/rotation.
Node Keys
Location:migrations/001_initial_schema.sql:88
Encryption hierarchy:
Auth Nonce
Location:internal/models/auth.go — Single-use challenge for wallet verification. 64-char random, 5-min TTL.
Refresh Token
Location:internal/models/auth.go — SHA-256 hashed, 7-day TTL. Atomic rotation on refresh (old revoked, new created in one transaction). Multiple active tokens per user (different devices).
Rollout Group
Location:internal/models/rollout.go
Coordinates multi-binary upgrades (e.g., ethereum-holesky: geth + lighthouse). Executes component rollouts in declared order.
Group status values:
pending, running, paused, completed, partial, failed, cancelled, rolled_back
Concurrency: Partial unique index on (chain_profile_id) WHERE status IN ('pending', 'running', 'paused') — one active group per chain.
Standalone single-component rollouts do not require a group.
Rollout
Location:internal/models/rollout.go
Single-component upgrade rollout. Tracks progress, strategy, and per-node status.
Rollout status values:
pending, scheduled, running, paused, completed, failed, cancelled, rolled_back
Concurrency: Partial unique index on (chain_profile_id) WHERE status IN ('pending', 'scheduled', 'running', 'paused').
Rollout Status State Machine
Rollout Node
Location:internal/models/rollout.go
Per-node upgrade tracking within a rollout.
Node upgrade status values:
pending, in_progress, validating, succeeded, failed, rolled_back, skipped
UpgradePhase Enum
Display states derived from executor progress through the action sequence:NodeConfigState
Location:internal/models/node_config_state.go
Tracks binary version per node. Pre-existing node_config_state table now has Go model and repository.
Written by
UpdateNodeBinaryVersion activity after successful upgrade, and by provisioning workflow after initial CONFIGURE.
See also: Workflows — Upgrade Rollout Workflows for the full workflow hierarchy.
Business Rules
Key Management
- Keys exist in plaintext only temporarily for node operation
- Encrypted recovery backups only within subscription TTL
- No human-accessible key material (no SSH, keys only via encrypted gRPC)
- Recovery without backup is impossible by design
- User-provided secrets use client-side NaCl sealed box encryption
Migration
- Gradual detection: Consecutive failures tracked per node (default: 3 required for DOWN)
- Cooldown: 1h between migration attempts (persisted in DB)
- Auto-recovery: Heartbeat during DOWN grace period -> back to HEALTHY, counter reset
- Steps: Verify backup -> provision new infra -> restore keys -> start node -> destroy old
- Non-blocking: No user approval required
Subscription Lifecycle
- Two-phase creation:
POST /subscriptionscreates withpending_payment. Idempotent — returns existing if same user/chain/nodeType/duration. - Pending cleanup: Abandoned
pending_paymentdeleted after TTL (default 30m) - Expiration:
active->expiring(24h grace period). Nodes continue running. - Termination: Grace period expired -> terminate all nodes, delete backups ->
terminated
Database Schema Summary
Main App Database
Payment Service Database
Separate PostgreSQL instance. See Payment Service for schema.Key Indexes
State Storage Locations
Related Documents
- Overview — System overview and service descriptions
- Workflows — State transition triggers, execution flows
- Health and Incidents — Health evaluation pipeline
- Extending — Adding wallet types