Skip to content

Apply specific variant types for ChainIndexingMetrics#1612

Merged
tk-o merged 13 commits intomainfrom
refactor/ponder-sdk-indexing-metrics
Feb 11, 2026
Merged

Apply specific variant types for ChainIndexingMetrics#1612
tk-o merged 13 commits intomainfrom
refactor/ponder-sdk-indexing-metrics

Conversation

@tk-o
Copy link
Contributor

@tk-o tk-o commented Feb 6, 2026

Lite PR

Tip: Review docs on the ENSNode PR process

Summary

  • Ponder SDK improved data model.
    • All possible variants of ChainIndexingMetrics type have been collected as a discriminated union.
      • ChainIndexingMetricsHistorical includes historicalTotalBlocks field to support ENSIndexer use case where, at the ENSIndexer app start, all block refs must be fetched from RPC and cached for further use.
      • ChainIndexingMetricsRealtime includes latestSyncedBlock field
      • ChainIndexingMetricsCompleted includes finalIndexedBlock field
    • PonderIndexingStatus now includes the checkpointBlock field for each indexed chain.
    • Unvalidated<T> type has been introduced to support creating objects shaped like business-layer data model, but not validated yet.

Why

  • More accurate data model allows learning the business logic details more easily.
  • Validating objects with Zod schemas requires building unvalidated representation of a given business-level data model.

Testing

  • Testing suite defined for Ponder SDK is happy with the updated data model (minor testing data tweaks were required to account for improved invariants logic).

Notes for Reviewer (Optional)

  • Updates in this PR maintain the support for goals on the ENSIndexer side.

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 February 6, 2026 13:59
@vercel
Copy link
Contributor

vercel bot commented Feb 6, 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 Feb 11, 2026 10:23am
ensnode.io Skipped Skipped Feb 11, 2026 10:23am
ensrainbow.io Skipped Skipped Feb 11, 2026 10:23am

@changeset-bot
Copy link

changeset-bot bot commented Feb 6, 2026

⚠️ No Changeset found

Latest commit: 586ce13

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

@coderabbitai
Copy link

coderabbitai bot commented Feb 6, 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

Convert chain indexing metrics to a discriminated-union model (Queued/Backfill/Realtime/Completed), refactor deserialization to build/validate per-chain variants, update mocks and a client test, add DeepPartial utility, and wrap per-chain checkpointBlock in a new ChainIndexingStatus object.

Changes

Cohort / File(s) Summary
Type definitions
packages/ponder-sdk/src/indexing-metrics.ts
Replace monolithic ChainIndexingMetrics with four discriminated variants (Queued, Backfill, Realtime, Completed), add ChainIndexingStates/discriminator and rename/adjust per-state fields (e.g., backfillSyncBlocksTotalbackfillTotalBlocks).
Deserialization — indexing metrics
packages/ponder-sdk/src/deserialize/indexing-metrics.ts
Add buildUnvalidatedChainIndexingMetrics to produce per-chain unvalidated metrics, refactor buildUnvalidatedPonderIndexingMetrics to use it, require ponder_historical_completed_indexing_seconds, and switch to unvalidated→validated flow; remove previous global invariant about mutually-exclusive flags.
Mocks — indexing metrics
packages/ponder-sdk/src/deserialize/indexing-metrics.mock.ts
Refactor per-chain mock entries to the new Realtime shape (state: ChainIndexingStates.Realtime, latestSyncedBlock), add satisfies ChainIndexingMetricsRealtime assertions, and append ponder_historical_completed_indexing_seconds gauge blocks across multiple valid/invalid mocks.
Utilities
packages/ponder-sdk/src/deserialize/utils.ts
Add exported DeepPartial<T> utility type that recursively makes properties optional (handles nested objects and arrays).
Indexing status types
packages/ponder-sdk/src/indexing-status.ts
Add exported ChainIndexingStatus ({ checkpointBlock: BlockRef }) and change PonderIndexingStatus.chains from Map<ChainId, BlockRef> to Map<ChainId, ChainIndexingStatus>.
Deserialization — indexing status
packages/ponder-sdk/src/deserialize/indexing-status.ts
Change local map type and population to use ChainIndexingStatus (chains.set(chainData.id, { checkpointBlock: chainData.block })) and update imports accordingly.
Tests
packages/ponder-sdk/src/client.test.ts
Update expected error message in metrics() invalid-conflicting-metrics test to reference Prometheus metrics ponder_sync_is_complete and ponder_sync_is_realtime and state that they cannot both be 1 for chain 10.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇 I hopped through types, four states in a row,
Queued, Backfill, Realtime, Completed — quick and low,
I tucked checkpoints in boxes, made mocks sing with glee,
DeepPartial softened edges so schemas hop free,
Thump-thump — a rabbit cheers the code with tiny tea!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description follows the template structure but contains inconsistencies between the summary and PR objectives regarding field names and terminology. Clarify the exact field names and variant type names: the summary mentions ChainIndexingMetricsHistorical and field names like historicalTotalBlocks/latestSyncedBlock/finalIndexedBlock, while the objectives reference ChainIndexingMetricsQueued/Backfill/Realtime/Completed with different field names (backfillTotalBlocks/latestKnownBlock/targetBlock). Ensure the description accurately reflects the implemented changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary change: introducing specific variant types for ChainIndexingMetrics as a discriminated union.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/ponder-sdk-indexing-metrics

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

