27 KiB
27 KiB
Trade Migration Plan: k3s on mevnode alongside agave
Date: 2026-04-12
Migration Revision Baseline
- Accepted migration baseline revision:
R001 R001means the currently working trade application running onk3sbefore migration cutover.- The current source runtime for
R001is the livetrade-stagingstack onmevnode_bot. - The first migration target is not a redesign. It is a faithful
reconstruction of
R001on the targetk3senvironment. - Functional changes, refactors, topology changes, and cleanup should
be treated as later revisions after
R001is reproducible. - During reconstruction, every manifest, secret mapping, image
reference, and host dependency should be mapped against
R001.
Goal
- Prepare migration of the trade system to
k3son hostmevnode(sol) in parallel withagave-validator. - Do not degrade validator stability, memory headroom, or disk/I/O for ledger and accounts.
Current Findings
mevnode_botis reachable again after restart.- Previous reset on
mevnode_botlooks likeACPI power state transition occurred. - I did not find a clear
OOM,kernel panic,watchdog, orI/O errortrail explaining the previous host reset. k3sonmevnode_botis up and the node isReady.- The main runtime failure is not the host itself but the DLOB
publishers:
dlob-publisher-hotis0/1dlob-publisher-allis0/1- both have very high restart counts
- both fail startup/liveness probes
- Publisher logs show Redis connectivity problems:
ClusterAllFailedError: Failed to refresh slots cacheNone of startup nodes is available
dlob-rediscurrently exposes a singleClusterIPendpoint on6379, while the publishers behave like cluster-aware Redis clients.Hasura,trade-api,trade-frontend,trade-ingestor,postgres, and the writer pods are currently up.trade-ingestorhad transient fetch failures after reboot, then resumed inserts.- There is a persistent warning in
k3sabout missing image pull secretgitea-registry.
Resource Picture: mevnode
- CPU:
32 vCPU - RAM total: about
186 GiB - RAM available: about
111 GiBat check time - Swap total: about
31 GiB - Swap used: about
13 GiB - Root filesystem
/:btrfs, about1.8 TiBtotal, about1.6 TiBfree /data/ledgerlives on a separate1.8 TiBNVMe device./data/statelives on another separate1.8 TiBNVMe device.- The current candidate place for trade persistent storage on
solis the root NVMe filesystem/, not the validator ledger or accounts devices. agave-validatorRSS: about129 GiBagave-validatoris already a heavy resident workload and must remain the priority workload on this host.
Resource Picture: mevnode_bot
- Root filesystem
/:ext4onmd RAID, about3.6 TiBtotal, about2.8 TiBfree - Current memory use after reboot is low, but this is not the main constraint.
- The main storage warning is current
k3sdata growth:/var/lib/rancher/k3s: about593 GiB/var/lib/rancher/k3s/storage/...data-postgres-0: about585 GiB- the related PVC is declared as only
20Gi
- This means the current
local-pathstorage setup does not give a meaningful hard size limit in practice. - The target Gitea organization
trade-nextnow exists. - The target repository set now exists in
trade-next:trade-apitrade-frontendtrade-ingestortrade-bottrade-dlobtrade-gitopstrade-host-iactrade-docs
- The current live component-to-repository boundary was documented in
trade-next/trade-docs/doc/k3s-component-repo-map.md. - We have not yet reconstructed the current live
trade-stagingmanifests and host drift intotrade-gitopsandtrade-host-iac. - The first canonical bootstrap module now exists in
trade-next/trade-gitops:bootstrap/gitea-actions. - An organization-scoped Gitea Actions runner for
trade-nextis now online onsoland can reach the targetk3scluster using theK3S_KUBECONFIG_B64org secret. - The first canary environment now exists in
trade-next/trade-gitopsunderenvironments/sol/trade-r001-canary. - Namespace
trade-r001-canaryis live onsolwith initial quota and limit guardrails, and can reach the sharedPostgresandRedisservices intrade-infra. - The first application surface for
R001is now live intrade-r001-canary:Hasura,trade-api, andtrade-frontend. - The target host
Postgresonsolnow hasTimescaleDBinstalled and a matching application database/user provisioned forR001. trade-ingestoris now live intrade-r001-canaryonsolin a schema-compatible canary mode that reads fromdlob_stats_latest.- Current live
trade-stagingdrift was confirmed fortrade-ingestor: the live script now readsdlob_hot_derived_latestanddlob_all_derived_latest, but that derived writer chain has not yet been reconstructed in thesolcanary schema. agaveRPC onsolis currently healthy; repeated samples after the canary rollout show slot lag of about2versusapi.mainnet-beta.solana.com.
Migration Risks
- Risk 1: moving the current storage model unchanged to
mevnodecan silently consume hundreds of GiB on the host root filesystem. - Risk 2:
agave-validatoralready uses most of the memory footprint onmevnode; uncontrolledk3sworkloads can force more swap or destabilize validator performance. - Risk 3: if trade workloads share disks or I/O paths with ledger/accounts, validator performance may degrade.
- Risk 4: current publisher instability must be fixed before migration, otherwise we would move a broken topology.
- Risk 5: missing
gitea-registrysecret can break future image pulls and cold-start behavior.
Plan
1.
Reconstruct the current live mevnode_bot state into
canonical Git/IaC
- Map each live
trade-stagingobject to a target repository and file path. - Split application code, Kubernetes manifests, and host-level infrastructure into separate Git sources of truth.
- Capture the current live deltas that are not present in local repos
or in
~/trade-k8s. - Prefer a new Gitea organization for the migration target instead of long-lived migration branches in existing repos.
- Treat live
k3sas input for reconstruction, not as a YAML dump to mirror blindly.
2. Stabilize
the current trade stack on mevnode_bot
- Fix
dlob-publisher-hot. - Fix
dlob-publisher-all. - Verify Redis mode and publisher Redis client mode are aligned.
- Confirm all
trade-stagingpods become healthy after a full restart.
3. Audit storage before any migration
- Measure actual PostgreSQL data size inside the current PVC.
- Determine how much of the
585 GiBis real database payload versus local-path overhead. - Identify retention candidates for historical data, derived tables, logs, and caches.
- Decide what data must move and what can be rebuilt.
4. Define target
storage layout on mevnode
- Do not place trade persistent data on the same critical paths as agave ledger/accounts.
- Do not rely on the current local-path behavior without hard operational limits.
- Choose a dedicated storage path or dedicated device for trade state.
- Set explicit quotas/limits and monitor actual bytes used on disk.
5. Define resource
isolation on mevnode
- Reserve headroom for
agave-validator. - Add CPU and memory requests/limits for trade workloads.
- Keep low-priority or bursty trade jobs away from validator-critical resources.
- Treat publisher and DB workloads as separate scheduling/priority classes.
6. Recreate the missing operational prerequisites
- Restore or recreate
gitea-registryimage pull secret. - Verify registry pull works after node reboot and after cache eviction.
- Verify secrets for RPC, gRPC, Hasura, Postgres, and frontend auth are reproducible.
7. Prepare a staged migration
- Start with a minimal canary namespace on
mevnode. - Bring up infrastructure first: Redis, Postgres, Hasura.
- Then bring up API and frontend.
- Migrate publishers and ingest only after storage and Redis behavior are validated.
- Keep
mevnode_botas rollback target until the new stack is stable.
8. Add migration validation checks
- Host-level: memory, swap, disk, inode, and load checks.
- Agave-level: slot lag, RPC health, and validator memory trend.
- Trade-level: pod readiness, Redis health, Hasura health, API health, publisher health, ingest success.
- End-to-end: UI, API, DLOB data freshness, and bot-related paths.
Immediate Next Actions
- Map the current live
trade-stagingstate into target Git repositories and IaC paths. - Start the first canonical inventory pass into
trade-gitopsandtrade-host-iac. - Inspect publisher deployment/env and confirm whether Redis should be single-instance or cluster mode.
- Inspect Redis server configuration and topology in
trade-staging. - Measure real Postgres database size inside the trade PVC.
- Draft target disk placement for trade data on
mevnode. - Decide minimum safe RAM headroom to keep free for
agave-validator.
Open Questions
- Which repositories belong in the new Gitea organization, and which existing repos should stay only as upstream history?
- Do we need a clean cutover into a new organization, or only a temporary compatibility layer with the current repos?
- Is the intended Redis topology for trade a single Redis instance or Redis Cluster?
- Which trade datasets are required to migrate, and which can be rebuilt from source?
- Where exactly on
mevnodeshould persistent trade storage live? - What is the acceptable validator memory floor and swap ceiling during trade rollout?
Chapter 2
01. Define hard database and cache constraints
- In progress.
- Storage limits for
PostgresandRedismust be enforced by the real storage backend, not only declared in Kubernetes manifests. - In core
Postgres, the hard stop for total on-disk growth should still be treated as the filesystem quota; PostgreSQL itself should add earlier guardrails before that limit is reached. Postgresmust not be able to grow beyond the actually allocated disk budget.Postgresmust have explicit CPU and memory requests/limits consistent with host headroom.Postgresshould usetemp_file_limitso large sorts and hashes fail before they can consume uncontrolled temporary disk space.Postgresmust have operating thresholds for free disk, WAL growth, autovacuum, and table/index bloat.Postgresshould use boundedmax_wal_sizeand, if replication slots are present, boundedmax_slot_wal_keep_size, while treating WAL limits as early control signals rather than the final hard stop.Postgresshould have retention and pruning policies for time-series data so historical chunks are dropped before storage pressure reaches the OS quota.Postgresshould have an operational read-only switch prepared, so new write traffic can be stopped before the filesystem quota is actually hit.Redismust have explicit memory limits and an intentional eviction/persistence policy.Redismust use explicitmaxmemorybelow the container memory limit and must set an intentionalmaxmemory-policyinstead of relying on the default unlimited dataset behavior.Redisshould setmaxmemory-clientsso client buffers cannot grow without bound under connection pressure.Redismust not be allowed to consume unbounded RAM or disk on the target host.- Database and cache capacity alerts must fire before any hard limit is reached, not after the filesystem is already effectively full.
- Full-disk and out-of-memory behavior for
PostgresandRedismust be tested before application cutover. - Done on
2026-04-12: installedPostgreSQL 16andRedis 7onsoland started both services. - Done on
2026-04-12: movedPostgresdata directory to/srv/trade/postgres/16/main. - Done on
2026-04-12: appliedPostgresguardrails onsol:shared_buffers=2GB,effective_cache_size=8GB,work_mem=16MB,maintenance_work_mem=512MB,temp_file_limit=8GB,max_wal_size=4GB,min_wal_size=512MB,max_slot_wal_keep_size=2GB,idle_in_transaction_session_timeout=10min. - Done on
2026-04-12: prepared and tested operationalPostgresread-only switch at/usr/local/bin/trade-postgres-readonly. - Done on
2026-04-12: appliedRedisguardrails onsol:maxmemory=384MB,maxmemory-clients=64MB,maxmemory-policy=noeviction,save \"\",appendonly no. - Done on
2026-04-12: initializedRedisas a single-node cluster with all16384slots assigned andcluster_state: ok.
02.
Reconstruct the current live mevnode_bot and
trade-staging state into canonical Git/IaC in a new Gitea
organization
- Live
k3sis the input for reconstruction, not the final source of truth. - Split application repositories, Kubernetes manifests, and host IaC into separate repositories.
- Prefer a new Gitea organization over long-lived migration branches in existing repositories.
- Recommended target organization name:
trade-next. - Target repository set in
trade-next:trade-api,trade-frontend,trade-ingestor,trade-bot,trade-dlob,trade-gitops,trade-host-iac,trade-docs. - Keep the current repository names where possible and change only the organization to reduce migration risk.
- Use
trade-gitopsas the GitOps source fork3smanifests andtrade-host-iacfor host-level IaC, SSH, storage, and operations. - Record every current live drift before moving workloads.
- Done on
2026-04-12:trade-nextwas created and the target repositories now exist. - Done on
2026-04-12: the live component boundary map was written totrade-next/trade-docs/doc/k3s-component-repo-map.md. - Done on
2026-04-12: bootstrappedtrade-next/trade-gitopswith the first runnable IaC module forsolunderbootstrap/gitea-actions. - Next for item
02: reconstruct the livetrade-stagingobjects, overlays, secrets wiring, and host-specific drift intotrade-gitopsandtrade-host-iac.
03.
Fix dlob-publisher-hot startup, readiness, and
liveness
- Pending.
04.
Fix dlob-publisher-all startup, readiness, and
liveness
- Pending.
05. Confirm whether trade Redis is meant to run as a single instance or as Redis Cluster
- Pending.
06. Align publisher Redis client mode with the actual deployed Redis topology
- Pending.
07.
Restore or recreate the missing gitea-registry image pull
secret
- Pending.
08. Verify that image pulls work after node reboot and on a cold start
- Pending.
09. Measure the real PostgreSQL data size inside the current trade PVC
- Pending.
10.
Explain why the declared 20Gi PVC currently consumes about
585 GiB on disk
- Pending.
11. Decide which trade datasets must be migrated and which can be rebuilt
- Pending.
12.
Define a dedicated storage path or device for trade data on
mevnode
- Done on
2026-04-12. - Current preferred target layout on
solis to place trade persistent storage on the root NVMe filesystem/, because validator ledger and validator state already have their own dedicated devices. - Do not place
PostgresorRedison/data/ledgeror/data/state. - Reserve a dedicated
1 TiBstorage budget forPostgresonsolas the first target sizing forR001. - Reserve a separate small storage budget for
Redisonsol, initially2 GiBunless persistence requirements change. - Prefer a dedicated path such as
/srv/trade/postgresand/srv/trade/redisover implicit storage under/var/lib/rancher/k3s/storage. - On the current
btrfsroot filesystem, the preferred implementation path is a dedicated subvolume plus enforcedqgroupquota for each persistent component. - Done on
2026-04-12: createdbtrfssubvolumes onsolat/srv/trade,/srv/trade/postgres, and/srv/trade/redis.
13.
Define hard storage quotas and monitoring for trade data on
mevnode
- In progress.
- Use enforceable limits, not only declarative PVC sizes without backend enforcement.
- Add warning and critical thresholds for disk usage, inode usage, and PVC growth.
- Target hard quota for
Postgresonsol:1 TiB. - Target hard quota for
Redisonsol:2 GiBunless later requirements justify persistence growth. - Keep enough unallocated free space on the root NVMe for the OS,
k3s, container images, logs, and rollback operations after trade quotas are reserved. - Done on
2026-04-12: enabledbtrfsqgroups on/atsoland applied hard quotas of1 TiBto/srv/trade/postgresand2 GiBto/srv/trade/redis.
14.
Define CPU and memory reservations that protect
agave-validator
- Pending.
15.
Define the minimum safe free RAM headroom on mevnode during
rollout
- Pending.
16.
Define the maximum acceptable swap usage on mevnode during
rollout
- Pending.
17. Install and
bootstrap k3s on mevnode
- Done on
2026-04-12. - Define the target
k3sinstallation model onmevnode. - Define the target storage class, host paths, and bootstrap dependencies needed before application rollout.
- Verify that the base cluster, registry access, namespaces, and secret distribution work before any application components are installed.
- Done on
2026-04-12: installed single-nodek3sonsolasv1.34.6+k3s1. - Done on
2026-04-12: installedk3sin a minimal profile withtraefik,servicelb,local-storage, andnetwork-policydisabled. - Done on
2026-04-12: validated core addons onsol; bothcorednsandmetrics-serverare healthy. - Done on
2026-04-12: prepared shared namespacetrade-infrawithClusterIPservicespostgres-hostandredis-hostbacked by hostEndpointSlicetargets on149.50.96.162. - Done on
2026-04-12: openedufwaccess oncni0only for pod CIDR10.42.0.0/24to6443,10250,5432, and6379. - Done on
2026-04-12: validatedpod -> service -> hostconnectivity from a test pod to bothPostgresandRedis.
18. Install
Postgres and Redis on
mevnode
- Done on
2026-04-12for host-level bootstrap. - Bring up the data plane first:
PostgresandRedis. - Validate persistence, storage placement, service discovery, restart behavior, and cold start.
- Validate that
Postgreslands on the dedicated1 TiBtrade storage area and thatRedislands on its separate small quota before application rollout. - Do not install application components until
PostgresandRedisare healthy. - Done on
2026-04-12:PostgresandRedisare running onsolas host services, bound to127.0.0.1and149.50.96.162fork3spod access. - Done on
2026-04-12: validatedPostgreson/srv/trade/postgres/16/main, validatedRedison/srv/trade/redis, and confirmedbtrfsquotas are active for both paths. - Done on
2026-04-12:Postgreslistens with pod CIDR access inpg_hba.conf, andRedisannounces149.50.96.162for cluster-aware clients insidek3s. - Done on
2026-04-12: installedTimescaleDB 2.26.2packages on hostPostgresforsol, enabledshared_preload_libraries=timescaledb, and provisioned application role/databaseadmin/cryptomatching the livetrade-postgressecret fromtrade-staging.
19.
Bootstrap an organization-scoped Gitea Actions runner on
mevnode
- Done on
2026-04-12. - Use
trade-next/trade-gitopsas the canonical bootstrap source, not an ad-hoc shell history on the host. - Scope the runner at the
trade-nextorganization level so every migration repository can reuse the same execution plane. - Keep the first bootstrap simple: one runner replica on
sol, one deployer service account, and one org secret carrying a kubeconfig for cluster access. - Keep the runner state persistent only where necessary; avoid long-lived Docker layer storage until image caching strategy is explicitly planned.
- Done on
2026-04-12: createdtrade-next/trade-gitopsinitialmainbranch withbootstrap/gitea-actionsmanifests, scripts, and a smoke workflow. - Done on
2026-04-12: deployedtrade-next-act-runnerin namespacegitea-actionsonsolusingdocker.io/gitea/act_runner:latestwith adocker:27-dindsidecar. - Done on
2026-04-12: createdtrade-gitops-deployerservice account and initialcluster-adminbinding for first-pass GitOps bootstrap. - Done on
2026-04-12: syncedK3S_KUBECONFIG_B64as an organization secret intrade-next. - Done on
2026-04-12: verified the runner isonlinein Gitea with labelsubuntu-latestandk3s-deploy. - Done on
2026-04-12: verified workflow execution fromtrade-next/trade-gitops; commitc566978completed with statussuccessforrunner-smoke / smoke (push).
20.
Prepare a minimal canary namespace for trade on
mevnode
- Done on
2026-04-12. - Use a dedicated canary namespace for
R001, not the default namespace and not the infra namespace. - Put namespace-level CPU, memory, object-count, and storage caps in place before the first application manifests are applied.
- Keep the first canary envelope conservative until the validator protection budget is finalized.
- Validate that the canary namespace can resolve and reach
postgres-host.trade-infra.svc.cluster.local:5432andredis-host.trade-infra.svc.cluster.local:6379. - Done on
2026-04-12: createdtrade-next/trade-gitopsenvironment module atenvironments/sol/trade-r001-canary. - Done on
2026-04-12: applied namespacetrade-r001-canaryonsolwith revision labelR001and canary environment labels. - Done on
2026-04-12: appliedResourceQuotaintrade-r001-canarywith capsrequests.cpu=2,limits.cpu=6,requests.memory=4Gi,limits.memory=12Gi,pods=20,services=10,configmaps=20,secrets=30,persistentvolumeclaims=4,requests.storage=100Gi. - Done on
2026-04-12: appliedLimitRangeintrade-r001-canarywith default request100m/128Mi, default limit1 CPU/1Gi, and per-container max2 CPU/4Gi. - Done on
2026-04-12: verified deployment through Gitea Actions; commit060dcc3completed with statussuccessfordeploy-trade-r001-canary / apply (push). - Done on
2026-04-12: verified an in-cluster connectivity smoke test fromtrade-r001-canaryto host-backedPostgresandRedisservices.
21. Bring up
Hasura, API, and frontend on mevnode
- Done on
2026-04-12. - Bring up
Hasuraonly afterPostgresis healthy. - Bring up API and frontend only after
Hasura,Postgres, andRedisare healthy. - Use
trade-next/trade-gitopsas the source of truth for the canary application surface, not ad-hoc manifests on the host. - Keep the first application rollout internal to the cluster:
ClusterIPservices and health checks first, public ingress later. - Reuse the live
R001application images and live application secrets, but point them at the target hostPostgresonsol. - Done on
2026-04-12: addedHasura,trade-api, andtrade-frontendmanifests totrade-next/trade-gitopsunderenvironments/sol/trade-r001-canary. - Done on
2026-04-12: added operator scripts totrade-next/trade-gitopsforsolhost preparation,gitea-registrysecret creation, and live secret sync frommevnode_bot/trade-stagingintotrade-r001-canary. - Done on
2026-04-12: preparedtrade-r001-canarysecrets onsolfortrade-postgres,trade-hasura,trade-api,trade-frontend-tokens,trade-basic-auth, andgitea-registry. - Done on
2026-04-12: ranpostgres-migrateandhasura-bootstrapjobs successfully onsol. - Done on
2026-04-12: deployedHasura,trade-api, andtrade-frontendtotrade-r001-canary; all three deployments reachedAvailable. - Done on
2026-04-12: verified internal HTTP health checks200forhttp://hasura:8080/healthz,http://trade-api:8787/healthz, andhttp://trade-frontend:8081/healthz. - Done on
2026-04-12: verified GitOps execution through Gitea Actions; commit6672e10completed with statussuccessfordeploy-trade-r001-canary / apply (push).
22. Migrate publishers and ingest only after storage and Redis validation pass
- In progress.
- Substep A is now in place on
sol: deploytrade-ingestorfirst in a schema-compatible canary mode and verify its secret wiring, rollout behavior, and Hasura connectivity. - Substep B remains pending: reconstruct the live
R001DLOB derived writer chain and thedlob_*_derived_latesttables so the canary ingestor can switch fromdlob_stats_latestto the exact current live query path. - Done on
2026-04-12: extendedtrade-next/trade-gitopscanary environment withtrade-ingestor, syncedtrade-ingestor-tokens, and addedtrade-ingestorrollout checks to thedeploy-trade-r001-canaryworkflow. - Done on
2026-04-12: manually validatedtrade-ingestoronsol; deployment reachedAvailable, restart count stayed at0, and startup logs confirmedHASURA_GRAPHQL_URL=http://hasura:8080/v1/graphql, marketsSOL-PERP,PUMP-PERP, and modedlob_stats_ticks. - Done on
2026-04-12: after push of commitb8f1303, the GitOps path onsolrolled a freshtrade-ingestorpod and it remained healthy with restart count0.
23. Validate host health, agave health, trade health, and end-to-end flow after rollout
- Pending.
24.
Keep mevnode_bot as a rollback target until the new stack
is stable
- Pending.
Chapter 3
- ☐
- Define hard database and cache constraints.
- ☐
- Reconstruct the current live
mevnode_botandtrade-stagingstate into canonical Git/IaC in a new Gitea organization.
- Reconstruct the current live
- ☐
- Fix
dlob-publisher-hotstartup, readiness, and liveness.
- Fix
- ☐
- Fix
dlob-publisher-allstartup, readiness, and liveness.
- Fix
- ☐
- Confirm whether trade Redis is meant to run as a single instance or as Redis Cluster.
- ☐
- Align publisher Redis client mode with the actual deployed Redis topology.
- ☐
- Restore or recreate the missing
gitea-registryimage pull secret.
- Restore or recreate the missing
- ☐
- Verify that image pulls work after node reboot and on a cold start.
- ☐
- Measure the real PostgreSQL data size inside the current trade PVC.
- ☐
- Explain why the declared
20GiPVC currently consumes about585 GiBon disk.
- Explain why the declared
- ☐
- Decide which trade datasets must be migrated and which can be rebuilt.
- ☒
- Define a dedicated storage path or device for trade data on
mevnode.
- Define a dedicated storage path or device for trade data on
- ☐
- Define hard storage quotas and monitoring for trade data on
mevnode.
- Define hard storage quotas and monitoring for trade data on
- ☐
- Define CPU and memory reservations that protect
agave-validator.
- Define CPU and memory reservations that protect
- ☐
- Define the minimum safe free RAM headroom on
mevnodeduring rollout.
- Define the minimum safe free RAM headroom on
- ☐
- Define the maximum acceptable swap usage on
mevnodeduring rollout.
- Define the maximum acceptable swap usage on
- ☒
- Install and bootstrap
k3sonmevnode.
- Install and bootstrap
- ☒
- Install
PostgresandRedisonmevnode.
- Install
- ☒
- Bootstrap an organization-scoped Gitea Actions runner on
mevnode.
- Bootstrap an organization-scoped Gitea Actions runner on
- ☒
- Prepare a minimal canary namespace for trade on
mevnode.
- Prepare a minimal canary namespace for trade on
- ☒
- Bring up
Hasura, API, and frontend onmevnode.
- Bring up
- ☐
- Migrate publishers and ingest only after storage and Redis validation pass.
- ☐
- Validate host health, agave health, trade health, and end-to-end flow after rollout.
- ☐
- Keep
mevnode_botas a rollback target until the new stack is stable.
- Keep