Skip to content

Epoch Processing Pipeline

RunTimeAdmin edited this page Nov 5, 2026 · 34 revisions

Epoch Processing Pipeline

**Referenced Files in This Document** - [index.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/index.js) - [epoch-policy.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/epoch-policy.js) - [chain.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/chain.js) - [scoring.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/scoring.js) - [payments.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/payments.js) - [payment-scan.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/payment-scan.js) - [store.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/store.js) - [merkle.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/merkle.js) - [external.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/external.js) - [jobs.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/jobs.js) - [response-cache.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/response-cache.js)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion

Introduction

This document explains the oracle’s epoch processing pipeline that transforms raw payment-backed attestations into normalized reputation scores. It covers:

  • Two-phase processing: stateless reads followed by sequential writes with nonce management.
  • Payment scanning that backfills historical settlement data and maintains chain synchronization.
  • Challenge window coordination between primary and checker operators.
  • Divergence detection when operators compute different scores for the same agent.
  • ERC-8183 job registry integration as a read-only evidence source.
  • Performance optimizations including chunked processing, concurrent RPC calls, and caching.
  • Error handling, retry logic, and recovery procedures for failed epochs.

Project Structure

The oracle is implemented as a Node.js service with clear separation of concerns:

  • Orchestrator and HTTP API: index.js
  • Epoch decision logic: epoch-policy.js
  • Chain interaction and retries: chain.js
  • Scoring model: scoring.js
  • Payment verification and decay math: payments.js
  • Pull-based payment scanner: payment-scan.js
  • Persistent state and divergence records: store.js
  • Merkle evidence commitment: merkle.js
  • External ERC-8004 feedback: external.js
  • ERC-8183 job registry reader: jobs.js
  • Response cache for expensive evidence responses: response-cache.js
graph TB
Index["index.js<br/>HTTP API + epoch orchestrator"] --> Policy["epoch-policy.js<br/>decideAction / decideCheckerAction"]
Index --> Chain["chain.js<br/>RPC wrapper, retries, proposals"]
Index --> Payments["payments.js<br/>verification, decay, web-of-trust"]
Index --> Scan["payment-scan.js<br/>pull Transfer logs"]
Index --> Store["store.js<br/>attestations, flags, events, divergences"]
Index --> Merkle["merkle.js<br/>evidence roots and proofs"]
Index --> External["external.js<br/>ERC-8004 feedback"]
Index --> Jobs["jobs.js<br/>ERC-8183 jobs (read-only)"]
Index --> Cache["response-cache.js<br/>LRU evidence cache"]
Loading

Diagram sources

Section sources