@tk-o tk-o marked this pull request as ready for review February 6, 2026 13:59
@tk-o tk-o requested a review from a team as a code owner February 6, 2026 13:59
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 6, 2026

Greptile Overview

Greptile Summary

This PR refactors the Ponder SDK data model to use a discriminated union for ChainIndexingMetrics, replacing the previous boolean flags (indexingCompleted, indexingRealtime) with explicit state variants.

Key changes:

  • Introduces ChainIndexingMetricsHistorical, ChainIndexingMetricsRealtime, and ChainIndexingMetricsCompleted as distinct types with a state discriminator
  • Adds Unvalidated<T> utility type to represent objects shaped like business-layer types before validation
  • Updates PonderIndexingStatus to include checkpointBlock field for each indexed chain
  • Refactors deserialization logic to build appropriate variant based on Prometheus metrics (ponder_sync_is_complete and ponder_sync_is_realtime)
  • Updates validation to check metrics at the Prometheus level before building unvalidated objects
  • All test mocks and expectations updated to reflect new data model

The changes maintain backward compatibility for ENSIndexer use cases and improve type safety through discriminated unions, making invalid states unrepresentable at the type level.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The refactoring is well-structured with comprehensive test coverage updates. The discriminated union pattern improves type safety by making invalid states unrepresentable. All validation logic has been moved to appropriate layers (Prometheus metrics level before building unvalidated objects). Previous review concerns have been addressed by the developer with clear explanations about Ponder's behavior.
  • No files require special attention

Important Files Changed

Filename Overview
packages/ponder-sdk/src/indexing-metrics.ts Refactors ChainIndexingMetrics into discriminated union with Historical, Realtime, and Completed variants
packages/ponder-sdk/src/deserialize/utils.ts Adds Unvalidated<T> and DeepPartial<T> utility types for building objects before validation
packages/ponder-sdk/src/deserialize/indexing-metrics.ts Refactors deserialization logic to build discriminated union variants based on Ponder metrics state
packages/ponder-sdk/src/indexing-status.ts Wraps BlockRef in new ChainIndexingStatus interface with checkpointBlock field

Sequence Diagram

sequenceDiagram
    participant Client
    participant PonderClient
    participant Ponder as Ponder App
    participant Deserializer
    participant Validator

    Client->>PonderClient: metrics()
    PonderClient->>Ponder: GET /metrics
    Ponder-->>PonderClient: Prometheus metrics text
    PonderClient->>Deserializer: deserializePonderIndexingMetrics(text)
    Deserializer->>Deserializer: deserializePrometheusMetrics(text)
    Deserializer->>Validator: check invariants (required metrics, no conflicts)
    alt Validation fails
        Validator-->>Client: throw Error
    end
    Deserializer->>Deserializer: buildUnvalidatedPonderIndexingMetrics()
    loop For each chain
        Deserializer->>Deserializer: buildUnvalidatedChainIndexingMetrics()
        alt ponder_sync_is_complete === 1
            Deserializer->>Deserializer: return ChainIndexingMetricsCompleted
        else ponder_sync_is_realtime === 1
            Deserializer->>Deserializer: return ChainIndexingMetricsRealtime
        else
            Deserializer->>Deserializer: return ChainIndexingMetricsHistorical
        end
    end
    Deserializer->>Validator: validate with Zod schemas
    alt Validation fails
        Validator-->>Client: throw Error
    end
    Validator-->>Client: PonderIndexingMetrics
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 6, 2026

Additional Comments (1)

packages/ponder-sdk/src/deserialize/indexing-metrics.ts
New required metric breaks

Adding ponder_historical_completed_indexing_seconds to requiredMetricNames makes deserialization fail against any Ponder /metrics output that doesn’t emit this series (e.g., older Ponder versions). If the SDK intends to remain compatible with previously-supported Ponder metric sets, this is a breaking change that will hard-fail PonderClient.metrics() at runtime.

If backward compatibility is required, gate this requirement (e.g., treat missing metric as “not supported” and fall back to prior logic), or bump major version / document the minimum Ponder version this SDK now requires.

Copy link
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

This pull request refactors the ChainIndexingMetrics type in the Ponder SDK from a single interface with boolean flags to a discriminated union with four distinct states. This improves type safety and makes the state machine more explicit and easier to reason about.

Changes:

  • Converted ChainIndexingMetrics from an interface with conditional boolean fields to a discriminated union of four state variants: Queued, Backfill, Realtime, and Completed
  • Updated deserialization logic to build the appropriate state based on Prometheus metrics values
  • Enhanced invariant checking to validate state-specific constraints at the Prometheus metrics level
  • Updated test mocks and test assertions to reflect the new data model

Reviewed changes

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

Show a summary per file
File Description
packages/ponder-sdk/src/indexing-metrics.ts Defines the new discriminated union type structure with four state variants, replacing the previous single interface with boolean flags
packages/ponder-sdk/src/deserialize/utils.ts Adds DeepPartial utility type for handling partial/unvalidated data structures during deserialization
packages/ponder-sdk/src/deserialize/indexing-metrics.ts Updates deserialization logic to construct appropriate state variants based on Prometheus metrics, with enhanced invariant validation
packages/ponder-sdk/src/deserialize/indexing-metrics.mock.ts Updates mock test data to use the new discriminated union structure with explicit type fields
packages/ponder-sdk/src/client.test.ts Updates test assertion to match the new error message format for conflicting metrics

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

Copy link
Contributor Author

@tk-o tk-o left a comment

Choose a reason for hiding this comment

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

Self-review completed.

Copy link

@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

🤖 Fix all issues with AI agents
In `@packages/ponder-sdk/src/deserialize/indexing-metrics.ts`:
- Around line 75-141: In buildUnvalidatedChainIndexingMetrics, treat missing
per-chain metric values as "Queued" instead of falling through to Backfill:
change the ponderHistoricalCompletedIndexingSeconds check to treat both 0 and
undefined/null as queued (e.g., if ponderHistoricalCompletedIndexingSeconds ==
null || ponderHistoricalCompletedIndexingSeconds === 0) and ensure the
subsequent checks for ponderSyncIsComplete and ponderSyncIsRealtime only
consider a value of 1 as true (leave them as strict === 1) so undefined/null
won't be misinterpreted; update logic around latestSyncedBlock and
backfillTotalBlocks handling accordingly so missing metric values don't produce
an unintended Backfill result.

In `@packages/ponder-sdk/src/deserialize/utils.ts`:
- Around line 31-37: DeepPartial currently treats any non-primitive as a nested
object (so Map/Set/Date/RegExp/Function get recursed into); update the
conditional in the DeepPartial type to exclude built-in non-plain objects by
adding a guard: after the array branch check add a case like "T[P] extends Date
| Function | Map<any, any> | Set<any> | WeakMap<any, any> | WeakSet<any> |
RegExp ? T[P] : DeepPartial<T[P]>" so that the type alias DeepPartial<T> only
recurses into plain object shapes and returns the original type for those
built-ins.

In `@packages/ponder-sdk/src/indexing-metrics.ts`:
- Around line 116-136: The doc comment for the interface
ChainIndexingMetricsRealtime uses ambiguous phrasing "the backfill phase
transitioned to completed phase"; update the comment above the
ChainIndexingMetricsRealtime declaration to clearly state that the backfill
phase has finished and the chain has entered realtime indexing mode (i.e.,
backfill completed and indexing continues with no target end block), replacing
the ambiguous "transitioned to completed phase" wording with a concise phrasing
like "the backfill phase has finished and the chain entered realtime mode" so
readers of the ChainIndexingMetricsRealtime docs understand the intended state.

@tk-o
Copy link
Contributor Author

tk-o commented Feb 6, 2026

@greptile review

@vercel vercel bot temporarily deployed to Preview – ensnode.io February 6, 2026 14:16 Inactive
@vercel vercel bot temporarily deployed to Preview – ensrainbow.io February 6, 2026 14:16 Inactive
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io February 6, 2026 14:16 Inactive
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copilot AI review requested due to automatic review settings February 6, 2026 16:23
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io February 6, 2026 16:23 Inactive
@vercel vercel bot temporarily deployed to Preview – ensrainbow.io February 6, 2026 16:23 Inactive
@vercel vercel bot temporarily deployed to Preview – ensnode.io February 6, 2026 16:23 Inactive
@tk-o
Copy link
Contributor Author

tk-o commented Feb 6, 2026

@greptile review

Copy link

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
packages/ponder-sdk/src/indexing-status.ts (1)

30-37: ⚠️ Potential issue | 🟡 Minor

JSDoc on chains is now slightly inaccurate.

Line 32 still says "Map of indexed chain IDs to their block reference," but the value type is now ChainIndexingStatus, not BlockRef.

📝 Suggested doc fix
   /**
-   * Map of indexed chain IDs to their block reference.
+   * Map of indexed chain IDs to their indexing status.
    *
    * Guarantees:
    * - Includes entry for at least one indexed chain.
    */
   chains: Map<ChainId, ChainIndexingStatus>;

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
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 7 out of 7 changed files in this pull request and generated 1 comment.


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

Copilot AI review requested due to automatic review settings February 10, 2026 17:32
@tk-o tk-o force-pushed the refactor/ponder-sdk-indexing-metrics branch from 849d4cd to 8feeada Compare February 10, 2026 17:32
@vercel vercel bot temporarily deployed to Preview – ensrainbow.io February 10, 2026 17:32 Inactive
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io February 10, 2026 17:32 Inactive
@vercel vercel bot temporarily deployed to Preview – ensnode.io February 10, 2026 17:32 Inactive
Copy link
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 8 out of 8 changed files in this pull request and generated 4 comments.


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

Copy link
Contributor Author

@tk-o tk-o left a comment

Choose a reason for hiding this comment

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

Self-review completed.

schemaChainIdString,
schemaSerializedChainIndexingMetrics,
);
const schemaChainsIndexingMetrics = z.map(schemaChainId, schemaChainIndexingMetrics);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We'll use business-level representation of ChainId instead of its string representation.


function invariant_includesAtLeastOneIndexedChain(
ctx: ParsePayload<SerializedPonderIndexingMetrics>,
function invariant_indexingCompletedAndRealtimeAreNotBothTrue(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Validating Prometheus Metrics before constructing Unvalidated<PonderIndexingMetrics>.

}
}

function invariant_includesRequiredMetrics(ctx: ParsePayload<PrometheusMetrics>) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Validating Prometheus Metrics before constructing Unvalidated<PonderIndexingMetrics>.

Comment on lines +120 to +124
// Invariant: All values in the 'chain' label of required chain metrics must be valid ChainId strings.
for (const maybeChainId of metricLabels) {
const result = schemaChainIdString.safeParse(maybeChainId);

if (!result.success) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks to this invariant we can later safely call Number(chainIdString) while building Unvalidated<PonderIndexingMetrics> object.

.pipe(schemaPonderIndexingMetrics);
.check(invariant_indexingCompletedAndRealtimeAreNotBothTrue);

function invariant_includesAtLeastOneIndexedChain(ctx: ParsePayload<PonderIndexingMetrics>) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Validating Prometheus Metrics before constructing Unvalidated<PonderIndexingMetrics>.

* @returns Unvalidated Chain Indexing Metrics
* to be validated by {@link schemaChainIndexingMetrics}.
*/
function buildUnvalidatedChainIndexingMetrics(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Logic in this function supports the current way ENSIndexer was determining a concrete Indexing Status type.

Comment on lines +290 to +292
const validation = schemaSerializedPonderIndexingMetrics
.transform(buildUnvalidatedPonderIndexingMetrics)
.pipe(schemaPonderIndexingMetrics)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

During deserialization, we parse raw text with schemaSerializedPonderIndexingMetrics into validated PrometheusMetrics instance, and then use that instance to build Unvalidated<PonderIndexingStatus>, which in turn gets validated with schemaPonderIndexingMetrics.

@tk-o
Copy link
Contributor Author

tk-o commented Feb 10, 2026

@greptile review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

8 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@tk-o
Copy link
Contributor Author

tk-o commented Feb 10, 2026

@greptile review

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

4 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

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

@tk-o Thank you 👍 Please take the lead to merge when ready

* Latest synced block
* Total count of historical blocks.
*
* Each time a Ponder app restarts, if the historical blocks have
Copy link
Member

Choose a reason for hiding this comment

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

This comment makes it sound like historicalTotalBlocks keeps increasing across time for a Ponder app instance that hasn't restarted yet. My understanding is it never changes unless the Ponder app restarts?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll refine the comment, your understanding is correct.

Copilot AI review requested due to automatic review settings February 11, 2026 10:23
@vercel vercel bot temporarily deployed to Preview – ensrainbow.io February 11, 2026 10:23 Inactive
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io February 11, 2026 10:23 Inactive
@vercel vercel bot temporarily deployed to Preview – ensnode.io February 11, 2026 10:23 Inactive
@tk-o tk-o merged commit 0444866 into main Feb 11, 2026
18 checks passed
@tk-o tk-o deleted the refactor/ponder-sdk-indexing-metrics branch February 11, 2026 10:24
Copy link
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 8 out of 8 changed files in this pull request and generated 3 comments.


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

});
}
}
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

invariant_includesRequiredMetrics validates that each required metric has some chain labels, but later buildUnvalidatedChainIndexingMetrics() assumes that for every chain present in ponder_sync_block there is also a corresponding sample in ponder_sync_block_timestamp (and ponder_historical_total_blocks for historical state). If any chain is missing in one of those metrics, getValue() returns undefined and the error will surface later as a less-actionable schema failure. Consider adding an invariant that the set of chain labels is consistent across required chain metrics (or at least that each chain in ponder_sync_block has non-undefined values for the other required metrics).

Suggested change
}
}
// Invariant: The set of 'chain' labels must be consistent across required chain metrics.
// In particular, every chain present in 'ponder_sync_block' must also be present in the
// other required chain metrics, so that downstream code can safely assume per-chain values exist.
const syncBlockChains = new Set(
prometheusMetrics.getLabels("ponder_sync_block", "chain"),
);
if (syncBlockChains.size > 0) {
const otherChainMetricNames = requiredChainMetricNames.filter(
(name) => name !== "ponder_sync_block",
);
for (const metricName of otherChainMetricNames) {
const metricChains = new Set(
prometheusMetrics.getLabels(metricName, "chain"),
);
for (const chain of syncBlockChains) {
if (!metricChains.has(chain)) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: `Metric '${metricName}' must include a 'chain' label for each chain present in 'ponder_sync_block'. Missing chain '${chain}'.`,
});
}
}
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +84
function invariant_indexingCompletedAndRealtimeAreNotBothTrue(
ctx: ParsePayload<PrometheusMetrics>,
) {
const { chains } = ctx.value;
const prometheusMetrics = ctx.value;
const chainReferences = prometheusMetrics.getLabels("ponder_sync_block", "chain");

if (chains.size === 0) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: "Ponder Indexing Metrics must include at least one indexed chain.",
for (const maybeChainId of chainReferences) {
const ponderSyncIsComplete = prometheusMetrics.getValue("ponder_sync_is_complete", {
chain: maybeChainId,
});
}
}

/**
* Schema representing settings of a Ponder app.
*/
const schemaSerializedApplicationSettings = z.object({
command: z.enum(PonderAppCommands),
ordering: z.enum(PonderIndexingOrderings),
});
const ponderSyncIsRealtime = prometheusMetrics.getValue("ponder_sync_is_realtime", {
chain: maybeChainId,
});

/**
* Schema describing the Ponder Indexing Metrics.
*/
const schemaPonderIndexingMetrics = z
.object({
appSettings: schemaSerializedApplicationSettings,
chains: schemaSerializedChainsIndexingMetrics,
})
.check(invariant_includesAtLeastOneIndexedChain);
if (ponderSyncIsComplete === 1 && ponderSyncIsRealtime === 1) {
ctx.issues.push({
code: "custom",
input: ctx.value,
message: `'ponder_sync_is_complete' and 'ponder_sync_is_realtime' metrics cannot both be 1 at the same time for chain ${maybeChainId}`,
});
}
}
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

The function name invariant_indexingCompletedAndRealtimeAreNotBothTrue no longer matches what it validates (it now checks the Prometheus metrics 'ponder_sync_is_complete'/'ponder_sync_is_realtime', not the removed indexingCompleted/indexingRealtime fields). Renaming it would make the invariant’s intent clearer and avoid confusion for future refactors.

Copilot uses AI. Check for mistakes.
Comment on lines +289 to +293
export function deserializePonderIndexingMetrics(ponderMetricsText: string): PonderIndexingMetrics {
const validation = schemaSerializedPonderIndexingMetrics
.transform(buildUnvalidatedPonderIndexingMetrics)
.pipe(schemaPonderIndexingMetrics)
.safeParse(ponderMetricsText);
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

deserializePonderIndexingMetrics was narrowed to accept only string, even though the schema uses z.coerce.string() and other deserializers in this package accept unknown. If this is part of the public SDK API, consider keeping the parameter as unknown (or string | unknown) for consistency and to avoid an unnecessary breaking change; otherwise, consider dropping z.coerce and requiring a string end-to-end.

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.

Built Ponder Client and rename ponder-metadata to ponder-sdk

2 participants