Skip to content

feat(checkpoint): remote-checkpoint indexing → load → deploy pipeline#2329

Merged
shrugs merged 15 commits into
mainfrom
feat/remote-checkpoint-tooling-prod
Jun 29, 2026
Merged

feat(checkpoint): remote-checkpoint indexing → load → deploy pipeline#2329
shrugs merged 15 commits into
mainfrom
feat/remote-checkpoint-tooling-prod

Conversation

@shrugs

@shrugs shrugs commented Jun 20, 2026

Copy link
Copy Markdown
Member

Remote-checkpoint indexing → load → deploy

Summary

  • Productionizes indexing a release candidate on disposable Cherry Servers hardware and loading the resulting schemas into a target ENSDb — cutting a full index from ~1 week to ~6 hours, with the deployed alpha/mainnet indexers resuming the warm schema instead of re-indexing.
  • Adds two private packages — @ensnode/ensdb-cli (dump/load ENSIndexer schemas + their ENSNode metadata) and @ensnode/ensindexer-checkpoint (Cherry/R2 box orchestration) — plus four [next]-prefixed workflows.
  • Fixes the stale alpha env defaults (searchlight/1 + unigraph) and introduces committed apps/ensindexer/configs/*.env as the single-source-of-truth build_id parity contract.

Why

Indexing ENS from scratch on Railway/Render takes ~1 week, so it's never a viable release step. This builds the fast path proven by an earlier prototype into a one-trigger pipeline:

  1. Resolve the release-candidate commit (head of the changesets "Release New Version" PR), build sha-tagged images, and index the two heavy mainnet-namespace configs (alpha, mainnet) on two Cherry boxes from the R2 ponder_sync seed.
  2. Load the output schemas into the target env's Postgres as warm <ns>Schema<VERSION>.
  3. Dispatch the existing blue/green deploy — alpha/mainnet resume in minutes; sepolia/etc index from scratch as normal.

Resume mechanism: the box runs the exact production identity (sourced from configs/<config>.env, no END_BLOCK_*), so Ponder's build_id matches the deployed service and it resumes rather than crashing. No build_id patching.

Identifiers: docker image = sha-<short>; schema name = <ns>Schema<VERSION> (so the RC pre-seed and the eventual release deploy share the schema); checkpoint dump = sha-keyed in R2.


What's here

  • packages/ensdb-clidump/load (schema via pg_dump/pg_restore, ENSNode metadata via ensdb-sdk, auto schema-rename, --skip-if-exists).
  • packages/ensindexer-checkpoint — bash: Cherry lifecycle (+ self-destruct watchdog), write-tuned Postgres, full-backfill + end-block modes, sha-keyed R2 lock/idempotency, alpha seed-refresh.
  • apps/ensindexer/configs/{alpha,mainnet}.env + drift-guard test — the build_id parity contract.
  • environment-defaults.ts — alpha → searchlight/1 + unigraph (EFP excluded from the shared default: its datasources are mainnet-only and would break alpha-sepolia; enabled explicitly in alpha.env).
  • Workflows ([next]): index_checkpoint_and_deploy.yml, checkpoint.yml (dev point-in-time), deploy_ensapi_hotfix.yml, checkpoint_reaper.yml.
  • deploy_ensnode_blue_green.yml — backward-compatible optional image_tag input (decouples image tag from schema version).

Testing

  • pnpm test (ensindexer + ensdb-cli): 256/256 pass; typecheck clean; biome clean.
  • ✅ All box scripts shellcheck-clean; all workflow YAML validated.
  • ✅ R2 seed bootstrapped into the ensindexer-checkpoints bucket (server-side copy, byte-verified).
  • Not yet exercised against live Cherry/R2/Railway — workflows are workflow_dispatch-only and need to be on main to run. The first run (planned: checkpoint.yml, config=mainnet, Jan 2019) is the real integration test. The one thing only a live run confirms is config parity (box identity == deployed service env).

Notes for Reviewer

  • Nothing auto-runs (all manual workflow_dispatch); the only scheduled job (reaper) only ever terminates ensindexer-checkpoint-* Cherry boxes — never production.
  • The single existing-workflow change (deploy_ensnode_blue_green.yml) is additive and backward-compatible.
  • Yellow (Render/Terraform) is intentionally out of scope for the index/deploy workflow — documented as a fast-follow.
  • New GitHub secrets the workflows expect: CHERRY_*, CF_R2_ACCESS_KEY_ID/CF_R2_SECRET_ACCESS_KEY, <TARGET>_ENSDB_ADMIN_URL (existing ALCHEMY_API_KEY reused).

Changeset: included for environment-defaults (behavior change for alpha instances); the two new packages are private (no changeset required).

Productionize indexing a release candidate on disposable Cherry Servers
hardware and loading the resulting schemas into a target ENSDb, cutting a
full index from ~1 week to ~6 hours. The deployed indexers resume the warm
schema rather than re-indexing.

- ensdb-cli: dump/load ENSIndexer schemas + their ENSNode metadata
- ensindexer-checkpoint: Cherry/R2 box orchestration (full-backfill + end-block modes)
- workflows ([next]-prefixed): index_checkpoint_and_deploy, checkpoint (dev), deploy_ensapi_hotfix, reaper
- environment-defaults: alpha → searchlight/1 + unigraph; committed configs/*.env parity contract
- deploy_ensnode_blue_green: optional image_tag input (decouple image tag from schema version)
@shrugs shrugs requested a review from a team as a code owner June 20, 2026 00:30
Copilot AI review requested due to automatic review settings June 20, 2026 00:30
@changeset-bot

changeset-bot Bot commented Jun 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7220f4f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 23 packages
Name Type
@ensnode/ensdb-sdk Minor
ensapi Minor
ensindexer Minor
@ensnode/ensdb-cli Patch
@ensnode/integration-test-env Minor
@ensnode/ensnode-sdk Minor
ensadmin Minor
ensrainbow Minor
fallback-ensapi Minor
enssdk Minor
enscli Minor
enskit Minor
ensskills Minor
@ensnode/datasources Minor
@ensnode/ensrainbow-sdk Minor
@ensnode/ponder-sdk Minor
@ensnode/ponder-subgraph Minor
@ensnode/shared-configs Minor
@docs/ensnode Minor
@docs/ensrainbow Minor
@namehash/ens-referrals Minor
@namehash/namehash-ui Minor
@ensnode/ensindexer-perf-testing Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
admin.ensnode.io Ready Ready Preview, Comment Jun 29, 2026 5:32pm
enskit-react-example.ensnode.io Ready Ready Preview, Comment Jun 29, 2026 5:32pm
ensnode.io Ready Ready Preview, Comment Jun 29, 2026 5:32pm
ensrainbow.io Ready Ready Preview, Comment Jun 29, 2026 5:32pm

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@shrugs, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 53 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ad3ef766-f09a-454d-bfc7-87bda7e9324b

📥 Commits

Reviewing files that changed from the base of the PR and between e57c211 and 7220f4f.

📒 Files selected for processing (1)
  • .github/workflows/deploy_ensnode_blue_green_v2.yml
📝 Walkthrough

Walkthrough

This PR updates alpha environment defaults (adds unigraph plugin, switches label set to searchlight/1), introduces a new @ensnode/ensdb-cli TypeScript package for Postgres schema dump/load with ENSNode metadata sidecars, expands @ensnode/ensdb-sdk with public schema and metadata helpers, adds a new @ensnode/ensindexer-checkpoint Bash orchestration package for Cherry ephemeral servers and Cloudflare R2, and adds four new or updated GitHub Actions workflows.

Changes

Alpha Environment Defaults

Layer / File(s) Summary
Alpha defaults, named config env files, and tests
.changeset/alpha-defaults-unigraph-searchlight.md, apps/ensindexer/src/config/environment-defaults.ts, apps/ensindexer/src/config/config.test.ts, apps/ensindexer/configs/alpha.env, apps/ensindexer/configs/mainnet.env, apps/ensindexer/configs/README.md
PluginName.Unigraph is appended to the alpha defaults plugin list; the label set is switched from subgraph/0 to searchlight/1; canonical alpha.env (with efp) and mainnet.env (subgraph-compat) identity files are added; the config test is updated to expect the new defaults.

ensdb-sdk public API expansion and ensdb-cli TypeScript package

Layer / File(s) Summary
EnsDbReader/EnsDbWriter schema and metadata helpers
packages/ensdb-sdk/src/client/ensdb-reader.ts, packages/ensdb-sdk/src/client/ensdb-reader.test.ts, packages/ensdb-sdk/src/client/ensdb-writer.ts, packages/ensdb-sdk/src/client/ensdb-writer.test.ts, packages/ensdb-sdk/src/client/index.ts, .changeset/ensdb-sdk-schema-metadata-helpers.md
schemaExists and public getEnsNodeMetadata are added to EnsDbReader; dropSchema, renameSchema, and public writeEnsNodeMetadata are added to EnsDbWriter; SerializedEnsNodeMetadata is re-exported from the SDK entrypoint; callers and tests are updated.
ensdb-cli package scaffold and CLI entrypoint
packages/ensdb-cli/package.json, packages/ensdb-cli/tsconfig.json, packages/ensdb-cli/tsup.config.ts, packages/ensdb-cli/vitest.config.ts, packages/ensdb-cli/src/main.ts, packages/ensdb-cli/src/cli.ts
Creates the private @ensnode/ensdb-cli ES-module package, maps the ensdb-cli binary to dist/cli.js, configures tsup to bundle src/cli.ts (copying migrations on success), and wires dump and load as subcommands.
pgtools, exec, and migrations library utilities
packages/ensdb-cli/src/lib/pgtools.ts, packages/ensdb-cli/src/lib/pgtools.test.ts, packages/ensdb-cli/src/lib/exec.ts, packages/ensdb-cli/src/lib/exec.test.ts, packages/ensdb-cli/src/lib/migrations.ts
Adds quoteIdent/pgDumpArgs/pgRestoreArgs/parseDumpSchemaName/dumpSchema/restoreDump/readDumpSchemaName; a child-process run helper with timeout and stdout/stderr capture; and ensnodeMigrationsDir; all with unit tests.
dump and load command implementations and tests
packages/ensdb-cli/src/commands/dump.ts, packages/ensdb-cli/src/commands/dump.test.ts, packages/ensdb-cli/src/commands/load.ts, packages/ensdb-cli/src/commands/load.test.ts
dump calls dumpSchema then writes an always-present .metadata.json sidecar; load reads the staging schema name, requires the sidecar, migrates, checks conflicts, restores, renames, and writes metadata; full unit test suites for both.

ensindexer-checkpoint orchestration package and CI workflows

Layer / File(s) Summary
Package scaffold, lib.sh, README, and config template
packages/ensindexer-checkpoint/package.json, packages/ensindexer-checkpoint/.gitignore, packages/ensindexer-checkpoint/README.md, packages/ensindexer-checkpoint/scripts/.shellcheckrc, packages/ensindexer-checkpoint/scripts/config.example.sh, packages/ensindexer-checkpoint/scripts/lib.sh
Creates the package manifest and gitignore; adds comprehensive README documenting the dual-phase model, build-ID parity contract, R2 layout, and safety guarantees; adds lib.sh with logging, SSH/SCP/R2 helpers, TTL-based R2 locks, Postgres binary discovery, memory-scaled tuning, and lifecycle/psql wrappers.
Cherry server provisioning and teardown
packages/ensindexer-checkpoint/scripts/cherry-up.sh, packages/ensindexer-checkpoint/scripts/cherry-down.sh
cherry-up.sh POSTs to Cherry API, polls for active state and SSH readiness, and arms an on-box self-destruct watchdog; cherry-down.sh issues an authenticated DELETE and cleans up local state files.
Remote provisioning and storage rehydration
packages/ensindexer-checkpoint/scripts/remote-provision.sh, packages/ensindexer-checkpoint/scripts/remote-rehydrate.sh
remote-provision.sh installs PostgreSQL 17, Node.js 24/pnpm, and rclone on a fresh Ubuntu box; remote-rehydrate.sh prepares NVMe/RAID0 storage, initializes Postgres, and idempotently restores ponder_sync from the R2 seed.
Remote checkout, indexing, completion detection, and end-block resolution
packages/ensindexer-checkpoint/scripts/remote-checkout.sh, packages/ensindexer-checkpoint/scripts/remote-index-one.sh, packages/ensindexer-checkpoint/scripts/detect-done.sh, packages/ensindexer-checkpoint/scripts/remote-resolve-end-blocks.sh, apps/ensindexer/scripts/print-indexed-chain-ids.ts
remote-checkout.sh clones, checks out, and builds ensdb-cli; remote-index-one.sh loads config identity, starts ensrainbow, resolves end blocks (in end-block mode), drops schema, starts ponder in an isolated process group, polls detect-done.sh, and gracefully stops; remote-resolve-end-blocks.sh queries ponder_sync and writes a manifest JSON.
remote-checkpoint.sh orchestrator and seed export
packages/ensindexer-checkpoint/scripts/remote-checkpoint.sh, packages/ensindexer-checkpoint/scripts/remote-seed-export.sh
remote-checkpoint.sh acquires an R2 lock, skips configs already in R2, runs rehydration and per-config indexing in parallel, dumps with metadata sidecars, optionally loads into the target ENSDb, and optionally refreshes the canonical seed; remote-seed-export.sh pg_dumps ponder_sync and rotates the canonical R2 seed object.
checkpoint.sh local orchestration and load-checkpoints.sh restoration
packages/ensindexer-checkpoint/scripts/checkpoint.sh, packages/ensindexer-checkpoint/scripts/load-checkpoints.sh
checkpoint.sh validates inputs, provisions a Cherry box with EXIT-trap teardown, uploads scripts and canonical configs, writes and uploads the .run-env secrets file, conditionally provisions remote toolchain, invokes remote-checkpoint.sh, and optionally triggers loader; load-checkpoints.sh downloads checkpoint dumps and sidecars from R2 and restores them into the target ENSDb using ensdb-cli.
GitHub Actions workflows for checkpoint, deploy, GC, and hotfix
.github/actions/deploy_railway_services/action.yml, .github/workflows/checkpoint.yml, .github/workflows/checkpoint_gc.yml, .github/workflows/index_checkpoint_and_deploy.yml, .github/workflows/deploy_ensapi_hotfix.yml, .github/workflows/deploy_ensnode_blue_green.yml
Adds deploy_railway_services composite action (sequential GraphQL mutation per service); adds checkpoint.yml (manual end-block trigger with R2 summary), checkpoint_gc.yml (hourly Cherry server GC), index_checkpoint_and_deploy.yml (full RC index+load+blue/green deploy), and deploy_ensapi_hotfix.yml (manual ENSApi hotfix); updates deploy_ensnode_blue_green.yml to separate version from tag.

Sequence Diagram(s)

sequenceDiagram
  participant GHA as GitHub Actions
  participant checkpoint_sh as checkpoint.sh
  participant CherryAPI as Cherry Servers API
  participant remote_checkpoint as remote-checkpoint.sh
  participant R2 as Cloudflare R2
  participant ponder as ponder indexer
  participant ensdb_cli as ensdb-cli

  GHA->>checkpoint_sh: trigger (CONFIGS, SHA, MODE, TARGET_URL)
  checkpoint_sh->>CherryAPI: POST /projects/:id/servers
  CherryAPI-->>checkpoint_sh: server id + IP
  checkpoint_sh->>remote_checkpoint: ssh on_box source .run-env && remote-checkpoint.sh
  remote_checkpoint->>R2: acquire_lock + check existing checkpoint dumps
  remote_checkpoint->>ponder: rehydrate ponder_sync from R2 seed, start per-config indexers
  ponder-->>remote_checkpoint: is_ready=1
  remote_checkpoint->>ensdb_cli: dump schema + write .metadata.json sidecar
  remote_checkpoint->>R2: upload dump + metadata sidecar
  remote_checkpoint->>ensdb_cli: load dump into TARGET_URL (DO_LOAD=1)
  remote_checkpoint->>R2: refresh canonical seed (DO_SEED=1)
  remote_checkpoint->>R2: release_lock
  remote_checkpoint-->>checkpoint_sh: CHECKPOINT_DONE_OK
  checkpoint_sh->>CherryAPI: DELETE /servers/:id (teardown)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • namehash/ensnode#1660: Introduces the ENSNode metadata data model and SerializedEnsNodeMetadata types that EnsDbReader.getEnsNodeMetadata and EnsDbWriter.writeEnsNodeMetadata are now built on.
  • namehash/ensnode#1778: The ensdb-sdk/ensdb-cli checkpoint metadata and schema-management helpers build directly on the ENSNode/ENSIndexer schema objects introduced in this PR.

Poem

🐇 Hop, hop, across the cloud I go,
Spinning up Cherry boxes row by row,
Dumping schemas into R2's keep,
While ponder indexes (never to sleep),
Searchlight shines on the label's new name —
The rabbit deployed, and all went to frame! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 48.98% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(checkpoint): remote-checkpoint indexing → load → deploy pipeline' accurately describes the main feature addition of a three-stage remote indexing, schema loading, and deployment pipeline.
Description check ✅ Passed The PR description comprehensively covers the summary (how deployment time is reduced), reasoning (why the pipeline was built), what is included (packages, workflows, configs), testing status, and notes for reviewers addressing the template sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/remote-checkpoint-tooling-prod

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a production-oriented “remote checkpoint” pipeline that indexes a release-candidate on disposable Cherry Servers, exports the resulting ENSIndexer schema + ENSNode metadata to R2, optionally loads it into a target ENSDb, and then deploys the stack so alpha/mainnet resume from a warm schema rather than re-indexing.

Changes:

  • Introduces two new private packages: @ensnode/ensdb-cli (schema dump/load + ENSNode metadata) and @ensnode/ensindexer-checkpoint (Cherry/R2-based orchestration scripts).
  • Adds committed named ENSIndexer identity configs (apps/ensindexer/configs/*.env) plus drift-guard tests; updates alpha defaults to include unigraph and searchlight/1.
  • Adds [next] GitHub workflows for checkpointing/indexing + load + deploy, a dev checkpoint workflow, a reaper safety net, and extends blue/green deploy to accept an optional image_tag.

Reviewed changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks dependencies for the new packages and updated dependency graph.
packages/ensindexer-checkpoint/scripts/remote-seed-export.sh Dumps ponder_sync and refreshes the canonical R2 seed (plus purges older seed objects).
packages/ensindexer-checkpoint/scripts/remote-run.sh Rehydrates seed, checks out a commit, runs ENSRainbow + ENSIndexer to is_ready=1, then graceful-stops.
packages/ensindexer-checkpoint/scripts/remote-resolve-end-blocks.sh Resolves deterministic per-chain end blocks from ponder_sync for dev checkpoints.
packages/ensindexer-checkpoint/scripts/remote-rehydrate.sh Initializes/mounts box storage, starts a tuned local Postgres cluster, restores R2 seed.
packages/ensindexer-checkpoint/scripts/remote-provision.sh Provisions the box toolchain (Node/pnpm/pg/rclone, etc.).
packages/ensindexer-checkpoint/scripts/remote-checkpoint.sh On-box orchestrator: lock → produce/reuse checkpoint → optional load → optional seed refresh.
packages/ensindexer-checkpoint/scripts/lib.sh Shared helpers for SSH/scp, R2 paths/locking, and Postgres lifecycle.
packages/ensindexer-checkpoint/scripts/detect-done.sh Reads Ponder readiness (_ponder_meta.app.is_ready) for completion detection.
packages/ensindexer-checkpoint/scripts/config.example.sh Environment-driven config template for CI/manual runs (no committed secrets).
packages/ensindexer-checkpoint/scripts/cherry-up.sh Provisions Cherry box, records id/ip, arms self-destruct watchdog.
packages/ensindexer-checkpoint/scripts/cherry-down.sh Terminates the Cherry box and cleans up local state.
packages/ensindexer-checkpoint/scripts/checkpoint.sh Manual end-to-end runner (up → ship → provision → run → down).
packages/ensindexer-checkpoint/scripts/.shellcheckrc Shellcheck configuration for these scripts.
packages/ensindexer-checkpoint/README.md Package documentation and operational overview of the checkpoint pipeline.
packages/ensindexer-checkpoint/package.json Declares the private package and its lint scripts.
packages/ensindexer-checkpoint/.gitignore Ignores secrets/state files (config.sh, .box-id, .box-host).
packages/ensdb-cli/vitest.config.ts Vitest config for the CLI package.
packages/ensdb-cli/tsup.config.ts Bundling config for the CLI binary output.
packages/ensdb-cli/tsconfig.json TypeScript config for the CLI package.
packages/ensdb-cli/src/main.ts Defines the CLI’s top-level command and subcommands.
packages/ensdb-cli/src/lib/pgtools.ts pg_dump/pg_restore helpers including identifier quoting and TOC parsing.
packages/ensdb-cli/src/lib/pgtools.test.ts Unit tests for pgtools quoting/arg building/TOC parsing.
packages/ensdb-cli/src/lib/exec.ts spawn() wrapper to run external tools and capture output/errors.
packages/ensdb-cli/src/lib/ensdb.ts ENSDb SDK helpers for schema ops and metadata dump/load.
packages/ensdb-cli/src/commands/load.ts Restores a dump, optional schema rename, optional metadata upsert.
packages/ensdb-cli/src/commands/dump.ts Dumps a schema and optional ENSNode metadata sidecar JSON.
packages/ensdb-cli/src/cli.ts Node shebang entrypoint that runs the citty command.
packages/ensdb-cli/package.json Declares the private CLI package, binary, scripts, deps.
apps/ensindexer/src/config/named-configs.test.ts Drift-guard tests to ensure committed configs match EnvironmentDefaults.
apps/ensindexer/src/config/environment-defaults.ts Updates alpha defaults (adds unigraph + searchlight/1 label set).
apps/ensindexer/src/config/config.test.ts Updates expected default client label set values.
apps/ensindexer/configs/README.md Documents “named config” identity env files and build_id parity contract.
apps/ensindexer/configs/mainnet.env Committed identity env vars for mainnet (subgraph-compatible).
apps/ensindexer/configs/alpha.env Committed identity env vars for alpha (mainnet namespace + efp, searchlight/1).
.github/workflows/index_checkpoint_and_deploy.yml New workflow: resolve RC → build images → checkpoint+load → dispatch blue/green deploy.
.github/workflows/deploy_ensnode_blue_green.yml Adds optional image_tag input to decouple image tag from schema version.
.github/workflows/deploy_ensapi_hotfix.yml New workflow for ENSApi-only hotfix deploy without reindex.
.github/workflows/checkpoint.yml New workflow to produce point-in-time dev checkpoints (end-block mode).
.github/workflows/checkpoint_reaper.yml Scheduled safety workflow to terminate stale checkpoint boxes.
.changeset/alpha-defaults-unigraph-searchlight.md Changeset for updated alpha defaults behavior.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/ensindexer/src/config/named-configs.test.ts Outdated
Comment thread packages/ensindexer-checkpoint/scripts/remote-resolve-end-blocks.sh
Comment thread packages/ensindexer-checkpoint/README.md Outdated
Comment thread .github/workflows/index_checkpoint_and_deploy.yml
Comment thread packages/ensindexer-checkpoint/scripts/lib.sh Outdated
Comment thread packages/ensindexer-checkpoint/scripts/cherry-down.sh Outdated
Comment thread packages/ensindexer-checkpoint/scripts/remote-checkpoint.sh Outdated
@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR productionizes the ENSIndexer checkpoint pipeline: instead of re-indexing from scratch on Railway (~1 week), it indexes alpha and mainnet configs on disposable Cherry Servers bare metal (~6 hours), uploads pg_dump checkpoints to Cloudflare R2, and restores them into the target ENSDb so the deployed indexers resume from a warm schema rather than starting cold.

  • New packages: @ensnode/ensdb-cli (dump/load with pg_dump/pg_restore + ENSNode metadata sidecar, schema rename, fail-fast conflict detection) and @ensnode/ensindexer-checkpoint (Cherry lifecycle scripts, write-tuned Postgres, R2 idempotency lock, parallel multi-config indexing, self-destruct watchdog).
  • Four new [next] workflows: index_checkpoint_and_deploy.yml (three-phase: deploy-light → Cherry index + runner load → deploy-heavy), checkpoint.yml (dev point-in-time), checkpoint_gc.yml (hourly Cherry reaper), and deploy_ensapi_hotfix.yml; plus a reusable deploy_railway_services composite action with GraphQL-error body detection.
  • environment-defaults.ts: alpha defaults updated to searchlight/1 + unigraph; committed apps/ensindexer/configs/*.env files serve as the single source of truth for build_id parity.

Confidence Score: 5/5

Safe to merge. All manual-trigger-only workflows, no auto-run paths, and the single scheduled job (GC reaper) only ever deletes ensindexer-checkpoint-* boxes — never production infrastructure.

Every previously flagged finding has been addressed in the current HEAD: ENSRainbow gate requires both DB marker and live health; end-block resolution uses a temp file with explicit exit-code propagation; the Cherry API token ships via a 600 file; redeploy_service uses --fail --show-error; set_shared_variable inspects the response body for GraphQL errors; load.ts fails fast on an unresolvable dump schema. The remaining comments are diagnostic display nits, a masked watchdog startup guard, and a curl-pipe-bash supply chain note — none affect the functional correctness of the checkpoint-index-load-deploy flow.

No files require blocking attention. cherry-up.sh (watchdog || warn guard masked by &) and remote-provision.sh (curl | bash for Node/rclone) are worth a second look for hardening but do not affect correctness.

Important Files Changed

Filename Overview
packages/ensindexer-checkpoint/scripts/remote-index-one.sh Indexes one config to backfill completion in an isolated setsid process group. ENSRainbow gate requires both DB marker + live health check; end-block resolution writes to a temp file with explicit exit-code check; crash detection is process-liveness-first. All previous P1 findings have been addressed.
packages/ensindexer-checkpoint/scripts/remote-checkpoint.sh Orchestrates parallel indexing of multiple configs on a shared box — R2 idempotency lock, parallel background indexing with explicit wait + failure collection, sequential dump/upload. Logic is sound.
packages/ensindexer-checkpoint/scripts/cherry-up.sh Provisions Cherry box; self-destruct watchdog ships token via a 600 file (previous P1 fix applied). Minor: the
packages/ensindexer-checkpoint/scripts/lib.sh Shared helpers: write_env_file correctly escapes single quotes; acquire_lock has TOCTOU-acknowledged TOFU pattern; R2 path helpers are consistent.
packages/ensindexer-checkpoint/scripts/checkpoint.sh Unified orchestrator: ships secrets via 600 .run-env file, separates box teardown from load phase, correctly passes TARGET_URL only to load-checkpoints.sh (never reaches the box).
packages/ensindexer-checkpoint/scripts/load-checkpoints.sh Downloads R2 checkpoints and calls ensdb-cli load for each config sequentially; writes rclone config from credentials; cleans up dump files between configs.
packages/ensdb-cli/src/commands/load.ts Fail-fast when dump schema name is unresolvable; checks target+staging schema and metadata conflicts before restore; migrates ensnode schema first; renames restored schema to target name safely. Previous P1 fully addressed.
packages/ensdb-cli/src/commands/dump.ts Dumps schema via pg_dump and writes metadata sidecar; correctly uses try/finally for reader cleanup.
packages/ensdb-sdk/src/client/ensdb-reader.ts Adds schemaExists; makes getEnsNodeMetadata public with corrected full-record return type; fixes deserializeIndexingMetadataContext(record) to (record.value).
packages/ensdb-sdk/src/client/ensdb-writer.ts Adds dropSchema and renameSchema DDL helpers using sql.identifier; renames and publicises writeEnsNodeMetadata with correct re-keying behaviour.
.github/workflows/index_checkpoint_and_deploy.yml Three-phase pipeline with GraphQL body-error detection. Minor: set_shared_variable inlines values into the mutation string rather than using GraphQL variables protocol.
.github/actions/deploy_railway_services/action.yml Reusable action; fail_on_graphql_error catches body-level errors; both update_service_image and redeploy_service use --fail --show-error. Previous P1 fully addressed.
packages/ensindexer-checkpoint/scripts/remote-provision.sh One-time toolchain setup; uses curl
apps/ensindexer/src/config/environment-defaults.ts Updates alpha defaults to searchlight/1 + unigraph; test updated accordingly. Behavior change correctly changesetted.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant GH as GitHub Actions
    participant Cherry as Cherry Servers
    participant R2 as Cloudflare R2
    participant Target as Target ENSDb
    participant Railway as Railway
    GH->>GH: resolve RC commit + version + image_tag
    GH->>GH: build-images 4x sha-tagged to GHCR
    par Phase 1 deploy-light
        GH->>Railway: set sepolia schema vars
        GH->>Railway: deploy ENSRainbow + sepolia indexers + ENSApi
    and Phase 2 index-checkpoint on Cherry
        GH->>Cherry: cherry-up provision box
        GH->>Cherry: ship scripts + configs + rclone.conf + run-env 600
        Cherry->>R2: download ponder_sync seed rehydrate Postgres
        par alpha
            Cherry->>Cherry: ensrainbow searchlight ponder alpha
            Cherry-->>Cherry: "is_ready=1"
        and mainnet
            Cherry->>Cherry: ensrainbow subgraph ponder mainnet
            Cherry-->>Cherry: "is_ready=1"
        end
        Cherry->>Cherry: pg_dump schemas
        Cherry->>R2: upload sha-keyed dumps + sidecars
        Cherry->>R2: upload enriched ponder_sync seed
        GH->>Cherry: cherry-down terminate box
    end
    GH->>GH: Phase 2b load on plain runner
    GH->>R2: download checkpoints
    GH->>Target: ensdb-cli load alphaSchema + mainnetSchema
    GH->>Railway: Phase 3 set ALPHA MAINNET schema vars
    GH->>Railway: deploy heavy indexers
    Railway->>Target: resume from warm schemas
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant GH as GitHub Actions
    participant Cherry as Cherry Servers
    participant R2 as Cloudflare R2
    participant Target as Target ENSDb
    participant Railway as Railway
    GH->>GH: resolve RC commit + version + image_tag
    GH->>GH: build-images 4x sha-tagged to GHCR
    par Phase 1 deploy-light
        GH->>Railway: set sepolia schema vars
        GH->>Railway: deploy ENSRainbow + sepolia indexers + ENSApi
    and Phase 2 index-checkpoint on Cherry
        GH->>Cherry: cherry-up provision box
        GH->>Cherry: ship scripts + configs + rclone.conf + run-env 600
        Cherry->>R2: download ponder_sync seed rehydrate Postgres
        par alpha
            Cherry->>Cherry: ensrainbow searchlight ponder alpha
            Cherry-->>Cherry: "is_ready=1"
        and mainnet
            Cherry->>Cherry: ensrainbow subgraph ponder mainnet
            Cherry-->>Cherry: "is_ready=1"
        end
        Cherry->>Cherry: pg_dump schemas
        Cherry->>R2: upload sha-keyed dumps + sidecars
        Cherry->>R2: upload enriched ponder_sync seed
        GH->>Cherry: cherry-down terminate box
    end
    GH->>GH: Phase 2b load on plain runner
    GH->>R2: download checkpoints
    GH->>Target: ensdb-cli load alphaSchema + mainnetSchema
    GH->>Railway: Phase 3 set ALPHA MAINNET schema vars
    GH->>Railway: deploy heavy indexers
    Railway->>Target: resume from warm schemas
Loading

Reviews (18): Last reviewed commit: "ci: split next-gen blue/green deploy int..." | Re-trigger Greptile

Comment thread packages/ensindexer-checkpoint/scripts/remote-run.sh Outdated
Comment thread packages/ensdb-cli/src/commands/load.ts Outdated
Comment thread packages/ensindexer-checkpoint/scripts/cherry-up.sh Outdated
Comment thread packages/ensindexer-checkpoint/scripts/remote-run.sh Outdated
Comment thread .github/workflows/checkpoint_gc.yml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 22

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/deploy_ensnode_blue_green.yml (1)

28-41: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a concurrency group to prevent simultaneous deploys to the same target.

This deploy workflow modifies Railway services but has no concurrency protection. Concurrent runs targeting the same environment could cause race conditions in service updates.

♻️ Suggested fix

Add after line 26 (after permissions:):

concurrency:
  group: deploy-ensnode-${{ inputs.target }}
  cancel-in-progress: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/deploy_ensnode_blue_green.yml around lines 28 - 41, The
deploy-environment job lacks a concurrency group configuration, which could
allow simultaneous deployments to the same target environment, causing race
conditions. Add a concurrency block to the deploy-environment job that groups
deployments by the target environment name using inputs.target and sets
cancel-in-progress to false to prevent cancelling already-in-progress
deployments. Insert this concurrency section after the permissions field in the
deploy-environment job definition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/checkpoint_reaper.yml:
- Around line 8-14: The workflow checkpoint_reaper.yml lacks a concurrency group
configuration which can result in overlapping hourly runs if the reaper takes
longer than an hour to complete. Add a concurrency section to the workflow after
the permissions block that specifies a unique concurrency group name (such as
one derived from the workflow name) with cancel-in-progress set to true. This
will ensure that when a new scheduled run is triggered, any previously running
instance is cancelled, preventing resource waste and rate limit issues.

In @.github/workflows/checkpoint.yml:
- Around line 57-69: The free-form inputs chain_ids, commit, and timestamp are
being directly interpolated into the shell script using ${{ }} syntax, which
creates a shell injection vulnerability if these inputs contain shell
metacharacters. To fix this, define these inputs as environment variables in the
GitHub Actions workflow before the run step executes the shell script. For
example, add an env section to the step that maps inputs.chain_ids,
inputs.commit, and inputs.timestamp to environment variables, then reference
these environment variables within the shell script using standard shell
variable syntax (e.g., $CHAIN_IDS instead of ${{ inputs.chain_ids }}) to ensure
they are treated as literal strings rather than being evaluated by the shell.
- Around line 82-96: In the "Report checkpoint R2 location" step, the OBJ
variable is directly interpolating user inputs (inputs.config, inputs.commit,
and inputs.timestamp) into the shell script, creating a shell injection
vulnerability. Instead of using direct interpolation with ${{ }}, define these
inputs as environment variables at the top of the step (similar to the pattern
used elsewhere), and then reference those environment variables in the shell
script to safely construct the OBJ variable and subsequent commands that use it.

In @.github/workflows/deploy_ensapi_hotfix.yml:
- Around line 8-26: Add a concurrency group to the deploy_ensapi_hotfix workflow
to prevent race conditions when simultaneous hotfix deploys target the same
environment. Use the workflow input variable (target) as the concurrency group
key to ensure only one deploy runs per target at a time. Additionally, remove
the unused id-token: write permission from the permissions section since the
workflow does not perform OIDC authentication, keeping only the contents: read
permission that is actually needed.

In @.github/workflows/index_checkpoint_and_deploy.yml:
- Around line 162-170: The gh workflow run command in the "Dispatch blue/green
deploy" step can fail silently and still exit with status 0 (success) if the
workflow is not found or permissions are insufficient, causing the deploy job to
incorrectly succeed even though no deployment was triggered. Add the --json flag
to the gh workflow run command to output structured JSON, then capture and
validate the response to ensure the workflow dispatch actually succeeded before
allowing the step to complete. If the dispatch fails, the step should exit with
a non-zero status to properly fail the job.
- Around line 23-29: Remove the unused id-token: write permission from the
workflow-level permissions block in the index_checkpoint_and_deploy.yml file.
The workflow only needs contents: read and actions: write permissions for the gh
CLI and downstream workflow dispatch operations, so the id-token: write line
should be deleted to reduce unnecessary privilege escalation and improve the
security posture of the workflow.
- Around line 45-72: In the "Resolve RC commit, version, and image tag" step,
the inputs.commit value is being directly interpolated into the shell script.
Pass this user input safely through an environment variable instead. Add an env
section to the step that captures inputs.commit as an environment variable, then
reference that environment variable (e.g., $COMMIT_INPUT) in the shell script
where the condition checks for inputs.commit and assigns it to the SHA variable,
rather than using direct template interpolation ${{ inputs.commit }}.

In `@apps/ensindexer/src/config/named-configs.test.ts`:
- Around line 18-20: The code in the test parser does not validate that
trimmed.indexOf("=") returns a valid index before using the eq variable for
string slicing. Add a check after calculating eq to ensure it is not -1; if
indexOf("=") returns -1 (meaning no equals sign was found), throw an error or
skip the line to fail fast instead of silently creating corrupted key/value
pairs with the slice operations. This will prevent malformed env file content
from being silently processed and potentially hiding test failures.

In `@packages/ensdb-cli/src/commands/load.ts`:
- Around line 75-77: Remove the unsafe `as MetadataRow[]` type cast in the
metadata file parsing block. Instead, parse the JSON from the file and validate
it against a Zod schema for MetadataRow[] before passing the rows to the
loadMetadata() function. Define or import a Zod schema that validates the
structure of MetadataRow objects, then use Zod's parse method to validate the
JSON result. This ensures malformed metadata files fail immediately with clear
validation errors rather than silently accepting invalid data.

In `@packages/ensdb-cli/src/lib/ensdb.ts`:
- Around line 60-77: The loadMetadata function processes multiple metadata rows
in a loop without transactional protection, meaning if any row insertion fails,
the function will have already committed previous rows, leaving the database in
a partial/inconsistent state. Wrap the entire for loop that iterates over rows
and performs the insert operations in a single database transaction by using
writer.ensDb.transaction() to ensure all metadata rows are written atomically as
an all-or-nothing operation.

In `@packages/ensdb-cli/src/lib/exec.ts`:
- Around line 12-31: The run() function currently has no timeout protection,
which allows spawned subprocesses (like pg_dump or pg_restore) to block
indefinitely if they stall due to network issues, locks, or hung processes. Add
a timeout mechanism to the Promise returned by run() that will reject with an
appropriate error if the child process does not complete within a reasonable
time limit (consider a configurable timeout value). This should be implemented
alongside the existing event handlers so that if the timeout fires before the
child process closes, the promise is rejected and the child process is
terminated to prevent wedging checkpoint jobs.

In `@packages/ensindexer-checkpoint/scripts/checkpoint.sh`:
- Around line 23-25: The trap cleanup EXIT command is currently registered after
cherry-up.sh executes, but since set -e is inherited from lib.sh, if
cherry-up.sh fails, the script exits immediately without registering the trap,
leaving the provisioned Cherry box orphaned. Move the trap cleanup EXIT line to
execute before the bash "$LIB_DIR/cherry-up.sh" call to ensure that the cleanup
function is registered and will execute on exit regardless of whether
cherry-up.sh succeeds or fails.
- Around line 47-53: The on_box function call contains a shell injection
vulnerability where variables are enclosed in single quotes without proper
escaping. Single quotes prevent variable expansion but do not escape embedded
single quotes, backticks, or dollar signs in the variable values themselves,
allowing injection attacks. Fix this by using printf '%q' to properly escape
each variable (MODE, CONFIG, SHA, SCHEMA, ALCHEMY_API_KEY, DO_LOAD, TARGET_URL,
TARGET_SCHEMA, DO_SEED, TIMESTAMP, CHAIN_IDS, and CKPT_SUFFIX) before they are
passed to the on_box command, which will safely handle any special characters or
quotes within the values.

In `@packages/ensindexer-checkpoint/scripts/cherry-down.sh`:
- Around line 15-17: The rm -f command that removes "$LIB_DIR/.box-id" and
"$LIB_DIR/.box-host" executes regardless of whether the curl DELETE call
succeeds or fails. If the DELETE operation fails, the local state files should
not be removed, as this destroys the reference to the running server.
Restructure the script so that the file removal is conditional and only executes
after a successful DELETE call, allowing retries to work properly if the initial
termination attempt fails.

In `@packages/ensindexer-checkpoint/scripts/cherry-up.sh`:
- Around line 24-40: The server ID is persisted to .box-id immediately after
creation, but if the provisioning loop times out waiting for the active state
and IP address, there is no cleanup mechanism to delete the orphaned server. Add
a rollback function that deletes the Cherry server by making a DELETE API call
to $API/servers/$id before the die command that handles the timeout failure
condition when the 120 iteration loop completes without achieving both active
state and a valid IP.

In `@packages/ensindexer-checkpoint/scripts/detect-done.sh`:
- Around line 9-11: Remove the error suppression (2>/dev/null) from both psql
queries that set the ready and c1 variables so that database errors and query
failures are visible instead of being hidden. Additionally, remove the default
parameter expansion patterns (:-?) from the echo statement that fall back to ?
when variables are empty, or replace them with explicit error checking that
fails the script if either query returns no result. This will allow the script
to fail fast when the database is unavailable or the schema is missing, rather
than silently returning ? values that cause the polling loop in remote-run.sh to
run indefinitely.

In `@packages/ensindexer-checkpoint/scripts/lib.sh`:
- Line 25: The SSH_OPTS configuration is effectively disabling host key
verification by using UserKnownHostsFile=/dev/null, which prevents SSH from
persisting and checking known hosts on subsequent connections. Remove the
UserKnownHostsFile=/dev/null parameter from the SSH_OPTS array to allow SSH to
properly store and verify host keys in the default known_hosts file, which will
enable proper MITM protection for all SSH operations using these options.
- Around line 70-82: The acquire_lock() function has a race condition where it
checks lock existence with r2_exists and then writes with rclone rcat in
separate operations, allowing concurrent processes to both pass the check. Fix
this by implementing token ownership verification: write the lock token (the
timestamp stored in variable now) to the lock file path stored in variable p,
then immediately read it back and verify that the value matches what was
written; if the read-back value differs, another process has overwritten the
lock and you should fail or retry appropriately.

In `@packages/ensindexer-checkpoint/scripts/remote-checkpoint.sh`:
- Around line 20-24: Add validation checks for MODE, DO_SEED, and CKPT_SUFFIX
after the variable initialization section to prevent unsafe operations. First,
add a check that verifies when MODE is set to "end-block", the CKPT_SUFFIX
variable must not be empty to avoid checkpoint key collisions with full-backfill
runs. Second, add a check that restricts DO_SEED to only be set to 1 when CONFIG
is "alpha", preventing undefined behavior on non-alpha canonical R2 seeds. Use
die() to terminate with clear error messages for each validation failure,
inserting these checks after the CKPT_SUFFIX assignment.

In `@packages/ensindexer-checkpoint/scripts/remote-resolve-end-blocks.sh`:
- Around line 14-25: The TIMESTAMP and chain_id values (variable c) are being
used unquoted directly in the SQL query on the line with the pg command without
numeric validation, which creates a security vulnerability. Before the pg
command executes the SQL query, add validation to ensure that TIMESTAMP is a
valid positive integer and that the chain_id value stored in c is also a valid
positive integer. Use a pattern matching approach or numeric validation function
to verify these values contain only digits, and exit with an error message if
validation fails for either TIMESTAMP or any individual chain_id.

In `@packages/ensindexer-checkpoint/scripts/remote-run.sh`:
- Around line 81-86: The readiness check for EnsRainbow has a logic flaw where
the final guard statement at line 85 only verifies the marker file exists, but
does not confirm the health endpoint was actually successful. If the loop times
out after 720 iterations while the marker file exists but the health check never
succeeds, the script incorrectly proceeds. Modify the guard statement to verify
that both the marker file exists AND the health check endpoint returned
successfully, not just the presence of the marker file, to ensure the service is
truly ready before continuing.
- Around line 127-138: The while loop that waits for backfill completion
(checking for is_ready=1) has no timeout mechanism and can run indefinitely if
the indexer gets stuck. Add a timeout mechanism to this loop by tracking the
elapsed time using a start timestamp variable initialized before the loop
begins, and within the loop check if the elapsed time exceeds a reasonable
maximum wait time (such as 30 minutes or 1800 seconds). If the timeout is
exceeded before is_ready=1 is detected, exit the loop and call die with an
appropriate error message indicating that the wait for backfill completion timed
out.

---

Outside diff comments:
In @.github/workflows/deploy_ensnode_blue_green.yml:
- Around line 28-41: The deploy-environment job lacks a concurrency group
configuration, which could allow simultaneous deployments to the same target
environment, causing race conditions. Add a concurrency block to the
deploy-environment job that groups deployments by the target environment name
using inputs.target and sets cancel-in-progress to false to prevent cancelling
already-in-progress deployments. Insert this concurrency section after the
permissions field in the deploy-environment job definition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 91004aac-1854-4ae8-90bb-fce9cf067fcf

📥 Commits

Reviewing files that changed from the base of the PR and between 24d99ae and cfe43e7.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (40)
  • .changeset/alpha-defaults-unigraph-searchlight.md
  • .github/workflows/checkpoint.yml
  • .github/workflows/checkpoint_reaper.yml
  • .github/workflows/deploy_ensapi_hotfix.yml
  • .github/workflows/deploy_ensnode_blue_green.yml
  • .github/workflows/index_checkpoint_and_deploy.yml
  • apps/ensindexer/configs/README.md
  • apps/ensindexer/configs/alpha.env
  • apps/ensindexer/configs/mainnet.env
  • apps/ensindexer/src/config/config.test.ts
  • apps/ensindexer/src/config/environment-defaults.ts
  • apps/ensindexer/src/config/named-configs.test.ts
  • packages/ensdb-cli/package.json
  • packages/ensdb-cli/src/cli.ts
  • packages/ensdb-cli/src/commands/dump.ts
  • packages/ensdb-cli/src/commands/load.ts
  • packages/ensdb-cli/src/lib/ensdb.ts
  • packages/ensdb-cli/src/lib/exec.ts
  • packages/ensdb-cli/src/lib/pgtools.test.ts
  • packages/ensdb-cli/src/lib/pgtools.ts
  • packages/ensdb-cli/src/main.ts
  • packages/ensdb-cli/tsconfig.json
  • packages/ensdb-cli/tsup.config.ts
  • packages/ensdb-cli/vitest.config.ts
  • packages/ensindexer-checkpoint/.gitignore
  • packages/ensindexer-checkpoint/README.md
  • packages/ensindexer-checkpoint/package.json
  • packages/ensindexer-checkpoint/scripts/.shellcheckrc
  • packages/ensindexer-checkpoint/scripts/checkpoint.sh
  • packages/ensindexer-checkpoint/scripts/cherry-down.sh
  • packages/ensindexer-checkpoint/scripts/cherry-up.sh
  • packages/ensindexer-checkpoint/scripts/config.example.sh
  • packages/ensindexer-checkpoint/scripts/detect-done.sh
  • packages/ensindexer-checkpoint/scripts/lib.sh
  • packages/ensindexer-checkpoint/scripts/remote-checkpoint.sh
  • packages/ensindexer-checkpoint/scripts/remote-provision.sh
  • packages/ensindexer-checkpoint/scripts/remote-rehydrate.sh
  • packages/ensindexer-checkpoint/scripts/remote-resolve-end-blocks.sh
  • packages/ensindexer-checkpoint/scripts/remote-run.sh
  • packages/ensindexer-checkpoint/scripts/remote-seed-export.sh

Comment thread .github/workflows/checkpoint_gc.yml
Comment thread .github/workflows/checkpoint.yml Outdated
Comment thread .github/workflows/checkpoint.yml
Comment thread .github/workflows/deploy_ensapi_hotfix.yml
Comment thread .github/workflows/index_checkpoint_and_deploy.yml Outdated
Comment thread packages/ensindexer-checkpoint/scripts/lib.sh Outdated
Comment thread packages/ensindexer-checkpoint/scripts/remote-checkpoint.sh Outdated
Comment thread packages/ensindexer-checkpoint/scripts/remote-resolve-end-blocks.sh
Comment thread packages/ensindexer-checkpoint/scripts/remote-run.sh Outdated
Comment thread packages/ensindexer-checkpoint/scripts/remote-run.sh Outdated
Index both mainnet-namespace configs in parallel on a single disposable
box (shared Postgres + ponder_sync) instead of one box per config. Ponder
indexes single-threaded, so two configs fit a high-core box; co-location
restores the ~200GB ponder_sync seed once (not twice) and fetches the
shared chain-1 RPC tail once.

- bump default Cherry plan amd-ryzen-9900x -> amd-ryzen-9950x
- remote-checkout.sh: shared cheap setup (stop stale stack, checkout @ SHA,
  install, build ensdb-cli); frees all four ports
- remote-index-one.sh: index ONE config in an isolated process group on
  caller-assigned ports, graceful-stop only that config (fork-safe pgid
  capture; identity passed inline as process env, no shared .env.local)
- remote-checkpoint-prod.sh: lock -> checkout -> rehydrate once -> index
  alpha + mainnet in parallel -> dump/upload/load each -> refresh seed once
- index-both.sh: manual production runner
- remote-run.sh: now a thin single-config wrapper (dev checkpoint flow
  unchanged)
- index_checkpoint_and_deploy.yml: drop the per-config matrix; one job
  runs both configs on one box

Per-config isolation: ponder ports 42069/42169, ensrainbow ports
3223/3224 + separate data dirs (labelsets differ: searchlight/1 vs
subgraph/0; LevelDB takes an exclusive dir lock), separate schemas and
process groups.
Comment thread packages/ensindexer-checkpoint/scripts/index-both.sh Outdated

@shrugs shrugs left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like there's some duplication going on between these two approaches (index and load, checkpoint). but actually they're the same thing, just with END_BLOCK configuration in the checkpoint case and no load, just dump/upload?

Comment thread .github/workflows/checkpoint.yml Outdated
Comment thread .github/workflows/checkpoint.yml Outdated
Comment thread .github/workflows/checkpoint_reaper.yml Outdated
Comment thread .github/workflows/deploy_ensnode_blue_green.yml Outdated
Comment thread .github/workflows/deploy_ensapi_hotfix.yml Outdated
Comment thread packages/ensdb-cli/src/lib/ensdb.ts Outdated
Comment thread packages/ensdb-cli/src/lib/pgtools.ts
Comment thread packages/ensdb-cli/tsup.config.ts
Comment thread packages/ensdb-cli/package.json Outdated
Comment thread packages/ensindexer-checkpoint/scripts/config.example.sh Outdated
Collapse the production (index+load) and dev (point-in-time checkpoint)
flows into one parameterized pipeline — they differ only by MODE
(full-backfill vs end-block), CONFIGS, and the load/seed flags. Delete the
duplicated remote-run.sh / remote-checkpoint-prod.sh / index-both.sh in
favor of a single on-box orchestrator (remote-checkpoint.sh) and a single
manual runner (checkpoint.sh).

ensdb-cli + ensdb-sdk:
- terminology → ensIndexerSchemaName throughout
- dump always writes a single-row metadata sidecar (no opt-out)
- load gains --force; default fail-fast if target schema/metadata exists;
  migrates the ensnode schema before restore; fails fast on unresolvable
  dump schema name
- move schema exists/drop/rename + metadata read/write helpers into
  ensdb-sdk (public); CLI consumes them
- bundle migrations into the CLI dist (tsup) so it works standalone
- subprocess timeout in exec(); commented pg_dump/pg_restore args; v1.16.0

workflows:
- new reusable .github/actions/deploy_railway_services composite action
  (update+redeploy a set of service ids to one image, ENSRainbow first)
- index_checkpoint_and_deploy: 3 phases — deploy light (sepolia/*) →
  index+load alpha/mainnet on Cherry → deploy heavy (alpha/mainnet);
  timeout 960m; null-SHA guard; inputs via env (no shell injection)
- deploy_ensnode_blue_green: rename inputs (version=schemas, tag=images),
  use the reusable action
- deploy_ensapi_hotfix: use the reusable action; drop unused id-token
- checkpoint.yml: drop chain_ids input (chains derived from config)
- rename checkpoint_reaper.yml → checkpoint_gc.yml (garbage collector)

box scripts hardening:
- ship secrets (TARGET_URL, Cherry token) via 600 files, not SSH argv
- cherry-down keeps the box handle when termination fails (no orphan)
- per-run TOFU known_hosts; R2 lock owner-token race check
- ensrainbow health gate requires a live health response; crash detection
  is process-liveness-first; numeric validation before end-block SQL
- end-block mode derives indexed chain ids from the config
  (scripts/print-indexed-chain-ids.ts)

docs/config: configs README → "Hosted Instance Configurations"; bump
SELF_DESTRUCT_HOURS to 20; delete the named-configs drift-guard test.
@shrugs

shrugs commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

@greptile review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 51 out of 52 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread .github/workflows/checkpoint.yml Outdated
Comment thread packages/ensindexer-checkpoint/scripts/remote-seed-export.sh Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 52 out of 53 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment thread packages/ensindexer-checkpoint/scripts/lib.sh
Comment thread packages/ensindexer-checkpoint/scripts/remote-rehydrate.sh
- write_rclone_conf: create the R2 credentials file mode 600 (subshell-scoped
  umask 077) so it isn't world-readable.
- remote-rehydrate.sh: add 'require pg_restore' for a clear early failure.
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io June 23, 2026 21:03 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io June 23, 2026 21:03 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io June 23, 2026 21:03 Inactive
@shrugs

shrugs commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

@greptile review

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/ensindexer-checkpoint/scripts/remote-index-one.sh (1)

79-82: 🩺 Stability & Availability | 🟡 Minor

CHAIN_IDS derivation can silently pass with partial output from a failed tsx script.

When the tsx script fails after emitting some chain IDs, the assignment CHAIN_IDS="$(... | paste -sd, -)" still succeeds because bash doesn't honor set -e for assignment failures — the captured partial output becomes the assignment value. The -n guard only checks for non-empty content, not successful exit, so the run proceeds with an incomplete chain set. This contradicts the deliberate pattern at lines 84–90, which explicitly captures to a file and checks the exit code to prevent silently indexing incomplete chains to realtime.

Capture to a temporary file and check the exit code explicitly to match the end-block resolver pattern:

Suggested fix
-  CHAIN_IDS="$(cd "$REPO_DIR/apps/ensindexer" &&
-    ENSRAINBOW_URL="http://localhost:$RAINBOW_PORT" ENSINDEXER_SCHEMA_NAME="$SCHEMA" \
-      pnpm exec tsx scripts/print-indexed-chain-ids.ts | paste -sd, -)"
-  [ -n "$CHAIN_IDS" ] || die "[$CONFIG] could not derive indexed chain ids from config"
+  CHAIN_IDS_OUT="/tmp/${SCHEMA}.chainids"
+  (cd "$REPO_DIR/apps/ensindexer" &&
+    ENSRAINBOW_URL="http://localhost:$RAINBOW_PORT" ENSINDEXER_SCHEMA_NAME="$SCHEMA" \
+      pnpm exec tsx scripts/print-indexed-chain-ids.ts) >"$CHAIN_IDS_OUT" ||
+    die "[$CONFIG] could not derive indexed chain ids from config"
+  CHAIN_IDS="$(paste -sd, - <"$CHAIN_IDS_OUT")"
+  [ -n "$CHAIN_IDS" ] || die "[$CONFIG] derived empty chain id list"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ensindexer-checkpoint/scripts/remote-index-one.sh` around lines 79 -
82, The CHAIN_IDS assignment using command substitution does not properly
validate the exit code of the tsx script—bash allows partial output to be
captured even when the script fails, and the subsequent non-empty check only
verifies content exists, not that the command succeeded. Refactor this block to
capture the output to a temporary file instead of command substitution,
explicitly check the exit code of the pnpm exec tsx
scripts/print-indexed-chain-ids.ts command, and only assign CHAIN_IDS if the
exit code indicates success. This approach should match the pattern already used
in the end-block resolver section around lines 84-90 to ensure failed chain ID
derivation is caught before proceeding.
packages/ensindexer-checkpoint/scripts/remote-rehydrate.sh (1)

42-48: 🗄️ Data Integrity & Integration | 🟠 Major

Prevent partial ponder_sync restores from being treated as complete.

Line 42 skips restore whenever ponder_sync schema exists. If Line 47 pg_restore fails mid-restore, the schema will exist in a partial/corrupted state. On retry, the schema existence check treats this as a complete restore and skips the restore step, leaving the system with a corrupted cache.

Suggested fix
 elif r2_exists "$(r2_seed "$R2_SEED_OBJECT")"; then
   log "downloading + restoring ponder_sync from R2 (large; be patient)"
-  rclone copy --progress "$(r2_seed "$R2_SEED_OBJECT")" "$DATA_MOUNT/"
-  pg_restore --no-owner --no-privileges -j 4 -d "$PG_CONN" "$DATA_MOUNT/$R2_SEED_OBJECT"
-  rm -f "$DATA_MOUNT/$R2_SEED_OBJECT"
+  seed_path="$DATA_MOUNT/$R2_SEED_OBJECT"
+  rclone copyto --progress "$(r2_seed "$R2_SEED_OBJECT")" "$seed_path"
+  if ! pg_restore --exit-on-error --no-owner --no-privileges -j 4 -d "$PG_CONN" "$seed_path"; then
+    warn "ponder_sync restore failed; dropping partial schema before retry"
+    pg -c 'DROP SCHEMA IF EXISTS ponder_sync CASCADE;' || true
+    rm -f "$seed_path"
+    die "failed restoring ponder_sync from seed"
+  fi
+  rm -f "$seed_path"
 else
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ensindexer-checkpoint/scripts/remote-rehydrate.sh` around lines 42 -
48, The schema existence check on line 42 (checking if ponder_sync exists) does
not account for partial/corrupted restores. If the pg_restore command on line 47
fails midway, the ponder_sync schema will exist in a corrupted state, and on
retry the existence check will incorrectly skip the restore step. Add error
handling after the pg_restore command that removes the ponder_sync schema if the
restore fails, ensuring the next retry will detect the schema as missing and
re-attempt the full restore from scratch. Alternatively, use a completion marker
file or flag that only gets set after a successful restore to track restore
completion rather than relying solely on schema existence.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/deploy_ensnode_blue_green.yml:
- Around line 49-54: The "Require version or tag" step directly interpolates
workflow_dispatch inputs into the shell script at lines 51-52, creating a shell
injection vulnerability where crafted inputs with metacharacters could break the
condition and inject arbitrary commands. Instead, pass the inputs through
environment variables using the env: keyword at the step level, then reference
those environment variables in the shell script using $VARIABLE_NAME syntax,
following the same safer pattern used for environment variables elsewhere in the
workflow.

---

Outside diff comments:
In `@packages/ensindexer-checkpoint/scripts/remote-index-one.sh`:
- Around line 79-82: The CHAIN_IDS assignment using command substitution does
not properly validate the exit code of the tsx script—bash allows partial output
to be captured even when the script fails, and the subsequent non-empty check
only verifies content exists, not that the command succeeded. Refactor this
block to capture the output to a temporary file instead of command substitution,
explicitly check the exit code of the pnpm exec tsx
scripts/print-indexed-chain-ids.ts command, and only assign CHAIN_IDS if the
exit code indicates success. This approach should match the pattern already used
in the end-block resolver section around lines 84-90 to ensure failed chain ID
derivation is caught before proceeding.

In `@packages/ensindexer-checkpoint/scripts/remote-rehydrate.sh`:
- Around line 42-48: The schema existence check on line 42 (checking if
ponder_sync exists) does not account for partial/corrupted restores. If the
pg_restore command on line 47 fails midway, the ponder_sync schema will exist in
a corrupted state, and on retry the existence check will incorrectly skip the
restore step. Add error handling after the pg_restore command that removes the
ponder_sync schema if the restore fails, ensuring the next retry will detect the
schema as missing and re-attempt the full restore from scratch. Alternatively,
use a completion marker file or flag that only gets set after a successful
restore to track restore completion rather than relying solely on schema
existence.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: feef34e0-ab91-4522-a9e1-4c12e1105072

📥 Commits

Reviewing files that changed from the base of the PR and between c43b9ad and e57c211.

📒 Files selected for processing (12)
  • .github/actions/deploy_railway_services/action.yml
  • .github/workflows/deploy_ensnode_blue_green.yml
  • .github/workflows/index_checkpoint_and_deploy.yml
  • packages/ensdb-cli/src/lib/migrations.ts
  • packages/ensindexer-checkpoint/.gitignore
  • packages/ensindexer-checkpoint/README.md
  • packages/ensindexer-checkpoint/scripts/checkpoint.sh
  • packages/ensindexer-checkpoint/scripts/lib.sh
  • packages/ensindexer-checkpoint/scripts/load-checkpoints.sh
  • packages/ensindexer-checkpoint/scripts/remote-checkpoint.sh
  • packages/ensindexer-checkpoint/scripts/remote-index-one.sh
  • packages/ensindexer-checkpoint/scripts/remote-rehydrate.sh

Comment thread .github/workflows/deploy_ensnode_blue_green.yml Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 51 out of 52 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

…2.yml

Leave the live deploy_ensnode_blue_green.yml untouched (restored to main); the
version/tag-split rewrite lives in a parallel v2 workflow to validate before swapping.
@github-actions github-actions Bot mentioned this pull request Jun 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 51 out of 52 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment on lines +56 to +60
(cd "$REPO_DIR/apps/ensrainbow" &&
DATA_DIR="$RAINBOW_DATA_DIR" LABEL_SET_ID="$LABEL_SET_ID" LABEL_SET_VERSION="$LABEL_SET_VERSION" \
PORT="$RAINBOW_PORT" pnpm run entrypoint >"/tmp/ensrainbow-$CONFIG.log" 2>&1) &
RAINBOW_MARKER="$RAINBOW_DATA_DIR/ensrainbow_db_ready"
rainbow_ready=0

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 51 out of 52 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

Comment on lines +105 to +108
// Upsert the metadata under the target schema (re-keyed by the writer).
if (metadata) {
await writer.writeEnsNodeMetadata(metadata);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants