Skip to content

Introduce indexing metadata context data model#1997

Draft
tk-o wants to merge 22 commits intomainfrom
feat/indexing-metadata-context
Draft

Introduce indexing metadata context data model#1997
tk-o wants to merge 22 commits intomainfrom
feat/indexing-metadata-context

Conversation

@tk-o
Copy link
Copy Markdown
Member

@tk-o tk-o commented Apr 24, 2026

Lite PR

Tip: Review docs on the ENSNode PR process

Summary

  • What changed (1-3 bullets, no essays).

Why

  • Why this change exists. Link to related GitHub issues where relevant.

Testing

  • How this was tested.
  • If you didn't test it, say why.

Notes for Reviewer (Optional)

Resolves #1884


Pre-Review Checklist (Blocking)

  • This PR does not introduce significant changes and is low-risk to review quickly.
  • Relevant changesets are included (or are not required)

Copilot AI review requested due to automatic review settings April 24, 2026 18:09
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 24, 2026

⚠️ No Changeset found

Latest commit: f59ae1c

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 24, 2026

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

3 Skipped Deployments
Project Deployment Actions Updated (UTC)
admin.ensnode.io Skipped Skipped Apr 26, 2026 3:38pm
ensnode.io Skipped Skipped Apr 26, 2026 3:38pm
ensrainbow.io Skipped Skipped Apr 26, 2026 3:38pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Consolidates ENSNode metadata into a single IndexingMetadataContext record and moves ENS DB writer startup out of the API entrypoint into an idempotent onchain-event initialization flow; many reader/writer, serialization, worker, and tests updated to use the new context shape.

Changes

Cohort / File(s) Summary
Ponder API entrypoint
apps/ensindexer/ponder/src/api/index.ts
Removed immediate ENS DB writer bootstrap call; route wiring and error handler unchanged.
Ponder event handling
apps/ensindexer/src/lib/indexing-engines/ponder.ts, apps/ensindexer/src/lib/indexing-engines/ponder.test.ts
Replaced ENSRainbow-gated precondition with idempotent onchain-event initialization; renamed event type to OnchainEvent; added module-level precondition state and dynamic import of initIndexingOnchainEvents; tests updated to mock/init accordingly.
Onchain initialization
apps/ensindexer/src/lib/indexing-engines/init-indexing-onchain-events.ts
Added initIndexingOnchainEvents() to migrate schema, wait for ENSRainbow health, gather configs/snapshot, construct & upsert IndexingMetadataContextInitialized, wait for readiness, and start ENS DB writer worker; logs and exitCode behavior on error.
IndexingMetadataContext model & schemas
packages/ensnode-sdk/src/ensnode/metadata/..., packages/ensnode-sdk/src/ensnode/metadata/index.ts, packages/ensnode-sdk/src/ensnode/index.ts
Introduced discriminated IndexingMetadataContext (Uninitialized/Initialized), Zod schemas, serialize/deserialize/validate pipelines, builders, and re-exported via metadata and ensnode barrels.
ENSDb SDK reader/writer & serialization
packages/ensdb-sdk/src/client/ensdb-reader.ts, packages/ensdb-sdk/src/client/ensdb-writer.ts, packages/ensdb-sdk/src/client/ensnode-metadata.ts, packages/ensdb-sdk/src/client/serialize/ensnode-metadata.ts
Replaced multi-key metadata API with a single IndexingMetadataContext record: added getIndexingMetadataContext() (returns uninitialized default if missing) and upsertIndexingMetadataContext(); removed separate version/public-config/status methods and simplified serialized metadata union.
ENSDb writer worker & mocks/tests
apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts, .../ensdb-writer-worker.mock.ts, .../ensdb-writer-worker.test.ts, apps/ensindexer/src/lib/ensdb-writer-worker/singleton.ts
Worker now fetches and upserts IndexingMetadataContextInitialized on interval; removed public-config, version, dev-mode and retry plumbing; mocks and tests refactored for metadata-context API; singleton instantiation updated to new constructor signature.
ENSRainbow readiness
apps/ensindexer/src/lib/ensrainbow/singleton.ts
Added waitForEnsRainbowToBeHealthy() with cached promise and retry logging; adjusted waitForEnsRainbowToBeReady() error handling to rethrow original error.
API handlers, cache & config
apps/ensindexer/ponder/src/api/handlers/ensnode-api.ts, apps/ensapi/src/cache/indexing-status.cache.ts, apps/ensapi/src/config/config.schema.ts, apps/ensapi/src/config/config.schema.test.ts
Handlers, cache loader, and config builders now read IndexingMetadataContext and gate on statusCode === Initialized; tests updated to mock/deserialize full serialized indexing metadata context for inputs.
SDK & client tests
packages/ensdb-sdk/src/client/ensdb-reader.test.ts, packages/ensdb-sdk/src/client/ensdb-writer.test.ts
Tests updated to validate new reader/writer behavior, Deserialize/Serialize flows, single-record semantics, and Drizzle execute mocking.
Integration orchestrator
packages/integration-test-env/src/orchestrator.ts
Polling now uses getIndexingMetadataContext() and considers omnichain readiness only when context is Initialized.

Sequence Diagram(s)