Core Components

  • Epoch orchestrator (index.js)
    • Loads configuration, initializes chain and external modules, runs periodic epochs, exposes HTTP endpoints for attestations, evidence, divergence, jobs, badges, and scoring previews.
    • Refuses to start on a numeric setting that is set but unusable, and on an ORACLE_MODE other than unset, primary or checker, since a misspelt mode would otherwise run a primary.
    • Coordinates payment scanning before scoring to ensure both operators converge on the same evidence set. Before that, where PAYER_OUTCOMES_PEERS is set, it imports the payer-signed outcomes the other operators hold, so each epoch scores every outcome a payer signed, not only those posted to this operator.
  • Epoch policy (epoch-policy.js)
    • Pure functions for deciding whether to propose, finalize, skip, or flag divergence.
    • Validates epoch interval and divergence tolerance to prevent misconfiguration from silently breaking checks.
    • Holds the clock-skew check that stops an epoch when the chain's clock and the host's disagree, and the rule /health uses to call scoring stale.
    • Decides when a pending proposal can be finalized in one place, finalizableAtOf: the contract's stored finalizableAt, or proposedAt plus the live window for a proposal made before that field existed (it reads zero), as the contract judges it. The primary, the checker, the divergence log and the watcher all use it, so a window changed after a proposal neither delays its finalization nor makes the oracle send a transaction the contract refuses with ChallengeWindowActive.
  • Chain layer (chain.js)
    • Wraps RPC calls with exponential backoff and transient error detection (read-retry.js), and asks for a log read the node refuses for its size in halves (log-spans.js).
    • Manages proposal finalization, pending score inspection, fee gating, and operator standing.
  • Scoring model (scoring.js)
    • Computes six factors: fee, success, age, external, community, propagation.
    • Uses chain clock to ensure deterministic results across operators.
  • Payment system (payments.js)
    • Verifies ERC-20 transfers, applies decay, diversification, self-payment checks, and web-of-trust multipliers.
    • Produces measured fee volume and activity windows used by scoring.
  • Payment scanner (payment-scan.js)
    • Pulls Transfer logs over block ranges, plans credits, and persists checkpoints.
    • Includes canary validation to prove query correctness when no payments are found.
  • State store (store.js)
    • Persists attestations, flags, links, payment events, used transactions, and divergence records.
    • Provides pruning and cooldown mechanisms, and keeps the previous save as a backup it falls back to.
  • Evidence commitment (merkle.js)
    • Builds Merkle trees over payment events and generates proofs for auditors.
  • External feedback (external.js)
    • Reads ERC-8004 feedback (as of the start of the UTC day when the registry is on the oracle's own chain, as on Arc testnet) from the addresses that paid the agent, at most the 100 most paid and the newest 10 rows of each, normalizes recognized tags, counts only reviewers that pass reviewers.js, and caps the external score at 25. The factor is kept per snapshot unless the evidence changes, and a node failure is thrown, which skips the agent, rather than read as 0 (external.js:44-48, external.js:178-191, index.js:473-490). A counterparty's standing, as the web of trust reads it, is further limited by its matured total.
  • ERC-8004 links (link-scan.js, link-claims.js)
    • With EXTERNAL_LINK_FROM_BLOCK set and the registry on the oracle's own chain, each epoch reads the registry's sigvara MetadataSet logs since the last scan and keeps the claims made by the agent's operator (index.js:595-607). An epoch whose scan has not reached the day's snapshot block skips the agents its unchecked logs name; one that cannot list those logs, or read the registry's chain, scores nothing (index.js:1154-1219).
  • Job registry (jobs.js)
    • Observes ERC-8183 escrow lifecycle events and converts them to evidence-compatible records without feeding scores directly.
  • Response cache (response-cache.js)
    • Byte-bounded LRU cache keyed by revision token to avoid recomputing expensive evidence payloads.

Section sources

Architecture Overview

The epoch pipeline follows a two-phase design, after a pre-flight check:

  • Pre-flight
    • Confirm the RPC serves the chain named by EXPECTED_CHAIN_ID (when set) and the chain the state file was saved on, warn about transactions from this wallet still pending from an earlier epoch, and compare the chain's latest block time with the host clock.
    • A gap past MAX_CLOCK_SKEW_SECONDS (600 by default) means one of the two clocks is wrong, because every payment is dated and every score aged by the block time the RPC reports. No epoch runs until they agree. A failed pre-flight aborts the epoch, which is logged and counted, and the next tick tries again.
  • Phase 1: Stateless reads
    • Fetch registered agents and their info concurrently in chunks.
    • Read pending scores and fee coverage.
    • Run payment scan to backfill historical settlements.
  • Phase 2: Sequential writes
    • Guard reads per chunk to validate current status and bonding.
    • Decide action based on challenge window and mode (primary vs checker).
    • Compute scores and the evidence root, charge epoch fees (primary only, after a dry run of the proposal), and propose or finalize.
sequenceDiagram
participant Timer as "Epoch Timer"
participant Index as "index.js"
participant Chain as "chain.js"
participant Scan as "payment-scan.js"
participant Store as "store.js"
participant Policy as "epoch-policy.js"
participant Contract as "SigvaraReputation"
Timer->>Index : trigger runEpoch()
Index->>Chain : assertChainId(), clock-skew check
Index->>Chain : getRegisteredAgents()
Index->>Scan : runPaymentScan(agents)
Scan->>Chain : provider.getLogs(...)
Scan-->>Index : payments found
Index->>Store : creditPayment(...)
Index->>Chain : getChallengeWindow(), getLatestBlockTimestamp()
Index->>Policy : decideAction(pending, window, now)
alt Primary proposes
Index->>Contract : proposeReputation(scores, evidenceRoot)
else Checker agrees and the window passed by the grace
Index->>Contract : finalizeReputation(didHash)
end
Loading

Diagram sources

Section sources

Detailed Component Analysis

Two-Phase Epoch Processing and Nonce Management

  • Concurrency control
    • Phase 1 uses chunked concurrency to avoid RPC bursts while preserving input order. A chunk is sized in calls to fit one JSON-RPC batch of at most 20 (chunkForReads), which is 10 agents at two reads each or 6 at three with fee gating active, and the epoch waits READ_PACE_MS, 1.5 seconds, between chunks (epoch-policy.js:295-308, index.js:1247-1259).
    • Phase 2 enforces sequential writes because all proposals share the oracle wallet nonce; concurrent submissions would collide.
  • Guard reads
    • Before each write, fresh agent info and bonding status are batched per chunk to minimize stale-state risk. This step waits READ_PACE_MS before every chunk, since its first follows phase 1 (index.js:1292-1296).
  • Fee gating
    • Optional per-epoch fee charging occurs before proposing; checker mode never charges to avoid double-debiting, and startup refuses ORACLE_MODE=checker together with a fee registry.
    • A dry run of the proposal comes first, so an agent the contract would refuse is not charged, and one whose dry run cannot be made is skipped uncharged for the epoch. An agent already charged this epoch is not charged again.
  • Proposal strategy
    • Primary may overwrite its own stale proposal.
    • Checker uses compare-and-swap (proposeIfEmpty) to avoid overwriting another party’s proposal.
    • On main, a pending proposal whose proposer has lost its authority cannot be finalized. The primary replaces it with a fresh proposal, and a checker treats it as an empty slot once a dry run shows the contract would accept its own proposal there. Both are counted as proposalsStranded.
  • Counterparty standing
    • A payer’s standing is read once per epoch and retried when the node throttles or drops the read. A read that still fails skips that agent for the epoch rather than scoring it as no standing.
flowchart TD
Start(["Epoch start"]) --> PreFlight["Pre-flight: chain id, clock skew"]
PreFlight --> Scan["Run payment scan<br/>backfill settlements"]
Scan --> Phase1["Phase 1: Concurrent reads<br/>getAgentInfo, getPendingScore, isCovered"]
Phase1 --> Phase2Start["Phase 2: Sequential writes"]
Phase2Start --> Guards["Guard reads per chunk<br/>getAgentInfo, isBonded"]
Guards --> Decision{"Primary or Checker?"}
Decision --> |Primary| Compute["Compute score + evidence root"]
Compute --> Charge["Dry-run the proposal, then charge epoch fee (if configured)"]
Charge --> Propose["proposeReputation"]
Decision --> |Checker| Audit["Audit against proposedAt clock"]
Audit --> Compare{"Agree within tolerance?"}
Compare --> |No| RecordDivergence["Record divergence"]
Compare --> |Yes| FinalizeCheck{"Window passed by the grace?"}
FinalizeCheck --> |Yes| Finalize["finalizeReputation"]
FinalizeCheck --> |No| Skip["Skip, or leave it to the primary"]
Propose --> End(["Epoch done"])
Finalize --> End
RecordDivergence --> End
Skip --> End
Loading

Diagram sources

Section sources

Payment Scanning and Chain Synchronization

  • Backfilling
    • Scans ERC-20 Transfer logs for agent addresses in configurable chunks and recipient batches (50 recipients to a call), with PAYMENT_SCAN_PACE_MS (500 ms by default) between calls so a long catch-up finishes instead of looping on refusals. A span the node refuses for its size is asked for in halves, at the same pace (payment-scan.js:430-433, log-spans.js:44-62).
    • Plans credits using pure logic, then persists via store with deduplication keyed by transaction, recipient, and payer.
    • Skips a payment from the agent's address, or from its operator at the payment's own block, as self_payment. The registry scan reads OperatorTransferred in one filter with AgentRegistered to know who that was (chain.js:247-268, payment-scan.js:388-398).
    • Skips a payment in a block before the one the agent registered in as before_registration; the registration's own block counts (payment-scan.js:245-249).
    • Off by default (PAYMENT_SCAN_ENABLED=1), and it must be on for every operator or for none.
  • Safety and canary
    • Stays behind chain head by minimum confirmations (at least 6) to avoid reorg issues.
    • Runs a canary check when no payments are credited to verify the filter shape still finds the newest settlement the operator holds, of any agent. A canary that cannot find it (failed) holds the checkpoint, so the whole range is scanned again at the next epoch; one that cannot run (unavailable) does not.
  • Checkpointing
    • Advances only up to the earliest unresolved block timestamp to avoid losing future credits.
  • Payment chains (ADR 0009)
    • Off by default: with PAYMENT_CHAIN_IDS unset none of this runs (payment-chains.js:37-39).
    • A chain that is on is scanned before the scored chain's scan, in steps of MAX_BLOCKS saved as each completes, until it reaches finalized or has spent 10 minutes (index.js:211-213, index.js:922-924, index.js:1099-1102).
    • A chain in shadow, the default mode, is scanned beside the epoch, one run at a time, and credits nothing, so the epoch does not wait for it (index.js:978-989, index.js:1113). On 4 November 2026 Elysium testnet ran this way on the checker only (0009-payments-on-base.md:236-237).
    • An operator whose scan of an on chain has not passed that chain's delay, or whose scored-chain scan is too far behind for a proposal to land in time, scores nothing that epoch and counts it failed. Scans that fall too far behind while the epoch runs leave the remaining agents unscored, and /health shows either as paymentChainHold (index.js:1156-1191, index.js:1818-1822).
flowchart TD
A["Start scan range [from, to]"] --> B["Batch recipients (max per call)"]
B --> C["Chunk blocks (chunkSize)"]
C --> D["provider.getLogs(asset, topics, fromBlock, toBlock)"]
D --> E["Decode Transfer logs"]
E --> F["planCredits(grouped by tx+recipient+sender)"]
F --> G{"Is already credited?"}
G --> |Yes| H["Skip"]
G --> |No| I["Credit payment if valid"]
I --> J["Persist checkpoint after unresolved-safe boundary"]
H --> K["Next chunk"]
J --> K
K --> L["Canary check if credited == 0"]
L --> M["Report metrics and summary"]
Loading

Diagram sources

Section sources

Challenge Window Coordination Between Primary and Checker

  • Primary behavior
    • If a proposal exists and its review window has ended (finalizableAt), it finalizes the existing proposal and proposes a new one.
    • If finalizing is refused because the proposer has lost its authority (on main), it counts the proposal as stranded and proposes a fresh one in its place.
    • Otherwise, it skips until the window closes.
  • Checker behavior
    • Re-reads the pending slot immediately before writing to avoid races.
    • Recomputes the expected score at the proposal’s proposedAt time, over the evidence as it stood then, to compare apples-to-apples. A payment that settled after the proposal is one its proposer could not have had, so it is left out, and an outcome a payer signed later reads as unreported when that makes the root match the one the proposal committed to. For a proposal made before evidence had a fixed order, it tries the operator's stored order too, since that proposal committed to its proposer's (evidence-as-of.js:40-44, index.js:351-362). With a payment chain on, it also tries each whole-minute cut-off in the half hour before the proposal, and waits until its own scans have passed the proposal's time (evidence-as-of.js:32-38, index.js:1490-1497).
    • Finalizes an agreed score, or proposes into an empty slot, only once the primary is CHECKER_GRACE_MINUTES late: the window past by that long, or the slot empty that long. On main, a proposal whose proposer has lost its authority counts as an empty slot and is covered at once.
    • Records divergence details including factor deltas and evidence roots for auditability, and whether its evidence as of the proposal matched the proposal's root (evidenceMatchesProposal).
sequenceDiagram
participant Checker as "Checker Operator"
participant Chain as "chain.js"
participant Store as "store.js"
participant Policy as "epoch-policy.js"
participant Contract as "SigvaraReputation"
Checker->>Chain : getPendingScore(didHash)
Chain-->>Checker : {exists, proposedAt, finalizableAt, data, evidenceRoot}
Checker->>Policy : decideCheckerAction(pending, own, window, now, tolerance, grace)
alt Agrees and window open
Policy-->>Checker : skip
else Agrees, window passed, primary not yet late
Policy-->>Checker : defer-finalize
else Agrees and window passed by the grace
Policy-->>Checker : finalize
Checker->>Contract : finalizeReputation(didHash)
else Disagrees beyond tolerance
Policy-->>Checker : diverged
Checker->>Store : recordDivergence(...)
end
Loading

Diagram sources

Section sources

Divergence Detection Mechanism

  • Factor-level comparison
    • Compares each of the six stored factors and reports deltas where they differ.
    • Uses both total difference and maximum single-factor difference against a bounded tolerance (DIVERGENCE_TOLERANCE, 3 by default and at most 10). A factor that cannot be compared counts as a divergence, not as agreement.
  • Evidence diagnostics
    • Stores each side’s Merkle evidence root and event counts, under evidence (ownEvidenceRoot, proposedEvidenceRoot, ownPaymentEvents, ownDistinctPayers and compareWith), so auditors can diff evidence sets.
    • Provides a named endpoint for fetching the other side’s evidence.
  • Persistence and pruning
    • Divergences are persisted per agent with bounded history and age-based pruning.
classDiagram
class DivergenceRecord {
+number at
+number proposedAt
+number pendingTotal
+number ownTotal
+object factors
+object? evidence
}
class Store {
+recordDivergence(didHash, divergence, proposedAt, now, evidence)
+getDivergences(didHash)
+pruneDivergences(maxAgeMs, now)
}
Store --> DivergenceRecord : "persists"
Loading

Diagram sources

Section sources

ERC-8183 Job Registry Integration

  • Read-only observation
    • Decodes standardized job lifecycle events and assembles per-job records.
    • Converts terminal jobs into evidence-compatible records without feeding scores directly.
  • Safety and rejection rules
    • Refuses self-paid and self-evaluated jobs; expired/refunded jobs produce no evidence.
  • Configuration and visibility
    • Disabled unless a registry address is provided; health endpoint reports feed status and chain id.
    • Exposes /jobs/<address> for diagnostic queries with bounded scan windows.
flowchart TD
A["GET /jobs/<provider>"] --> B["Enabled?"]
B --> |No| C["Return 501 disabled"]
B --> |Yes| D["scanLogs(registry, topics, chunked)"]
D --> E["decodeLog -> assembleJobs"]
E --> F["toEvidence(providerAddress)"]
F --> G["Return counts, dropped reasons, evidence"]
Loading

Diagram sources

Section sources

Scoring Model and Normalized Reputation Scores

  • Factors
    • Fee score: measured volume divided by fee unit, capped.
    • Success score: decay-weighted success ratio with pseudo-observations, over the outcomes a payer signed or a token holder recorded.
    • Age score: tenure-based logarithmic curve weighted by recency; 0 for an agent with no payments when verification is on.
    • External score: normalized ERC-8004 feedback, capped at 25.
    • Community score: penalty based on age-weighted flags.
    • Propagation score: inherited trust from counterparties’ hard standing.
  • Caps
    • The six factor caps (20, 15, 30, 25, 5 and 5) live in oracle/score-caps.js, and a test compares them with the contract’s MAX_*_SCORE constants.
  • Determinism
    • Uses chain clock for time-dependent factors to ensure identical computation across operators.
flowchart TD
A["Inputs: registeredAt, attestations, flags, externalScore, measuredFeeScore, activity, propagation, now"] --> B["feeScore = min(20, volume/feeUnit)"]
A --> C["successScore = floor(success/(total+prior)*15)"]
A --> D["ageScore = log2(spanDays+1)*3 * recency"]
A --> E["externalScore = clamp(0..25)"]
A --> F["communityScore = max(0, 5 - flags*2)"]
A --> G["propagationScore = min(5, sum(hardStanding/25))"]
B --> H["total = sum(factors)"]
C --> H
D --> H
E --> H
F --> H
G --> H
Loading

Diagram sources

Section sources

Dependency Analysis

Key dependencies and relationships:

  • index.js depends on all core modules to orchestrate the epoch and serve HTTP endpoints.
  • chain.js depends on ethers and contract ABIs; provides retry wrappers and proposal/finalization methods.
  • payment-scan.js is pure apart from the log reader index.js hands it, which wraps chain.js’s backoff. It shares payments.js’s transfer topic and countsAsPayment rule, and index.js does the crediting and checkpointing through store.js.
  • payments.js is pure except for config reading; used by scoring and attestation paths.
  • merkle.js is pure hashing and proof generation used to commit evidence roots.
  • external.js is optional and gated by environment variables.
  • jobs.js is optional and read-only, integrated via HTTP endpoint.
  • response-cache.js caches serialized evidence bodies keyed by revision token.
graph LR
Index["index.js"] --> Chain["chain.js"]
Index --> Policy["epoch-policy.js"]
Index --> Payments["payments.js"]
Index --> Scan["payment-scan.js"]
Index --> Store["store.js"]
Index --> Merkle["merkle.js"]
Index --> External["external.js"]
Index --> Jobs["jobs.js"]
Index --> Cache["response-cache.js"]
Scan --> Payments
Index --> Score["scoring.js"]
Loading

Diagram sources

Section sources

Performance Considerations

  • Chunked processing
    • Phase 1 reads use mapChunked(items, size, mapper, paceMs) to cap concurrent calls and preserve order. paceMs is a wait between chunks, not before the first or after the last.
    • Payment scans split recipients and block ranges to avoid RPC limits. A span a node refuses for its size is read in halves.
  • Concurrent RPC calls
    • Batch independent reads using Promise.all within chunks to reduce round trips. The providers in chain.js and external.js send at most 20 calls in a batch (MAX_BATCH_CALLS in read-retry.js, batchMaxCount).
    • Payer standing lookups are grouped and fetched concurrently, 10 at a time and 1.5 seconds apart (PAYER_READ_CHUNK, PAYER_READ_PACE_MS).
  • Caching strategies
    • In-memory payer score cache cleared per epoch.
    • External factor kept per snapshot, so the epoch, the checker's re-score and every GET /score share one reading, unless the evidence changes (index.js:473-490).
    • Byte-bounded LRU response cache for evidence payloads keyed by revision token.
    • Merkle leaves cached against each frozen payment event and roots against each frozen event array, so an agent whose evidence has not changed reuses its root; leaves are also built by hand instead of through the ABI coder (5,000 events took 1,450 ms before and 480 ms after).
    • Badge cache with TTL and short TTL for unavailable states.
  • RPC resilience
    • Exponential backoff for view calls and log scans.
    • Distinction between throttling and real errors to avoid resubmitting potentially mempool’d transactions.
    • A sent transaction is waited for TX_WAIT_SECONDS (60 by default), replaced once at twice the fee with the same nonce, and then given up on (counted as txUnconfirmed). The oracle will not sign a fee above MAX_FEE_GWEI or a worst-case cost above MAX_TX_COST.

Section sources

Troubleshooting Guide

  • Epoch failures
    • Aborted epochs are logged and counted; the guard prevents overlapping runs and protects against nonce collisions.
    • A chain mismatch, or a chain clock more than MAX_CLOCK_SKEW_SECONDS from the host’s, aborts the epoch the same way. A payer standing or an ERC-8004 registry the node cannot answer for skips only the agent it belongs to.
    • Payment scan failures do not abort the epoch; they are retried next cycle with checkpoint resume.
  • RPC throttling and rate limits
    • Throttled proposals and finalizations are skipped and retried next epoch; metrics distinguish throttling from real errors.
    • View calls retry transient network errors with exponential backoff.
  • State persistence
    • Health endpoint probes writability and last persist result; failures surface as unhealthy status. It also answers 503 once two epoch intervals pass with no successful epoch, counted from process start until the first one succeeds.
    • State file writes are atomic (temp + rename + fsync) to avoid corruption. The previous save is kept as <state>.bak; an unreadable file falls back to it (stateSource: backup on /health), and the process stops rather than starting empty when neither is usable.
  • Divergence investigation
    • Use /divergence endpoints to inspect factor deltas, evidence roots, and recommended comparison endpoint.
    • Ensure checker epoch is shorter than half the challenge window to observe proposals.
    • To follow one agent, the site's Score epochs page (sigvara.xyz/epochs) shows its pending proposal, every past proposal and what became of it, the payments behind its score and whether the checker disagreed, read from the chain, Sigvara's event index (the Arc explorer's when the index does not answer) and both operators' public endpoints.
  • Payment scan canary
    • When no payments are credited, the canary verifies the filter shape; “failed” indicates a broken query, not quietness, and the checkpoint stays where it was so the range is scanned again next epoch.

Section sources

Conclusion

The oracle’s epoch pipeline ensures deterministic, auditable reputation scoring through:

  • Two-phase processing that separates stateless reads from sequential writes.
  • Pull-based payment scanning that aligns operator evidence sets.
  • Clear primary/checker roles coordinated by challenge windows.
  • Robust divergence detection with actionable diagnostics.
  • Optional ERC-8183 job observation for future evidence integration.
  • Careful performance tuning and resilient error handling to maintain reliability under RPC constraints.

Overview

Clone this wiki locally