sequenceDiagram
    participant Ponder
    participant OnchainHandler as Onchain Event Handler
    participant InitModule as initIndexingOnchainEvents
    participant ENSRAIN as ENSRainbow
    participant ENSDB as ENS DB
    participant Worker as ENS DB Writer Worker

    Ponder->>OnchainHandler: receive OnchainEvent
    OnchainHandler->>InitModule: run preconditions (first call)
    InitModule->>ENSDB: migrate ENSNode schema
    InitModule->>ENSRAIN: wait for health (retry)
    ENSRAIN-->>InitModule: healthy
    InitModule->>ENSDB: fetch existing indexingMetadataContext & metadata
    InitModule->>ENSDB: fetch omnichain snapshot & configs
    InitModule->>InitModule: build IndexingMetadataContextInitialized
    InitModule->>ENSDB: upsert IndexingMetadataContext
    ENSDB-->>InitModule: upsert ok
    InitModule->>ENSRAIN: wait for ready
    ENSRAIN-->>InitModule: ready
    InitModule->>Worker: start ENS DB writer worker
    Worker->>ENSDB: periodically upsert updated IndexingMetadataContext
    OnchainHandler-->>Ponder: continue handling event
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through commits and stitched one gentle thread,
One metadata burrow where snapshots rest their head.
From event to worker, the checks now run once through,
A tidy little burrow — made fresh for me and you. 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description uses the template structure but all key sections (Summary, Why, Testing, Checklist items) are incomplete or unfilled, containing only placeholders and lacking substantive implementation details. Complete the Summary, Why, and Testing sections with specific details about the changes, rationale linking to issue #1884, and testing approach. Check both pre-review checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Introduce indexing metadata context data model' clearly and specifically summarizes the main change: the introduction of a new indexing metadata context data model to consolidate ENSNode Metadata records.
Linked Issues check ✅ Passed The PR fully implements the objectives from #1884: defines a consolidated IndexingMetadataContext model, ensures one metadata record per schema via upsert, stores initialized/uninitialized status variants, captures current indexing status and stack info, and simplifies metadata record definition/updates.
Out of Scope Changes check ✅ Passed All changes are scoped to introducing the indexing metadata context model and refactoring dependent components to use it. No unrelated modifications detected.

✏️ 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/indexing-metadata-context

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 and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces a new IndexingMetadataContext data model in @ensnode/ensnode-sdk to standardize how indexing metadata context is represented, validated, and (de)serialized, and refactors ENSIndexer’s Ponder initialization/preconditions into dedicated modules.

Changes:

  • Added IndexingMetadataContext (Uninitialized/Initialized) with Zod schemas plus serialize/deserialize/validate helpers in @ensnode/ensnode-sdk.
  • Refactored Ponder event-handler preconditions into init-indexing-setup and init-indexing-onchain-events, and adjusted event type IDs.
  • Removed ENSNode schema migration step from the Ponder API entrypoint (now triggered via indexing setup preconditions).

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
packages/ensnode-sdk/src/ensnode/metadata/zod-schemas/indexing-metadata-context.ts Zod schemas for serialized + runtime metadata context variants
packages/ensnode-sdk/src/ensnode/metadata/validate/indexing-metadata-context.ts Validator helper for initialized context
packages/ensnode-sdk/src/ensnode/metadata/serialize/indexing-metadata-context.ts Serialization helpers/types for metadata context
packages/ensnode-sdk/src/ensnode/metadata/deserialize/indexing-metadata-context.ts Deserialization pipeline from serialized context into validated runtime context
packages/ensnode-sdk/src/ensnode/metadata/indexing-metadata-context.ts Core types and builders for metadata context
packages/ensnode-sdk/src/ensnode/metadata/index.ts Barrel export for metadata module
packages/ensnode-sdk/src/ensnode/index.ts Re-exports the new metadata module from ensnode SDK
apps/ensindexer/src/lib/indexing-engines/ponder.ts Refactors preconditions logic; splits setup/onchain initialization
apps/ensindexer/src/lib/indexing-engines/init-indexing-setup.ts New setup initialization module (runs ENSNode schema migrations)
apps/ensindexer/src/lib/indexing-engines/init-indexing-onchain-events.ts New onchain initialization module (waits for ENSRainbow readiness)
apps/ensindexer/src/lib/indexing-engines/ponder.test.ts Updates tests to account for new precondition/migration mocking and PONDER_COMMON setup
apps/ensindexer/ponder/src/api/index.ts Removes ENSNode schema migration before starting writer worker

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

Comment thread apps/ensindexer/src/lib/indexing-engines/ponder.ts Outdated
Comment thread apps/ensindexer/ponder/src/api/index.ts Outdated
Comment thread apps/ensindexer/src/lib/indexing-engines/init-indexing-setup.ts Outdated
Comment thread packages/ensnode-sdk/src/ensnode/metadata/indexing-metadata-context.ts Outdated
Comment thread packages/ensnode-sdk/src/ensnode/metadata/validate/indexing-metadata-context.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
apps/ensindexer/src/lib/indexing-engines/ponder.test.ts (1)

10-51: 🧹 Nitpick | 🔵 Trivial

Add a test for the migration-failure precondition path.

mockMigrateEnsNodeSchema is wired up, but no test asserts what happens when initIndexingSetup() fails. Given this precondition runs before every setup handler and previously lived elsewhere, a regression test covering the rejection flow (setup handler is not invoked, error propagates from the registered callback) would close the coverage gap and catch any future changes to the setup branch of eventHandlerPreconditions.

♻️ Suggested test skeleton
describe("ENSNode schema migration preconditions (setup events)", () => {
  it("propagates migration failure and prevents setup handler execution", async () => {
    const { addOnchainEventListener } = await getPonderModule();
    const handler = vi.fn().mockResolvedValue(undefined);
    mockMigrateEnsNodeSchema.mockRejectedValue(new Error("migration failed"));

    addOnchainEventListener("Registry:setup" as EventNames, handler);

    await expect(
      getRegisteredCallback()({
        context: { db: vi.fn() } as unknown as Context<EventNames>,
        event: {} as IndexingEngineEvent<EventNames>,
      }),
    ).rejects.toThrow("migration failed");

    expect(handler).not.toHaveBeenCalled();
  });

  it("runs migration only once across multiple setup events", async () => {
    const { addOnchainEventListener } = await getPonderModule();
    const handler = vi.fn().mockResolvedValue(undefined);

    addOnchainEventListener("Registry:setup" as EventNames, handler);
    addOnchainEventListener("PublicResolver:setup" as EventNames, handler);

    await getRegisteredCallback(0)({
      context: { db: vi.fn() } as unknown as Context<EventNames>,
      event: {} as IndexingEngineEvent<EventNames>,
    });
    await getRegisteredCallback(1)({
      context: { db: vi.fn() } as unknown as Context<EventNames>,
      event: {} as IndexingEngineEvent<EventNames>,
    });

    expect(mockMigrateEnsNodeSchema).toHaveBeenCalledTimes(1);
  });
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensindexer/src/lib/indexing-engines/ponder.test.ts` around lines 10 -
51, Add tests exercising the migration-failure precondition in apps/ensindexer
by wiring mockMigrateEnsNodeSchema to reject and asserting initIndexingSetup's
failure prevents the setup handler from running and that the error propagates;
specifically, in the test suite using getPonderModule(), call
addOnchainEventListener("Registry:setup", handler) with handler = vi.fn(), make
mockMigrateEnsNodeSchema.mockRejectedValue(new Error("migration failed")),
invoke the registered callback via getRegisteredCallback() and expect it to
reject with that error and that handler was not called; also add a test that
registers two setup listeners (e.g., "Registry:setup" and
"PublicResolver:setup"), run both registered callbacks via
getRegisteredCallback(0) and getRegisteredCallback(1), and assert
mockMigrateEnsNodeSchema was called exactly once to ensure migration runs only
once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensindexer/src/lib/indexing-engines/init-indexing-setup.ts`:
- Line 42: Add a one-line comment above the dynamic import of
migrateEnsNodeSchema in initIndexingSetup explaining that the import is deferred
to avoid triggering the ensDbClient singleton's connection at module load time;
reference migrateEnsNodeSchema and ensDbClient in the comment so future
maintainers understand the side-effect and don't convert it to a static import.
- Around line 44-49: The log emitted in the migrateEnsNodeSchema() catch block
is using the wrong module string and an inaccurate message; update the
logger.error call in the migrateEnsNodeSchema().catch(...) handler to use the
correct module identifier (e.g., "ensindexer" or another appropriate module
name) and change the msg to accurately reflect the operation (e.g., "Failed to
migrate ENSNode schema" or similar), keeping the error payload intact so the
error details are preserved.
- Around line 41-53: Update the log message inside initIndexingSetup's
migrateEnsNodeSchema() catch to accurately describe the operation (e.g., "Failed
to run ENSNode schema migrations") and change the logger metadata to the correct
module label for the indexing layer (remove or replace module: "ponder-api" with
something like module: "indexing-engines"); retain logging of the error object.
Also add a brief comment above the dynamic import of migrateEnsNodeSchema
explaining why it is imported dynamically (to defer singleton/DB connection
initialization until after ENSDb is ready) so future readers understand the
rationale.

In
`@packages/ensnode-sdk/src/ensnode/metadata/deserialize/indexing-metadata-context.ts`:
- Around line 23-54: Rename the two helpers to drop the misleading "Schema"
suffix: change buildUnvalidatedIndexingMetadataContextSchema ->
buildUnvalidatedIndexingMetadataContext and
buildUnvalidatedIndexingMetadataContextInitializedSchema ->
buildUnvalidatedIndexingMetadataContextInitialized; update all internal
references/usages (including the .transform(...) call that currently passes
buildUnvalidatedIndexingMetadataContext and any imports/exports) and update the
JSDoc {`@link` ...} that references the old name to point to
buildUnvalidatedIndexingMetadataContext so names match the SDK convention (e.g.,
buildUnvalidatedCrossChainIndexingStatusSnapshot,
buildUnvalidatedEnsNodeStackInfo).
- Around line 35-44: Update the JSDoc for
buildUnvalidatedIndexingMetadataContextSchema: remove the redundant `@return` tag
that restates the summary and, if present, correct any incorrect type mention
(remove "IndexingMetadataContextInitialized") so the docs reflect the actual
return type Unvalidated<IndexingMetadataContext>; keep the summary and param
tags but drop the misleading/duplicative `@return` line to comply with the coding
guideline.

---

Outside diff comments:
In `@apps/ensindexer/src/lib/indexing-engines/ponder.test.ts`:
- Around line 10-51: Add tests exercising the migration-failure precondition in
apps/ensindexer by wiring mockMigrateEnsNodeSchema to reject and asserting
initIndexingSetup's failure prevents the setup handler from running and that the
error propagates; specifically, in the test suite using getPonderModule(), call
addOnchainEventListener("Registry:setup", handler) with handler = vi.fn(), make
mockMigrateEnsNodeSchema.mockRejectedValue(new Error("migration failed")),
invoke the registered callback via getRegisteredCallback() and expect it to
reject with that error and that handler was not called; also add a test that
registers two setup listeners (e.g., "Registry:setup" and
"PublicResolver:setup"), run both registered callbacks via
getRegisteredCallback(0) and getRegisteredCallback(1), and assert
mockMigrateEnsNodeSchema was called exactly once to ensure migration runs only
once.
🪄 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: 2d30f2e4-8a4d-4371-a2b0-cbc81e5b5876

📥 Commits

Reviewing files that changed from the base of the PR and between 0d8a4b4 and a5a8bd6.

📒 Files selected for processing (12)
  • apps/ensindexer/ponder/src/api/index.ts
  • apps/ensindexer/src/lib/indexing-engines/init-indexing-onchain-events.ts
  • apps/ensindexer/src/lib/indexing-engines/init-indexing-setup.ts
  • apps/ensindexer/src/lib/indexing-engines/ponder.test.ts
  • apps/ensindexer/src/lib/indexing-engines/ponder.ts
  • packages/ensnode-sdk/src/ensnode/index.ts
  • packages/ensnode-sdk/src/ensnode/metadata/deserialize/indexing-metadata-context.ts
  • packages/ensnode-sdk/src/ensnode/metadata/index.ts
  • packages/ensnode-sdk/src/ensnode/metadata/indexing-metadata-context.ts
  • packages/ensnode-sdk/src/ensnode/metadata/serialize/indexing-metadata-context.ts
  • packages/ensnode-sdk/src/ensnode/metadata/validate/indexing-metadata-context.ts
  • packages/ensnode-sdk/src/ensnode/metadata/zod-schemas/indexing-metadata-context.ts
💤 Files with no reviewable changes (1)
  • apps/ensindexer/ponder/src/api/index.ts

Comment thread apps/ensindexer/src/lib/indexing-engines/init-indexing-setup.ts Outdated
Comment thread apps/ensindexer/src/lib/indexing-engines/init-indexing-setup.ts Outdated
Comment thread apps/ensindexer/src/lib/indexing-engines/init-indexing-setup.ts Outdated
Comment thread apps/ensindexer/src/lib/indexing-engines/init-indexing-setup.ts Outdated
tk-o added 2 commits April 24, 2026 20:55
It will just have a single recurring task to keep the stored `IndexingMetadataContext` up to date
This one was complaining about `ponder:api` imports being made from outside of `apps/ensindexer/ponder/src/api` dir. The dynamic imports solve that problem.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 17 out of 17 changed files in this pull request and generated 9 comments.

Comments suppressed due to low confidence (1)

apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts:75

  • This file still imports and carries fields/docs for the previous worker responsibilities (pRetry/config validation, upserting ENSDb version/public config, etc.), but the implementation now only schedules IndexingMetadataContext upserts. Please remove now-unused imports/types/class fields and update the class JSDoc to match current behavior; otherwise this is likely to trip linting/TS unused checks and leaves misleading documentation.
import { getUnixTime, secondsToMilliseconds } from "date-fns";
import type { Duration } from "enssdk";
import pRetry from "p-retry";

import type { EnsDbWriter } from "@ensnode/ensdb-sdk";
import {
  buildCrossChainIndexingStatusSnapshotOmnichain,
  buildIndexingMetadataContextInitialized,
  type CrossChainIndexingStatusSnapshot,
  type EnsIndexerPublicConfig,
  IndexingMetadataContextStatusCodes,
  OmnichainIndexingStatusIds,
  type OmnichainIndexingStatusSnapshot,
  validateEnsIndexerPublicConfigCompatibility,
} from "@ensnode/ensnode-sdk";
import type { LocalPonderClient } from "@ensnode/ponder-sdk";

import type { IndexingStatusBuilder } from "@/lib/indexing-status-builder/indexing-status-builder";
import { logger } from "@/lib/logger";
import type { PublicConfigBuilder } from "@/lib/public-config-builder/public-config-builder";

/**
 * Interval in seconds between two consecutive attempts to upsert
 * the Indexing Status Snapshot record into ENSDb.
 */
const INDEXING_STATUS_RECORD_UPDATE_INTERVAL: Duration = 1;

/**
 * ENSDb Writer Worker
 *
 * A worker responsible for writing ENSIndexer-related metadata into ENSDb, including:
 * - ENSDb version
 * - ENSIndexer Public Config
 * - ENSIndexer Indexing Status Snapshots
 */
export class EnsDbWriterWorker {
  /**
   * Interval for recurring upserts of Indexing Status Snapshots into ENSDb.
   */
  private indexingStatusInterval: ReturnType<typeof setInterval> | null = null;

  /**
   * ENSDb Client instance used by the worker to interact with ENSDb.
   */
  private ensDbClient: EnsDbWriter;

  /**
   * Indexing Status Builder instance used by the worker to read ENSIndexer Indexing Status.
   */
  private indexingStatusBuilder: IndexingStatusBuilder;

  /**
   * ENSIndexer Public Config Builder instance used by the worker to read ENSIndexer Public Config.
   */
  private publicConfigBuilder: PublicConfigBuilder;

  /**
   * Local Ponder Client instance
   *
   * Used to get local Ponder app command.
   */
  private localPonderClient: LocalPonderClient;

  /**
   * @param ensDbClient ENSDb Writer instance used by the worker to interact with ENSDb.
   * @param publicConfigBuilder ENSIndexer Public Config Builder instance used by the worker to read ENSIndexer Public Config.
   * @param indexingStatusBuilder Indexing Status Builder instance used by the worker to read ENSIndexer Indexing Status.
   * @param localPonderClient Local Ponder Client instance, used to get local Ponder app command.
   */
  constructor(
    ensDbClient: EnsDbWriter,
    publicConfigBuilder: PublicConfigBuilder,
    indexingStatusBuilder: IndexingStatusBuilder,
    localPonderClient: LocalPonderClient,
  ) {

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

Comment thread apps/ensindexer/src/lib/indexing-engines/init-indexing-onchain-events.ts Outdated
Comment thread apps/ensindexer/src/lib/indexing-engines/init-indexing-onchain-events.ts Outdated
Comment thread packages/ensnode-sdk/src/ensnode/metadata/indexing-metadata-context.ts Outdated
Comment thread apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts Outdated
Comment thread apps/ensindexer/src/lib/indexing-engines/ponder.test.ts Outdated
Comment thread packages/ensdb-sdk/src/client/serialize/ensnode-metadata.ts
Comment thread packages/ensdb-sdk/src/client/ensnode-metadata.ts
Comment thread packages/ensnode-sdk/src/ensnode/metadata/validate/indexing-metadata-context.ts Outdated
Comment thread packages/ensnode-sdk/src/ensnode/metadata/indexing-metadata-context.ts Outdated
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io April 24, 2026 20:37 Inactive
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts (1)

17-31: 🧹 Nitpick | 🔵 Trivial

Rename stale INDEXING_STATUS_RECORD_* / indexingStatusInterval identifiers.

After the migration to a single IndexingMetadataContext record, these identifiers (constant INDEXING_STATUS_RECORD_UPDATE_INTERVAL with its JSDoc, and field indexingStatusInterval with its JSDoc on line 29) still describe an "Indexing Status Snapshot record". Renaming them to reference the IndexingMetadataContext (e.g., INDEXING_METADATA_CONTEXT_UPDATE_INTERVAL, indexingMetadataContextInterval) keeps the worker internals consistent with the new data model and avoids confusion when reading the file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts` around
lines 17 - 31, Rename the stale identifiers that reference "Indexing Status
Snapshot" to reflect the new single IndexingMetadataContext model: change the
constant INDEXING_STATUS_RECORD_UPDATE_INTERVAL (and its JSDoc) to
INDEXING_METADATA_CONTEXT_UPDATE_INTERVAL and update its JSDoc text accordingly,
and rename the class field indexingStatusInterval (and its JSDoc) to
indexingMetadataContextInterval; update all usages of these symbols within
EnsDbWriterWorker (and related comments) so names consistently reference
IndexingMetadataContext.
♻️ Duplicate comments (2)
packages/ensnode-sdk/src/ensnode/metadata/deserialize/indexing-metadata-context.ts (1)

67-69: ⚠️ Potential issue | 🟡 Minor

Error message says "Cannot validate" in a deserialize function, and ignores valueLabel.

This was previously flagged but the current code still says Cannot validate IndexingMetadataContext. For a deserialize* function the prefix should be Cannot deserialize ..., and using label (instead of the hardcoded type name) keeps the outer header consistent with the schema's inner field labels when a custom valueLabel is provided.

✏️ Proposed fix
   if (parsed.error) {
-    throw new Error(`Cannot validate IndexingMetadataContext:\n${prettifyError(parsed.error)}\n`);
+    throw new Error(`Cannot deserialize ${label}:\n${prettifyError(parsed.error)}\n`);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/ensnode-sdk/src/ensnode/metadata/deserialize/indexing-metadata-context.ts`
around lines 67 - 69, The error thrown in the deserialize path currently says
"Cannot validate IndexingMetadataContext" and hardcodes the type name; in the
deserialize function handling parsed.error (where parsed.error and prettifyError
are used) change the message to use the deserialize prefix and the dynamic label
(use the function's valueLabel/label variable) so it reads like "Cannot
deserialize {label}:\n{prettifyError(parsed.error)}"; update the throw in the
deserialize* function that references IndexingMetadataContext to build the
message from label/valueLabel instead of the hardcoded type name.
apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts (1)

92-108: ⚠️ Potential issue | 🟡 Minor

Stale JSDoc, typo, and stale identifier names — previously flagged.

These were called out on a prior commit but are still present:

  • Line 93 JSDoc: still says Upsert the current Indexing Status Snapshot into ENSDb. — should reference the IndexingMetadataContext.
  • Line 106 error message: typo should be be initialized first (duplicated "be").
  • Lines 125–126 comment still talks about "failure to retrieve the Indexing Status" — should reference the metadata context upsert flow.
📝 Proposed fix
   /**
-   * Upsert the current Indexing Status Snapshot into ENSDb.
+   * Upsert the current Indexing Metadata Context into ENSDb.
    *
    * This method is called by the scheduler at regular intervals.
    * Errors are logged but not thrown, to keep the worker running.
    */
   private async upsertIndexingMetadataContext(): Promise<void> {
     try {
       // get system timestamp for the current iteration
       const snapshotTime = getUnixTime(new Date());
       const indexingMetadataContext = await this.ensDbClient.getIndexingMetadataContext();

       if (indexingMetadataContext.statusCode === IndexingMetadataContextStatusCodes.Uninitialized) {
         throw new Error(
-          `Cannot upsert Indexing Status Snapshot into ENSDb because Indexing Metadata Context should be be initialized first`,
+          `Cannot upsert Indexing Metadata Context into ENSDb because it must be initialized first`,
         );
       }
@@
     } catch (error) {
       logger.error({
         msg: "Failed to upsert indexing metadata context",
         error,
         module: "EnsDbWriterWorker",
       });
-      // Do not throw the error, as failure to retrieve the Indexing Status
-      // should not cause the ENSDb Writer Worker to stop functioning.
+      // Do not throw the error, as failure to upsert the Indexing Metadata
+      // Context should not cause the ENSDb Writer Worker to stop functioning.
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts` around
lines 92 - 108, The JSDoc and inline messages in upsertIndexingMetadataContext()
are stale/typoed: update the method JSDoc to reference "IndexingMetadataContext"
(not "Indexing Status Snapshot"), fix the duplicated word in the thrown Error
message inside upsertIndexingMetadataContext() (remove the extra "be"), and
revise any comments that mention "failure to retrieve the Indexing Status" to
instead reference the metadata context upsert flow; locate these texts around
the method upsertIndexingMetadataContext, the thrown Error that uses
IndexingMetadataContextStatusCodes.Uninitialized, and any adjacent comments
inside the same method to update wording accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts`:
- Around line 100-118: Parallelize the two independent async calls by kicking
them off together with Promise.all: call getIndexingMetadataContext() and
getOmnichainIndexingStatusSnapshot() concurrently, await Promise.all to receive
[indexingMetadataContext, omnichainSnapshot], then perform the existing check on
indexingMetadataContext.statusCode and build the updated context with
buildCrossChainIndexingStatusSnapshotOmnichain(snapshotTime) and
buildIndexingMetadataContextInitialized before calling
ensDbClient.upsertIndexingMetadataContext; this replaces the sequential awaits
of getIndexingMetadataContext and getOmnichainIndexingStatusSnapshot while
preserving the existing validation and upsert flow.

In `@packages/ensnode-sdk/src/ensnode/metadata/indexing-metadata-context.ts`:
- Around line 63-72: The builder buildIndexingMetadataContextInitialized should
accept an optional valueLabel parameter and pass it through to
validateIndexingMetadataContextInitialized; update the function signature to add
valueLabel?: string and forward it in the call
(validateIndexingMetadataContextInitialized({...}, valueLabel)) so the builder
API matches the validator/deserializer behavior and preserves error-labeling
consistency for IndexingMetadataContextInitialized.

In
`@packages/ensnode-sdk/src/ensnode/metadata/validate/indexing-metadata-context.ts`:
- Around line 18-22: The thrown error message currently hardcodes
"IndexingMetadataContextInitialized", which can mismatch a custom valueLabel;
update the throw in the validation branch to use the valueLabel (falling back to
the existing default) instead of the hardcoded string so the error header
matches the schema's inner field labels — modify the throw in the function that
checks result.error in validate/indexing-metadata-context.ts to interpolate
valueLabel (or its default) into the error prefix (ensure valueLabel is in scope
or passed through where the validation occurs).

---

Outside diff comments:
In `@apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts`:
- Around line 17-31: Rename the stale identifiers that reference "Indexing
Status Snapshot" to reflect the new single IndexingMetadataContext model: change
the constant INDEXING_STATUS_RECORD_UPDATE_INTERVAL (and its JSDoc) to
INDEXING_METADATA_CONTEXT_UPDATE_INTERVAL and update its JSDoc text accordingly,
and rename the class field indexingStatusInterval (and its JSDoc) to
indexingMetadataContextInterval; update all usages of these symbols within
EnsDbWriterWorker (and related comments) so names consistently reference
IndexingMetadataContext.

---

Duplicate comments:
In `@apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts`:
- Around line 92-108: The JSDoc and inline messages in
upsertIndexingMetadataContext() are stale/typoed: update the method JSDoc to
reference "IndexingMetadataContext" (not "Indexing Status Snapshot"), fix the
duplicated word in the thrown Error message inside
upsertIndexingMetadataContext() (remove the extra "be"), and revise any comments
that mention "failure to retrieve the Indexing Status" to instead reference the
metadata context upsert flow; locate these texts around the method
upsertIndexingMetadataContext, the thrown Error that uses
IndexingMetadataContextStatusCodes.Uninitialized, and any adjacent comments
inside the same method to update wording accordingly.

In
`@packages/ensnode-sdk/src/ensnode/metadata/deserialize/indexing-metadata-context.ts`:
- Around line 67-69: The error thrown in the deserialize path currently says
"Cannot validate IndexingMetadataContext" and hardcodes the type name; in the
deserialize function handling parsed.error (where parsed.error and prettifyError
are used) change the message to use the deserialize prefix and the dynamic label
(use the function's valueLabel/label variable) so it reads like "Cannot
deserialize {label}:\n{prettifyError(parsed.error)}"; update the throw in the
deserialize* function that references IndexingMetadataContext to build the
message from label/valueLabel instead of the hardcoded type name.
🪄 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: fbf20fd0-a69b-4b70-b016-e8446280a039

📥 Commits

Reviewing files that changed from the base of the PR and between 4684fb4 and 1ff960e.

📒 Files selected for processing (4)
  • apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts
  • packages/ensnode-sdk/src/ensnode/metadata/deserialize/indexing-metadata-context.ts
  • packages/ensnode-sdk/src/ensnode/metadata/indexing-metadata-context.ts
  • packages/ensnode-sdk/src/ensnode/metadata/validate/indexing-metadata-context.ts

Comment thread apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 27 out of 27 changed files in this pull request and generated 5 comments.


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

Comment thread packages/ensdb-sdk/src/client/serialize/ensnode-metadata.ts
Comment thread apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts Outdated
Comment thread apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.ts Outdated
Comment thread apps/ensindexer/src/lib/ensdb-writer-worker/ensdb-writer-worker.test.ts Outdated
Comment thread apps/ensindexer/src/lib/indexing-engines/ponder.ts
tk-o added 2 commits April 26, 2026 14:48
Refactor logic into `IndexingMetadataContextBuilder` class and `StackInfoBuilder` class
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 33 out of 33 changed files in this pull request and generated 5 comments.


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

* Update the current Indexing Status Snapshot into ENSDb.
*
* This method is called by the scheduler at regular intervals.
* Errors are logged but not thrown, to keep the worker running.
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

The doc comment says errors are “not thrown, to keep the worker running”, but updateIndexingMetadataContext() sets process.exitCode and rethrows. Please align documentation and behavior (either swallow/log errors, or intentionally stop the worker/exit in a controlled way).

Suggested change
* Errors are logged but not thrown, to keep the worker running.
* Errors are logged, the process exit code is set to a non-zero value,
* and the error is rethrown because this is treated as a critical failure.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +18
const result = makeIndexingMetadataContextInitializedSchema(valueLabel).safeParse(
maybeIndexingMetadataContext,
);

if (result.error) {
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

Repo’s validator helpers typically branch on safeParse’s success flag (e.g. validateEnsIndexerPublicConfig in packages/ensnode-sdk/src/ensindexer/config/validate/ensindexer-public-config.ts). For consistency and clarity, prefer if (!result.success) here instead of checking result.error directly.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to 4
import type { EnsNodeMetadata, EnsNodeMetadataKeys } from "../ensnode-metadata";

Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

EnsNodeMetadata is imported but only referenced in a JSDoc {@link ...}; this will be treated as an unused import by Biome/TS and can fail lint/CI. Remove EnsNodeMetadata from the import, or reference it via an import()-style JSDoc link that doesn’t require a TS import.

Copilot uses AI. Check for mistakes.
Comment on lines 23 to 27
// Invariant: the public config is guaranteed to be available in ENSDb after
// application startup.
if (typeof publicConfig === "undefined") {
if (indexingMetadataContext.statusCode !== IndexingMetadataContextStatusCodes.Initialized) {
throw new Error("Unreachable: ENSIndexer Public Config is not available in ENSDb");
}
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

/config can observe an uninitialized indexing metadata context shortly after startup with the new init flow. Throwing an “Unreachable” error here turns that expected transient state into a 500 without a structured response. Prefer returning a non-500 response (e.g. 503) or an explicit error payload while the context is still uninitialized.

Copilot uses AI. Check for mistakes.
// Recurring update of the Indexing Metadata Context record in ENSDb.
this.indexingStatusInterval = setInterval(
() => this.upsertIndexingStatusSnapshot(),
() => this.updateIndexingMetadataContext(),
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

setInterval(() => this.updateIndexingMetadataContext()) invokes an async function without handling the returned promise. If updateIndexingMetadataContext() rejects (and it currently rethrows), this becomes an unhandled rejection and can crash the process depending on Node settings. Either swallow/log errors inside the callback (to keep the worker running) or explicitly stop the interval / exit the process, but avoid leaving a floating rejected promise.

Suggested change
() => this.updateIndexingMetadataContext(),
() => {
void this.updateIndexingMetadataContext().catch(() => {
// The update method already logs the error and sets a non-zero exit code.
// Stop the interval so the process can terminate cleanly without leaving
// a floating rejected promise behind.
this.stop();
});
},

Copilot uses AI. Check for mistakes.
@tk-o tk-o force-pushed the feat/indexing-metadata-context branch from 0cb1767 to 98e6c45 Compare April 26, 2026 13:14
@vercel vercel Bot temporarily deployed to Preview – ensnode.io April 26, 2026 13:14 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 26, 2026 13:14 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io April 26, 2026 13:14 Inactive
Copilot AI review requested due to automatic review settings April 26, 2026 15:38
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io April 26, 2026 15:38 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io April 26, 2026 15:38 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io April 26, 2026 15:38 Inactive
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 33 out of 33 changed files in this pull request and generated 4 comments.


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

Comment on lines 89 to 113
/**
* Get validated ENSIndexer Public Config object for the ENSDb Writer Worker.
*
* The function retrieves the ENSIndexer Public Config object from both:
* - stored config in ENSDb, if available, and
* - in-memory config from ENSIndexer Client.
*
* If a stored config exists **and** the local Ponder app is **not** in dev
* mode, the in-memory config is validated for compatibility against the
* stored one. Validation is skipped if the local Ponder app is in dev mode,
* allowing to override the stored config in ENSDb with the current in-memory
* config, without having to keep them compatible.
*
* @returns The in-memory config when validation passes or no stored config
* exists.
* @throws Error if either fetch fails, or if the in-memory config is
* incompatible with the stored config.
*/
private async getValidatedEnsIndexerPublicConfig(): Promise<EnsIndexerPublicConfig> {
/**
* Fetch the in-memory config with retries, to handle potential transient errors
* in the ENSIndexer Public Config Builder (e.g. due to network issues).
* If the fetch fails after the defined number of retries, the error
* will be thrown and the worker will not start, as the ENSIndexer Public Config
* is a critical dependency for the worker's tasks.
*/
const configFetchRetries = 3;

logger.debug({
msg: "Fetching ENSIndexer public config",
retries: configFetchRetries,
module: "EnsDbWriterWorker",
});

const inMemoryConfigPromise = pRetry(() => this.publicConfigBuilder.getPublicConfig(), {
retries: configFetchRetries,
onFailedAttempt: ({ attemptNumber, retriesLeft }) => {
logger.warn({
msg: "Config fetch attempt failed",
attempt: attemptNumber,
retriesLeft,
module: "EnsDbWriterWorker",
});
},
});

let storedConfig: EnsIndexerPublicConfig | undefined;
let inMemoryConfig: EnsIndexerPublicConfig;

try {
[storedConfig, inMemoryConfig] = await Promise.all([
this.ensDbClient.getEnsIndexerPublicConfig(),
inMemoryConfigPromise,
]);
logger.info({
msg: "Fetched ENSIndexer public config",
module: "EnsDbWriterWorker",
config: inMemoryConfig,
});
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";

logger.error({
msg: "Failed to fetch ENSIndexer public config",
error,
module: "EnsDbWriterWorker",
});

// Throw the error to terminate the ENSIndexer process due to failed fetch of critical dependency
throw new Error(errorMessage, {
cause: error,
});
}

// Validate in-memory config object compatibility with the stored one,
// if the stored one is available.
// The validation is skipped if the local Ponder app is running in dev mode.
// This is to improve the development experience during ENSIndexer
// development, by allowing to override the stored config in ENSDb with
// the current in-memory config, without having to keep them compatible.
if (storedConfig && !this.localPonderClient.isInDevMode) {
try {
validateEnsIndexerPublicConfigCompatibility(storedConfig, inMemoryConfig);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";

logger.error({
msg: "In-memory config incompatible with stored config",
error,
module: "EnsDbWriterWorker",
});

// Throw the error to terminate the ENSIndexer process due to
// found config incompatibility
throw new Error(errorMessage, {
cause: error,
});
}
}

return inMemoryConfig;
}

/**
* Upsert the current Indexing Status Snapshot into ENSDb.
* Update the current Indexing Status Snapshot into ENSDb.
*
* This method is called by the scheduler at regular intervals.
* Errors are logged but not thrown, to keep the worker running.
*/
private async upsertIndexingStatusSnapshot(): Promise<void> {
private async updateIndexingMetadataContext(): Promise<void> {
try {
// get system timestamp for the current iteration
const snapshotTime = getUnixTime(new Date());

const omnichainSnapshot = await this.getValidatedIndexingStatusSnapshot();

const crossChainSnapshot = buildCrossChainIndexingStatusSnapshotOmnichain(
omnichainSnapshot,
snapshotTime,
);
const indexingMetadataContext =
await this.indexingMetadataContextBuilder.getIndexingMetadataContext();

await this.ensDbClient.upsertIndexingStatusSnapshot(crossChainSnapshot);
await this.ensDbClient.upsertIndexingMetadataContext(indexingMetadataContext);
} catch (error) {
// If any error happens during the update of indexing metadata context record in ENSDb,
// we want to log the error and exit the process with a non-zero exit code,
// since this is a critical failure that prevents the ENSIndexer instance from functioning properly.
logger.error({
msg: "Failed to upsert indexing status snapshot",
error,
msg: "Failed to update indexing metadata context record in ENSDb",
module: "EnsDbWriterWorker",
error,
});
// Do not throw the error, as failure to retrieve the Indexing Status
// should not cause the ENSDb Writer Worker to stop functioning.
}
}

/**
* Get validated Omnichain Indexing Status Snapshot
*
* @returns Validated Omnichain Indexing Status Snapshot.
* @throws Error if the Omnichain Indexing Status is not in expected status yet.
*/
private async getValidatedIndexingStatusSnapshot(): Promise<OmnichainIndexingStatusSnapshot> {
const omnichainSnapshot = await this.indexingStatusBuilder.getOmnichainIndexingStatusSnapshot();

// It only makes sense to write Indexing Status Snapshots into ENSDb once
// the indexing process has started, as before that there is no meaningful
// status to record.
// Invariant: the Omnichain Status must indicate that indexing has started already.
if (omnichainSnapshot.omnichainStatus === OmnichainIndexingStatusIds.Unstarted) {
throw new Error("Omnichain Status must not be 'Unstarted'.");
process.exitCode = 1;
throw error;
}
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

The docstring says errors are “logged but not thrown, to keep the worker running”, but the catch block sets process.exitCode and rethrows. Rethrowing here will surface as an unhandled promise rejection from the setInterval callback, and the interval will continue keeping the event loop alive (so exitCode alone won’t reliably terminate the process). Consider either (a) not throwing and implementing retry/backoff to keep the worker running, or (b) explicitly stopping the interval and terminating the process (or bubbling the error to a supervisor) so the worker fails fast without repeated unhandled rejections.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to 67
// Recurring update of the Indexing Metadata Context record in ENSDb.
this.indexingStatusInterval = setInterval(
() => this.upsertIndexingStatusSnapshot(),
() => this.updateIndexingMetadataContext(),
secondsToMilliseconds(INDEXING_STATUS_RECORD_UPDATE_INTERVAL),
);
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

setInterval(() => this.updateIndexingMetadataContext(), …) schedules an async task without awaiting it. If an iteration takes longer than the interval, multiple updates can overlap concurrently (and errors become unhandled rejections unless caught at the call site). Consider adding a re-entrancy guard / in-flight flag, or replacing setInterval with an async loop that awaits each update before sleeping for the next tick.

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +90
invariant_indexingStatusIsUnstartedForIndexingMetadataContextUninitialized(
inMemoryIndexingStatusSnapshot,
);
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

Treating a missing record as Uninitialized and then enforcing omnichainStatus === Unstarted will break upgrades: existing deployments that don’t yet have an indexing_metadata_context row (but do have checkpoints / are already indexing) will restart with storedIndexingMetadataContext = Uninitialized and an in-memory status like Backfill/Following, causing a hard throw and preventing startup. Consider allowing initialization even when the in-memory status is not Unstarted (e.g., when the DB row is missing because of an upgrade), or add a data migration/backfill path from the legacy metadata records so restarts don’t fail.

Suggested change
invariant_indexingStatusIsUnstartedForIndexingMetadataContextUninitialized(
inMemoryIndexingStatusSnapshot,
);
if (
inMemoryIndexingStatusSnapshot.omnichainStatus ===
OmnichainIndexingStatusIds.Unstarted
) {
invariant_indexingStatusIsUnstartedForIndexingMetadataContextUninitialized(
inMemoryIndexingStatusSnapshot,
);
} else {
logger.warn({
msg: `Indexing Metadata Context is "uninitialized" but the in-memory indexing status is already started; treating this as an upgrade/backfill scenario and rebuilding the stored context from in-memory state`,
omnichainStatus: inMemoryIndexingStatusSnapshot.omnichainStatus,
});
}

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +41
async getStackInfo(): Promise<EnsIndexerStackInfo> {
if (typeof this.immutableStackInfo === "undefined") {
const ensDbPublicConfig = await this.ensDbClient.buildEnsDbPublicConfig();
const ensIndexerPublicConfig = await this.publicConfigBuilder.getPublicConfig();
const ensRainbowPublicConfig = await this.ensRainbowClient.config();

this.immutableStackInfo = buildEnsIndexerStackInfo(
ensDbPublicConfig,
ensIndexerPublicConfig,
ensRainbowPublicConfig,
);
Copy link

Copilot AI Apr 26, 2026

Choose a reason for hiding this comment

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

The cache is value-based (immutableStackInfo) rather than promise-based. If getStackInfo() is called concurrently before the first call resolves, it will trigger duplicate work (multiple ENSDb version queries / config fetches / ENSRainbow requests) and then race to assign immutableStackInfo. Consider caching an in-flight promise (e.g., private stackInfoPromise?: Promise<EnsIndexerStackInfo>) so concurrent callers share the same build.

Copilot uses AI. Check for mistakes.
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.

Create a single data model to capture all variants for ENSNode Metadata records

2 participants