Skip to main contentSkip to data table
Pharos

Worker and API Limits

Operational limits for the Pharos Worker and API: cron budgets, fetch connection caps, polling intervals, cache behavior, and guardrail checks.

Reference for limits we can verify from repo code and checked-in config.

This document intentionally focuses on:

  • limits enforced in code
  • budgets encoded in config
  • runtime assumptions the scheduler is explicitly designed around

It intentionally does not treat vendor pricing-plan quotas as source of truth. Cloudflare, CoinGecko, Alchemy, Etherscan, Anthropic, X, and similar providers can change those independently of this repo. Re-check official vendor docs or your live account dashboard before making spend-sensitive or capacity-sensitive changes.

Agent navigation — Grep the heading you need: Primary Sources · Worker Runtime · Cron Budgeting · Upstream Fetch Budgets · Request Timeouts Worth Preserving · Anthropic / Digest Runtime · Design Guidance.


Primary Sources

  • worker/wrangler.toml
  • shared/lib/cron-jobs.ts
  • worker/src/lib/rate-limit.ts
  • shared/lib/ops-limits.ts
  • worker/src/lib/api-keys.ts
  • worker/src/lib/circuit-breaker.ts
  • worker/src/handlers/http/gates.ts
  • worker/src/cron/sync-blacklist.ts
  • worker/src/cron/sync-mint-burn.ts
  • worker/src/cron/sync-live-reserves.ts
  • worker/src/cron/sync-live-reserves-config.ts
  • worker/src/lib/address-price-providers/index.ts
  • worker/src/lib/authoritative-price-sources/index.ts
  • worker/src/cron/sync-stablecoins/enrich-prices-cmc-pass.ts
  • worker/src/cron/sync-stablecoins/post-enrichment.ts
  • worker/src/cron/dex-discovery/orchestrator.ts
  • worker/src/cron/measured-execution/sync.ts
  • worker/src/cron/measured-execution/profiles.ts
  • worker/src/cron/sync-stablecoins/enrich-prices.ts
  • worker/src/cron/sync-fx-rates.ts
  • worker/src/cron/prepare-safety-score-v9-input.ts
  • worker/src/cron/compute-safety-score-v9.ts
  • worker/src/cron/daily-digest.ts
  • worker/src/cron/sync-yield-data.ts
  • worker/src/cron/sync-yield-supplemental.ts
  • worker/src/cron/fetch-tbill-rate.ts

Worker Runtime

ConstraintCurrent repo valueSourceNotes
Worker CPU budget per invocation300000 msworker/wrangler.tomlHard repo-configured CPU cap via [limits].cpu_ms. Cloudflare's Workers limits apply a 30-second CPU class to Cron expressions with intervals below one hour and a 15-minute class to hourly-or-longer expressions. The existing DEX lanes (halfHourlyOffset, halfHourlyChartsOffset) and the converted quarterHourly, v9SupplyAttributionOffset, depegResolverOffset, v9PublicationOffset, and statusSelfCheckOffset lanes, plus both paired mint/burn lanes, use hourly physical aliases to qualify for the 15-minute class while preserving logical sub-hourly cadence; the repo cap remains five minutes. Telegram dispatch (C102) continues to apply its own fresh-send budget before formatting.
Cron expressions / trigger slotsSource-owned; reviewed ceilings in CRON_GROWTH_HEADROOM_POLICY; run the cron checksworker/wrangler.toml, shared/lib/cron-jobs.ts, shared/lib/scheduled-runner-registry.tsCRON_SCHEDULES owns logical status/slot cadence, while CRON_TRIGGER_SCHEDULES owns the deployed physical expressions. The reviewed physical-trigger, fetch-capable-entry, and headroom-full ceilings live in CRON_GROWTH_HEADROOM_POLICY (shared/lib/cron-jobs.ts); run npm run check:cron-sync / npm run check:cron-connections for the live topology rather than reading a count here. The reviewed shape is the hourly DEX source lane (halfHourlyOffset), the paired-hourly DEX consumer lane (halfHourlyChartsOffset), plus hourly aliases for quarterHourly, v9SupplyAttributionOffset, depegResolverOffset, statusSelfCheckOffset, and v9PublicationOffset, and paired hourly aliases for both mint/burn lanes; ADR-21 converted V9 publication, ADR-22 isolated DDR memory and restored the critical mint/burn lane's hourly CPU class, and ADR-23 corrected the extended lane after production abandonments. The runner registry maps every physical expression back to one logical slot identity, and npm run check:cron-sync enforces that mapping. Splitting aliases rebalances existing logical work rather than adding scheduled work. check:cron-connections also models budget-only scheduled surfaces with connectionGroup metadata. Failure-isolated reserve recovery remains independent of the interrupted invocation.
Status-tracked and budget-only jobsSource-owned; exposed by status and budget registriesshared/lib/cron-jobs.ts, shared/lib/scheduled-runner-registry.tsRuntime scheduling matches the shared status metadata expected by /api/status; budget-only work is modeled separately by the connection-budget registry.
API key default limiter120 requests / 60 seconds per keyshared/lib/ops-limits.ts, worker/src/lib/api-keys.tsNon-exempt /api/* requests require a valid X-API-Key. shared/lib/api-endpoints/ owns the no-key exceptions; read the current set there rather than from a list here. The D1-backed api_key_rate_limit table enforces per-key quotas in the normal path, and per-key overrides live in api_keys.rate_limit_per_minute. Protected cacheable GET edge-cache hits can use a bounded isolate-local fast path only for recently verified non-self-serve keys; cold, unknown, self-serve, expired auth-cache, cache-miss, cache-bypass, and non-GET requests stay on the D1-backed path or fail closed. After repeated D1 limiter failures, protected cacheable GET routes open a 60-second isolate-local fallback circuit capped at the self-serve quota (30/min); cold or unknown keys still fail closed with 503 and Retry-After: 60. Last-used metadata writes are best-effort.
Self-serve API key policy30 requests / 60 seconds, 60 days expiryshared/lib/ops-limits.ts, worker/src/api/api-key-requests.tsEmail-verified key issuance through /api/; one active/pending self-serve key claim per normalized email.
API key revocation latency≤5 seconds for protected cacheable edge-cache hits; immediate on the D1-backed pathworker/src/lib/api-key-core.ts (API_KEY_AUTH_CACHE_TTL_MS)Normal API-key authentication consults D1 and fails closed if lookup storage is unavailable. A recently verified non-self-serve key can use the bounded fresh auth cache only on the narrow protected cacheable GET edge-cache-hit fast path, and the cache is not accepted as stale authentication material after lookup failures.
Feedback limiter3 submissions / 10 minutes per salted IP hashworker/src/api/feedback.ts, worker/src/lib/rate-limit.tsSeparate from the per-key limiter
Self-serve request limiter5/hour per salted IP hash, 3/day per private email hashshared/lib/ops-limits.ts, worker/src/api/api-key-requests.tsProtects POST /api/api-key-requests; dependency failures fail closed with 503 and Retry-After: 60.
Self-serve verification limiter20/10 minutes per salted IP hash, 5/10 minutes per token hashshared/lib/ops-limits.ts, worker/src/api/api-key-requests.tsProtects POST /api/api-key-requests/verify; issuance is also capped to one creation per salted IP hash per 24 hours.
Request attribution telemetry retention35 daysworker/src/lib/request-source-attribution.ts, functions/lib/request-attribution.tsTotal site-vs-external demand, worker-lane load, and per-key public-API load buckets in api_request_consumer_stats / site_data_request_stats / api_key_request_stats are pruned opportunistically. Worker route/source, Worker per-key, and Pages site-data counters use short isolate-local batching before D1 upsert. Set REQUEST_SOURCE_ATTRIBUTION_DISABLED=true on Worker and/or Pages to pause low-value route/source writes without disabling API-key auth, D1-backed rate limiting, or per-key public API load telemetry. Set API_KEY_REQUEST_ATTRIBUTION_DISABLED=true on the Worker only for keyed public-API spikes where per-key observability writes also need a D1 pressure relief valve; auth, rate limiting, and last-used metadata stay enabled.

Connection-budget operating assumption

Cloudflare currently limits each invocation to six simultaneous outbound requests that are still waiting for response headers. A request stops occupying that header-wait slot once headers arrive. Pharos deliberately uses a stricter trigger-wide six-connection model in its static scheduler budget so nested and chained fetch phases cannot accidentally overlap at the platform ceiling. See the Workers limits and the April 2026 connection-limit change.

The scheduler is structured around that conservative repo constraint:

  • heavy lanes get isolated trigger slots (sync-blacklist, sync-mint-burn, sync-mint-burn-extended, sync-dex-discovery, sync-dex-liquidity-stage)
  • shared slots bundle only related work
  • the quarter-hourly handler sequences jobs instead of fanning them out blindly, with D1-only DDR work moved to the later +8 follow-up lane
  • npm run check:cron-connections fails any trigger at or above 6/6 and reports 5/6 triggers as headroom full
  • the connection check includes budget-only scheduled surfaces that do not create separate cron_runs rows: telegram-registration-reconciliation, telegram-digest-outbox-drain, and digest-trigger-poll

Treat any new fetch-heavy work added to an existing trigger slot as competing for the same trigger-wide outbound connection budget. A trigger at 5/6 must be treated as full for new fetch-heavy work unless the change also reduces existing peak usage or moves work to a different slot.

Current state: halfHourlyOffset is the only modeled 5/6 slot and is full for new fetch-heavy work. It contains the hourly :10 sync-dex-liquidity-stage, whose nested direct-API phase can reach that static peak while staying below both the platform header-wait ceiling and the repo budget. Active measured execution is reduced to three EVM lanes (3/6), while the shadow EVM lane runs daily. The D1-only sync-dex-liquidity consumer and exit-route turnover watchdog remain in a serial 1/6 chain: prices publish hourly at :16, liquidity scores and the watchdog run on even-hour :16, and :46 reuses the current generation without rewriting DEX surfaces.

For sync-stablecoins, failed upstream responses must still be consumed or canceled before later passes start. That rule bounds unread bytes, completes transport cleanup, and keeps fetch phases deterministic; it is not a claim that an unread body still occupies Cloudflare's header-wait slot. The late fallback phase remains CoinMarketCap -> Jupiter -> DexScreener.

The same cleanup rule applies to Worker-side integration clients. Telegram delivery, X posting, and GitHub feedback submission should consume or cancel response bodies before returning so transports do not retain unread streams or unbounded response bytes.


Cron Budgeting

AreaCurrent repo budgetSourceNotes
DEX discovery overall deadline12 minutesworker/src/cron/dex-discovery/orchestrator.tsShared deadline for the discovery pass before persistence/cleanup tail work
DEX discovery per-coin budget25 secondsworker/src/cron/dex-discovery/orchestrator.tsPrevents one slow coin from consuming the whole staging lane
Stellar Horizon discovery pacing1 request start / second; 8-second stage timeoutworker/src/cron/dex-discovery/crawl-horizon-pools.ts, worker/src/lib/rate-limit.tsKeeps the native Stellar AMM census within Horizon's public 3,600-request/hour limit and inside the shared per-coin deadline
Live reserve sync outer deadline12 minutesworker/src/lib/cron-timeouts.tsExplicit wrapper budget for the serialized reserve loop before the rest of the 4-hourly slot
Live reserve sync internal run budget9 minutesworker/src/cron/sync-live-reserves-config.tsDefault cursoring budget; if the remaining budget drops below one adapter attempt, the untouched tail is marked deferred and resumed from cursor on the next run, leaving at least two minutes of wrapper headroom for D1 cleanup and cron logging. Optional finalization cleanup/history pruning is skipped when the D1 tail budget is already exhausted, and the skip is recorded in cron metadata.
Live reserve adapter I/O peak2 outbound operations per adapter attemptworker/src/cron/reserve-adapters/concurrency.ts, shared/lib/cron-jobs.tsCoin loop is serialized, but individual adapters can fan out internally; shared fetch/RPC helpers enforce the per-attempt limiter
Live reserve recovery poll / deadline5 minutes / 13 minutes; 2/6 connection peakworker/src/handlers/scheduled/reserve-recovery.ts, worker/src/lib/scheduled-recovery-checkpoint.ts, shared/lib/cron-jobs.tsWORKER_RESERVE_RECOVERY_MODE gates the isolated lane: off skips scans, shadow is read-only, reconcile prepares an exact successor without claiming, and recover claims/replays. Compatible queue hashes are selected before the bounded window. Mutating modes retire at most five finished-slot incompatible checkpoints per poll; active child/recovery leases still fail closed.
Yield publication overall deadline10 minutesworker/src/lib/cron-timeouts.tsDedicated post-V9 sync-yield-data timeout after moving off the half-hourly lane
Yield supplemental overall deadline12 minutesworker/src/lib/cron-timeouts.tsDedicated 4-hour sync-yield-supplemental timeout for optional protocol families
Telegram dispatch overall deadline4.5 minutes hard timeout; 4 minutes send-loop soft deadlineshared/lib/telegram-delivery-policy.ts, worker/src/lib/cron-timeouts.ts, worker/src/handlers/scheduled/context.tsDedicated five-minute Telegram lane timeout plus 30-second lease heartbeat; pending-drain and fresh-send loops stop starting Telegram batches near the four-minute mark, releasing pending claims or queueing the untouched fresh tail so slow Bot API runs yield the next trigger interval
Telegram authoritative target planning32 durable transitions per dispatch invocation; 90 candidate chats/page; 45 handoff targets/page; 100 statements per D1 transactionshared/lib/telegram-delivery-policy.ts, worker/src/cron/dispatch-telegram-authoritative-planning.ts, worker/src/cron/telegram-alert-target-plans/Existing due deliveries drain before source-specific candidate capture and target planning. Candidate pages union only the source's direct, resolved-preset, and global scopes. Capture/planning reuse fan-out inputs only while preference generations match. Page materialization packs complete idempotent plan units into bounded D1 transactions; handoff validates exact plans before one set-based suppression pass and one atomic enqueue/backoff/target-state batch per page.
Telegram authoritative retention24 hours workflow state; 14 days settled exact replay and legacy terminal targets; 30 days unreferenced unresolved residue; 90 days ambiguous/audit; 100,000 high-volume rows/table/dayworker/src/cron/telegram-retention-cleanup.tsTerminal subscriber/page/item/expiry workflow state ages out first. Settled target/plan/source bundles and terminal pre-authoritative targets retain 14 days and are deleted child-first. Active pending/claimed/sending or execution_unknown effects remain protected; degraded and other ambiguous audit evidence retains 90 days. Expired source-less queued jobs and expired unresolved sources without any dependent rows retain 30 days. Deletes use 10,000-row SQL sub-batches and expose cutoff, oldest-row, cap, duration, and isolated-error telemetry.
Telegram personalized recap planning90 due preferences/page, 10 pages (900 recipients), 3,000 Tape rows/page cap-plus-one, 90 min Tape freshness, 6 h pending TTL; shared-slot 5 min less risk runtime and 30 sec reserveshared/lib/telegram-recap-policy.ts, worker/src/handlers/scheduled/five-minute-telegram.ts, worker/src/cron/telegram-recap-planner.tsD1-only deterministic planning with zero AI/external planning calls. Priority 100 keeps recap work below risk, legacy, and admin pending rows; incomplete Tape windows defer instead of silently truncating. A tokened run defers after locked/incomplete/failed risk dispatch and cannot start without positive shared-slot budget.
Telegram eventless dispatch fast path5-minute dispatch cadence with fan-out skipped on every no-change runworker/src/cron/dispatch-telegram-alerts.ts, worker/src/cron/dispatch-telegram-queue-paths.tsQuiet runs drain/clean due or expired pending rows and refresh only available snapshots. An unavailable safety source preserves its held baseline; a healthy safety reseed writes directly. Neither case creates a source event, captures subscribers, or builds fresh targets.
Telegram safety source stale threshold2 canonical V9 publication intervals (60 minutes)worker/src/lib/alert-safety-source-cache.ts, shared/lib/cron-jobs.tsSafety alerts remain suppressed until compute-safety-score-v9 publishes a fresh generation-valid canonical snapshot
Canonical V9 publication cache budget<= 1.9 MB stored; <= 1.35 MB compressed; <= 8 MB uncompressedworker/src/lib/safety-score-v9-publication-codec.tsThe full public V9 payload is canonicalized, checksum-verified, and stored as gzip/base64 in D1. The reader accepts the prior deployment envelope during the one-way cache-key migration; every successful canonical publication rewrites it in the current format.
V9 production schedulingSupply at +8; DEX-bound input and transfer-materiality preparation at +16 and +46; canonical publication at +22 and +52; D1 dependency/version/global-memory-lane admission; absolute slot deadlinesworker/wrangler.toml, shared/lib/scheduled-runner-registry.ts, worker/src/lib/v9-slot-window.tsSuccessful DEX publication passes its exact generation ID directly to V9 input preparation. That stage observes transfer materiality with a 3/6 peak; the failure-independent chart writer runs serially, so the trigger stays at 3/6. Supply and publication each request a three-minute outer window, the publication runner retains its independent two-minute compiler timeout, and runV9AfterCoreWithinWindow() clamps every requested window to the next quarter-hour boundary. Lane ordering, leases, and the publication-admission fences are owned by Worker Infrastructure: Cron Scheduling.
Isolated V9 supply-attribution producer3/6 outbound connections per capture; independent DDR runs at :13/:28/:43/:58 with 0/6 outbound connections; 12-minute complete-success due interval, 14-minute complete-rejection retry, 45-minute compiler acceptance window; V9 compilation consumes the generation from D1worker/src/cron/sync-v9-supply-attribution.ts, worker/src/cron/compute-depeg-resolver.ts, worker/src/lib/safety-score-v9-supply-attribution-generation.ts, worker/src/lib/safety-score-v9-supply-attribution.ts, worker/src/lib/safety-score-v9-wm-supply-observer.ts, worker/src/lib/safety-score-v9-centrifuge-supply-observer.ts, worker/src/lib/safety-score-v9-xaut-supply-observer.ts, shared/lib/cron-jobs.tsThe attribution slot reads the prior exact V8 input, observes the exact expected asset inventory, appends the bounded journal, and atomically writes a private content-addressed generation. The independent DDR slot lazily loads and runs compute-depeg-resolver from the latest sync-stablecoins capability metadata. DDR has zero outbound fetch budget and runs in a separate scheduled invocation, so the attribution trigger remains 3/6 and DDR remains 0/6; the same service may reuse a warm isolate, but the large run-scoped graphs no longer coexist within one invocation. The attribution due intervals sit under the 15-minute trigger grid, so every attribution firing captures and the 22,52 publication consumes the :08/:38 capture roughly 14 minutes old instead of the previous cycle's at roughly 29. The capture must stay before the 16,46 prepare slot: the compiler admits a generation only when captureClockSec <= fixedInput.clockSec, since a publication must not depend on an observation taken after its own input snapshot, and prepare-safety-score-v9-input stamps that clock. A capture moved into the prepare-to-publication gap is rejected as capture-clock-after-consumer, and the cadence-defer branch then skips the publication on every subsequent cycle — observed in production on 2026-08-09 when the grid was briefly moved to 5,20,35,50. XAUT consumes the configured Tether transparency response before RPC work, requires its issuer timestamp within 48 hours, and hashes the exact response body. It then selects a finalized Ethereum block and uses one Multicall plus serialized proxy/code-identity waves with a 3-connection peak; canonical total supply, pinned treasury not-issued inventory, adapter lockbox balance, token links, endpoint, implementations, and confirmation header are bound to that block. Issuer authorized/not-issued amounts must reconcile exactly to the finalized total/treasury reads before the adapter share is divided by circulating liabilities and bound to the complete reviewed XAUt0 representation-group inventory without inferring destination shares. XAUT's explicit one-hour observation limit is preserved when an accepted attribution becomes chain-supply fact evidence; every other asset retains the generic chain-supply freshness window. The wM observer visits its four EVM deployments sequentially at safely lagged, hash-bound blocks; each route's Multicall, proxy-runtime-code read, and implementation-slot read share a 3-connection peak, followed by a same-block implementation runtime-code read. The Centrifuge observer serializes all JTRSY or ACRDX routes and keeps the same 3-connection peak: one EVM Multicall runs beside runtime-code and implementation-slot reads, while Solana reads mint and direct authority in one finalized context. A finalized Solana bank context can be a skipped slot, so the observer resolves the newest produced finalized block within the prior 64 slots before binding the observation time and hash; the three calls remain sequential and do not increase peak connections. It admits only the complete official burn/mint inventory with pinned Spoke authorization, runtime code, non-proxy state, and Token-2022 authority; lock/mint or adapter inventories are ineligible. EVM observations remain at or before the source fixed clock. wM's finalized Solana observation may be later only while the complete packet remains inside the 120-second capture envelope; Centrifuge packets require every route at or before the clock. Complete reviewed packets are all-or-nothing, and every rejected attempt records immutable bounded admission/fallback provenance plus an allowlisted exact observer leaf code; known rejected source times reuse the bounded timestamp field. A generation whose only producer rejection is XAUT transparency-stale remains cron-healthy and records diagnostic rejected-asset counters because the aggregate-only fallback is bounded and complete; unavailable sources, invalid payloads, identity drift, RPC gaps, and reconciliation failures remain blocking rejected assets for producer health. The isolated V9 compiler accepts only a schema-valid, inventory-compatible fresh generation and re-derives its USD allocations against the exact V8 aggregate liabilities. The generation's captured registry fingerprint is provenance, not an admission gate: it is global, so a release editing any registry input rotates it for every asset including untouched ones, and gating on it discarded the whole generation for one publication cycle after each deploy. Registry relevance is enforced per asset instead, where re-derivation recomputes the route inventory digest and identity pins against the live registry. A complete same-fixed-input generation that finished shortly after the scoring clock is a neutral cadence defer, preserving the prior publication instead of publishing aggregate-only partial ratings. A missing, malformed, rejected, stale, or inventory-incompatible generation fails the whole map closed to aggregate-only attribution with a clause-specific reason; a single accepted asset that fails re-derivation against its own observation window, route inventory, or identity pins fails only that asset closed and is reported in supplyAttributionGeneration.invalidAssetIds. Neither compiler path performs network fan-out.
V9 production regression envelopeFull active registry plus accepted-publication comparison and canonical V9 publication pipeline under 128 MiB Node old-spaceworker/src/lib/__tests__/safety-score-v9-resource-budget.test.tsThe out-of-process bundled Node 24 guard inflates a prior accepted publication, projects the compact gate/delta baseline, releases the full prior graph, compiles the canonical response, and compresses the replacement publication. Production follows the same accepted-first ordering, and held-publication validation reads only the stored envelope identity instead of inflating the prior response again. This is a deterministic regression envelope, not proof of Cloudflare's total per-isolate memory limit; the fenced dedicated trigger still requires observed clean production executions before operational acceptance.
V9 supply-attribution source projection< 5% of the exact input's uncompressed bytes in the full-registry regression fixtureworker/src/lib/safety-score-v9-supply-attribution-source.ts, worker/src/lib/__tests__/safety-score-v9-supply-attribution-source.test.tsInput preparation atomically writes an identity-linked projection containing only attribution-cohort identities and supply rows. The +8 producer fails closed on a missing, malformed, or stale projection and does not inflate the full compiler input before RPC capture. This supersedes the older direct exact-input read while preserving the same base-input, source-generation, registry, and clock fences.
Telegram registration reconciliation peak1 outbound Bot API call at a timeworker/src/lib/telegram-webhook-registration.ts, shared/lib/cron-jobs.tsRuns serially before dispatch-telegram-alerts when 15-minute cache markers expire; modeled as the budget-only telegram-registration-reconciliation entry in the same five-minute Telegram connection group and surfaced through /api/status.budgetOnlySurfaces.
Telegram digest outbox retry peak1 Bot API call at a time; up to 4 due editions per five-minute pollworker/src/lib/telegram-digest-outbox.ts, worker/src/handlers/scheduled/digest-trigger-poll.tsRuns serially on the existing */5 digest-trigger slot. Retryable HTTP failures retain the exact stored chunks and honor retry_after; ambiguous and permanent outcomes stop for operator review. Retained terminal backlog degrades budget-surface telemetry but does not repeatedly feed the Telegram provider circuit when no network attempt occurred.
Daily digest social attachment peak1 outbound request at a timeworker/src/cron/daily-digest.ts, worker/src/lib/digest-safety-map.ts, worker/src/lib/twitter.ts, shared/lib/cron-jobs.tsThe 08:05 UTC digest chain serializes manifest GET, dated-image HEAD, optional X image GET/media upload/post, and Telegram delivery after the Anthropic request. The extra map requests do not raise the slot's 1/6 connection peak; every response is consumed or cancelled before the next phase. Missing/stale map state and X media failures fall back to text-only social delivery.
Persisted cron metadata ceiling<64 KiB per cron_runs.metadata payloadworker/src/lib/cron-metadata-persistence.ts, worker/src/lib/cron-logger.tsGlobal compaction retains counts, bounded samples, and latest drill-down state while preventing large asset/config arrays from driving unbounded D1 growth.
GBP SONIA retained-fallback degradation2 consecutive daily retained-fallback runsworker/src/cron/fetch-tbill-rate.tsThe first gbp-sonia-compounded-index-failed-retained run writes cache["fetch-tbill-rate:gbp-retained-fallback-streak"]; the second consecutive run remains visible through degraded cron status. Fresh GBP market data resets the streak.
Telegram pulse heavy-section cadence15 minutesworker/src/api/telegram-pulse.ts, worker/src/lib/telegram-usage-analytics.tsCurrent aggregate pulse counts still refresh every 5 minutes. Top coins, lifecycle history/fallback, and Mini App daily counters reuse a rendered pulse section for up to 15 minutes; pending-delivery count can reuse the dispatch lane's pending-capacity snapshot.
Telegram load simulation targets500, 1,000, 5,000, 10,000 active watchers plus production calibration at 855 subscribers / 338 candidates / 300 targetsshared/lib/telegram-delivery-policy.ts, shared/lib/telegram-recap-policy.ts, scripts/ci/check-telegram-load.tsnpm run check:telegram-load estimates drain time, per-invocation estimatedCpuMs, and D1 operations for risk, admin, and personalized-recap scenarios. The production-calibrated dispatch gate additionally enforces candidate-horizon reduction, one fan-out load per capture page without invalidation, at most eight D1 round trips per bounded handoff page, and projected wall time below three minutes. Recap scenarios enforce a 5,000-recipient target across all-due, risk burst, 429 storm, preset-heavy, global-scope, no-change, and stale-Tape cases, with aiCalls=0 and externalPlanningFetches=0; they also emit a non-enforced 10,000-recipient advisory. The recap planner uses a 900-recipient/5-minute-run page budget, a 6-hour pending TTL, and priority 100. The CPU dimension (C102) reads cpu_ms from worker/wrangler.toml and fails when required scenarios exceed 0.5×cpu_ms. scripts/lib/telegram-load-guard.mts owns the reviewed dependency triggers for local and CI execution.
Telegram group admin membership cache5 minutes for cached denial/admin-list copy; mutating auth revalidates per webhookworker/src/lib/telegram-chat-member.ts, worker/src/api/telegram-webhook-auth.ts/subscribe, /unsubscribe, /set in group/supergroup chats use a fresh getChatMember lookup for mutation authorization and fail closed if Telegram cannot confirm admin status. Soft-launch warnings and denial copy can still use the cached getChatAdministrators list (telegram:chat-admins:<chat_id>). These calls happen on webhook ingress, not inside the dispatch cron lane.
Live reserve history retention30 daysworker/src/lib/live-reserves-store-shared.ts, worker/src/lib/live-reserves-store-write.tsreserve_composition_history and reserve_sync_attempt_history are pruned during reserve-sync cleanup
Redemption route-status producerD1-free/static plus live-reserve metadataworker/src/lib/redemption-backstop-route-status.ts, worker/src/cron/sync-redemption-backstops.tsv4 route status remains four-hour snapshot data. sync-redemption-backstops does not add outbound route-status feed fetches; route availability comes from existing live-reserve adapter metadata, reviewed static policy, and market-implied severe-depeg overlays.
Blacklist sync runtime budget10 minutesworker/src/cron/sync-blacklist.tsGuardrail below the 12-minute trigger wrapper timeout
Blacklist sync subrequest budget900worker/src/cron/sync-blacklist.ts, worker/src/lib/evm-logs.tsCovers explorer/RPC calls for a single run
Blacklist provider pacing/concurrency3 requests/second, 1 live requestworker/src/cron/sync-blacklist.ts, worker/src/lib/evm-logs.tsThe serial limiter's declared concurrency matches the live connection posture; throughput and live connections are reported separately in cron metadata
Blacklist RPC topic aggregationOne OR-topic eth_getLogs request per range when supportedworker/src/cron/blacklist/evm-source.ts, worker/src/lib/alchemy-logs.tsRequired topic0 signatures share one completeness frontier; explorer fallback remains per-topic where OR filters are unavailable
Blacklist Alchemy split cap64 provider calls per log scan, depth 8worker/src/lib/alchemy-logs.tsSplit traversal is sequential and also bounded by deadline and the shared 900-subrequest budget; hitting any cap returns incomplete coverage without advancing beyond proof
Blacklist Arbitrum scan window25,000,000 explorer/Alchemy-primary blocks; 250,000 fallback-RPC blocks per config/runworker/src/cron/blacklist/evm-source.tsEvery topic shares the minimum proven frontier; the stored cursor never exceeds the 15-minute safe head
Blacklist amount-recovery batch100 queued rows / passworker/src/lib/blacklist/amount-recovery.ts, worker/src/lib/blacklist/amount-repair-queue.tsPriority/retry state is durable; event and queue outcomes are paired in shared D1 batches and stay under the sync subrequest/runtime budget
Mint/burn global request budget200worker/src/cron/sync-mint-burn.tsShared per-run request ceiling
Mint/burn D1 retentionevents: 8 days, 10,000/batch, 50,000/critical run; hourly: 95 days, 10,000/batch, 25,000/critical runworker/src/cron/mint-burn/retention.ts, worker/src/cron/sync-mint-burn.tsOldest-first cleanup runs only in the critical producer. Event deletion requires settled valuation, an existing stablecoin/chain/hour aggregate, and a persisted Tape cursor at or beyond the event timestamp; protected debt can outlive the nominal cutoff. Cleanup errors degrade successful ingestion and remain visible in cron metadata.
Mint/burn runtime self-budget9 minutes; 60 seconds minimum next-config windowworker/src/cron/mint-burn/run-configs.ts, worker/src/lib/alchemy-logs.tsGuardrail below the 10-minute cron wrapper timeout so the runner can stop before starting a config that would not leave enough persistence/logging tail room. Mint/burn eth_getLogs calls receive the same runtime deadline so a started config cannot continue log-scan recursion past the self-budget.
Mint/burn per-config budget (critical)60; 150 for bridge-aware critical configsworker/src/cron/sync-mint-burn.tsPrevents one hot config from consuming the full run while allowing bridge-aware high-volume configs enough tx-context headroom
Mint/burn tx-context batch size20 tx hashes/request; 3 concurrent batch requestsworker/src/lib/mint-burn-pipeline/classification.tsKeeps bridge-aware classification under the Worker connection pool and avoids one HTTP request per parsed USDC-style mint/burn transaction
Mint/burn per-config budget (extended)25worker/src/cron/sync-mint-burn.tsLower ceiling for long-tail backlog drain
Mint/burn max scan range50,000 blocksworker/src/cron/sync-mint-burn.tsKeeps per-request log scans bounded
Mint/burn timestamp resolution scopeNon-dust candidate logs onlyworker/src/cron/mint-burn/sync-config.tsDust-only blocks are excluded before eth_getBlockByNumber lookups so irrelevant logs cannot block sync-state advancement
Mint/burn SQL IN chunk size90 idsworker/src/cron/sync-mint-burn.tsCurrent safeguard for large batched SQL
Mint/burn event insert batch size50 statementsworker/src/lib/mint-burn-pipeline/persistence.tsEach insert binds 18 values; chunked to stay below D1 batch bind ceilings
Mint/burn extended attempt SLO2 due runs / 75 minutesworker/src/cron/mint-burn/run-state.tsThe next run resumes at the first capacity-deferred config; active provider deferrals are explicitly exempted until their recorded expiry
Stablecoin producer metadata ceiling<60 KiB before scheduler enrichment; <64 KiB persistedworker/src/cron/sync-stablecoins/metadata.ts, worker/src/lib/cron-metadata-persistence.tsThe stablecoin producer reserves 4 KiB for wrapper-owned lease, slot, invocation, and timeout metadata. Its size guard compacts diagnostics before that enrichment so top-level publication and active-price coverage survive unchanged into cron_runs; the global <64 KiB persistence guard remains the final fallback.

D1 overload retry posture

Cron persistence helpers retry transient D1 queue pressure through runWithOverloadRetry() in worker/src/lib/d1-overload-retry.ts. Retried errors include D1 DB is overloaded, Requests queued for too long, D1 storage-operation reset timeouts (D1 DB storage operation exceeded timeout ...), Cloudflare D1 internal-reference errors (D1_ERROR: internal error; reference = ...), and transient D1 transport loss (Network connection lost). Retry backoff is abort-aware, and batchExecute() can accept an AbortSignal so chunked persistence can stop before the next D1 batch after cron timeout or lease loss. Every status-tracked job in shared/lib/cron-jobs.ts must have an explicit CRON_TIMEOUT_MS ceiling; the scheduled-runner contract test fails when a new cron job would fall back implicitly. If a leased job ignores timeout or lease-loss aborts, runCronWithLease() records an abandoned failure and leaves the lease row to expire by TTL instead of releasing it while late writes may still be running. Long-job lease heartbeats renew every 30 seconds and abort controlled work after three consecutive renewal failures. The duration watchdog excludes stale-slot reconciled synthetic child rows from runtime averages, keeps 7-day runtime totals for diagnostics, but requires repeated cap hits or unrecovered runBudgetTruncated rows (those without a persisted deferral cursor, cursorTailState <> 'complete') to reach the alert threshold inside the 24-hour recency window — cursor-complete truncations are the designed graceful-deferral path and no longer alert; cron_slot_executions remains the separate scheduled-slot abandonment signal. DEX source-stage chunks use deterministic one-row direct conflict upserts with progress writes every 24 chunks plus the final interval, and an ambiguous final manifest update is accepted only after an exact readback proves the ready/consumed schema, slot, chunk, record, and byte totals. DEX-liquidity publication streams 15 candidate rows at a time into five three-row statements, staying below D1's 100-bind ceiling while reducing statement and batch-call overhead; it still validates the complete per-run generation before updating the public current table. Live reserve, redemption-backstop, cache-sentinel, and DEWS persistence paths should route bursty run-manifest writes, cleanup, prune, and chunked batch work through this helper or batchExecute() so one transient D1 queue spike does not fail a whole scheduled run.

D1 query budgeting

Public health/status diagnostics intentionally trade a few minutes of operator telemetry freshness for lower D1 pressure:

  • queryBlacklistGapMetrics() supports a core diagnostic mode that skips the amount-status and amount-source distribution scans when callers only need total/recoverable/recent gap counts for health scoring.
  • sync-blacklist materializes producer-owned blacklist gap snapshots into the existing D1 cache table only after every required config completes successfully in the run, no state CAS conflicts occur, and enough snapshot budget remains. Any skipped or incomplete config degrades the run and withholds publication. The snapshot timestamp is the oldest required config's successful-scan time, preventing a recent cron finish from masking a stale source cohort. It writes both full summary metrics and core health/status metrics from one live full query. /api/health, /api/status, and /api/blacklist-summary prefer those producer snapshots, then fall back to the short request cache and finally the live query if the producer row is missing or stale.
  • /api/blacklist-summary also has a producer-owned summary payload snapshot in the same D1 cache table. The handler preserves the public response shape, serves a fresh producer payload directly, serves a stale-but-present producer payload without rewriting the producer cache from public traffic, and falls back to live construction only when no valid snapshot has ever been written. Freshness headers are based on the producer snapshot timestamp, so stale-but-served fallback snapshots still emit the normal Warning header once they exceed the blacklist summary max age.
  • The short 5-minute D1 request cache remains only as a fallback pressure valve. It should not be treated as the freshness authority for blacklist health; producer materialization is the preferred source when present.
  • Cron health reads the latest 10 runs per tracked job through the existing (job, started_at DESC) index instead of applying a window function across the retained cron_runs table.
  • Tron blacklist amount recovery mirrors blacklist_current_balances through deterministic current-balance IDs and primary-key id IN (...) lookups instead of correlated LOWER() subqueries against the ledger table. The mirror is capped at 100 candidate events per pass and receives the same runtime budget as sync-blacklist.
  • DEWS producers maintain stress_signals_latest as the full latest-row materialization for Telegram dispatch and smoothing, sparse stress_signals history at least hourly plus material changes, and two exact generations in stress_signal_publication_rows. Pointer-bound public and PSI reads verify the exact buffered generation; a partial newer write cannot replace the previous proof. A missing dews:published-generation pointer is treated as first-run bootstrap and can use the unbounded latest-row fallback; corrupt or unreadable publication pointers return unavailable current rows instead of silently switching to an unbounded generation.
  • The active baseline contains the chain/hour mint_burn_hourly, stablecoin/time yield, depeg pagination/open-event indexes, and stress_signals_latest materialization introduced by historical migration 0144. Keep matching SQL comments stable so D1 insights can attribute reads to source-owned query families; use the migration manifest for lineage.
  • D1 capacity samples are hourly and bounded. Utilization boundaries, the growth-regression windows, and the conservative exhaustion forecast are documented in docs/operator procedures/d1-capacity-and-runtime-experiments.md. The scheduled status lane refreshes the control-plane observation; public health reads only the bounded cached assessment.
  • The five-minute Telegram lane reuses same-run pending-capacity and safety-source reads between dispatch, degradation watchdog, and pulse publication. Healthy no-change dispatch runs skip fan-out and fresh-send assembly while preserving pending drain, TTL cleanup, snapshot freshness, and watchdog-visible metadata. Source-event runs capture only direct/preset/global candidates relevant to the active families, reuse unchanged page fan-out inputs between eligibility and routing, and emit nested authoritativePlanning phase timings/counts in dispatch metadata.
  • Telegram registration reconciliation still keeps separate webhook, command, profile, and menu cache checks so each helper remains directly callable and testable. Coalesce those cache reads only with a helper that preserves the current standalone semantics and Bot API rate-limit backoff behavior.
  • Cron progress writes still go through the shared scheduled-runner logging path. There is no Telegram-specific progress-write opt-out today; add a generic, documented opt-out before reducing progress persistence for one lane.

When adding a new health/status loader, prefer a producer-cadence cache or an indexed top-N read over table-wide aggregates. Table scans are acceptable for low-frequency admin-only diagnostics, but not for public health probes, status self-checks, or same-origin browser polling.

Large cache-backed endpoints can opt into a response-ready companion cache when the producer has already validated the canonical payload. /api/stablecoins writes stablecoins:response-ready:v2 alongside the canonical stablecoins cache row after schema validation; the reader serves that raw body only when both rows share the same updated_at, then injects freshness metadata without reparsing the full payload. Companion rows are best-effort optimizations: read or write failures fall back to the canonical cache path and should not fail the producer. Endpoints with live transforms should not use this shortcut unless the companion body already includes those transforms.


Upstream Fetch Budgets

PathCurrent repo throttle / budgetSourceNotes
CoinGecko onchain discovery250 ms between requestsworker/src/lib/rate-limit.tsUsed by discovery crawlers
CoinGecko onchain discovery stage timeout8 seconds per crawl stageworker/src/cron/dex-discovery/staged-pool.tsDISCOVERY_STAGE_TIMEOUT_MS.cgOnchain, inside the 25 s per-coin and 12 min run budgets
CoinGecko backfill throttle200 ms between requestsworker/src/lib/rate-limit.tsUsed by CoinGecko backfill/admin flows
CoinGecko native-peg quotes50 ids/request, 10 s timeout/request, 1 retry; one shared batch per (vs_currencies, id set) per stablecoin sync runworker/src/lib/native-peg-quotes.ts, worker/src/cron/sync-stablecoins.tscreateNativePegQuoteSession() memoizes each batch response for the run so native-peg hardening, depeg detection, and pending-depeg confirmation reuse one fetch instead of one each; non-2xx responses, malformed payloads, and transport failures are never memoized
GeckoTerminal crawl throttle2000 ms between requestsworker/src/lib/rate-limit.tsConservative crawl pacing
GeckoTerminal discovery stage timeout8 seconds per crawl stageworker/src/cron/dex-discovery/staged-pool.tsDISCOVERY_STAGE_TIMEOUT_MS.geckoTerminal, inside the 25 s per-coin and 12 min run budgets
Protocol-redeem live override stage10 s total budget; grouped circuit for most external live RPC-backed providers, with dedicated Kava, JUSD, and AZND circuitsworker/src/lib/authoritative-price-sources/index.tsMissing-price candidates run before already-priced candidates and provider families are interleaved within each partition. Missing-only thin routes are excluded when a usable incumbent exists. Local par and inherited tracked-base overrides are cache/local decisions; circuit-open RPC providers fail closed.
Kava USDX oracle adapter4 sequential requests, 2.2 s timeout/request, 0 retries, 256 KiB max response/request; inside the shared 10 s live-override budgetworker/src/lib/authoritative-price-sources/kava-pricefeed.tsReads head, market, aggregate, and raw-oracle state serially. Wrong chain/market/oracle identity, a head older than 2 minutes, insufficient oracle expiry, or excessive dispersion fails closed.
Reviewed exact price routesInside the shared 10 s live-override budget; adapter-specific bounded RPC calls and executable notionalsworker/src/lib/authoritative-price-sources/sAID, AZND, and JUSD pin exact contracts/tokens and reject stale state, identity drift, missing trusted parents, insufficient capacity/depth, route-specific impact/divergence failures, failed public redemption, or RPC failure. AZND calls stay serial to preserve connection headroom.
DexScreener / CG-tickers fallback stage timeout6 seconds per fallback stageworker/src/cron/dex-discovery/staged-pool.tsDISCOVERY_STAGE_TIMEOUT_MS.dexscreener and .cgTickers; both late-stage fallbacks share the same per-coin deadline
Jupiter price fallback50 ids/request, 5 s timeout/request, 0 retries; up to 25 low-depth primary augmentation targets/run; at most 3 sequential 3 s slot-RPC probes/passworker/src/cron/sync-stablecoins/enrich-prices-jupiter-pass.tsSolana-only enrichment pass between CMC and DexScreener; can append agreeing Jupiter evidence to low-depth primary prices and fails closed when every bounded slot reference is unavailable
DexScreener price-enrichment pass1 request/run, up to 30 same-chain addresses/request, 5 s timeout, 45 s total budget, 0 retriesworker/src/cron/sync-stablecoins/enrich-prices-dexscreener-pass.tsBest-effort final fallback for missing prices through exact token-address lookups; chains and large within-chain cohorts rotate, symbol search is retired, and hard 429 / WAF 1015 refusals end the pass
Address-price augmentation groupCoinGecko Onchain exact-address provider enabled in production; 90 s shared group budget, 5 s timeout/request, 0 retriesworker/src/lib/address-price-providers/index.ts, worker/wrangler.tomlRuns during primary pricing for assets with missing prices, observations expiring before the next generation, low-confidence prices, or previous source depth below 3. Durable target cursors rotate only inside missing, expiring, low-depth, and remaining-priced cohorts. The public GeckoTerminal corroboration pass is not run inline because its repeated public errors and response retention exceeded the Worker memory boundary; its address-price provider has since been removed entirely.
vaults.fyi supplemental yield13 estimated credits/run on the four-hour lane; 2,500 estimated credits/UTC monthworker/src/cron/yield-sync/vaults-fyi.tsGeneration-fences one reservation owner with compare-and-swap, finalizes only the matching owner, dynamically throttles to the sustainable remainder, and fails closed before paid requests when the monthly ledger is corrupt; no provider-authoritative usage counter is available on this path
Coinbase CEX ticker products1 product request in flight, 10 s timeout/request, 1 retryworker/src/lib/cex-tickers.tsKeeps the Coinbase thunk inside the sync-stablecoins primary-provider cap instead of multiplying the quarter-hourly trigger fanout
Secondary FX mirror race3 mirror requests in parallelworker/src/cron/sync-fx-rates-sources.ts, shared/lib/cron-jobs.tsjsDelivr @latest, direct Pages mirror, and date-pinned jsDelivr package race inside the sync-fx-rates max connection declaration
Chainlink FX/commodity overlay3 feed pipelines in parallelworker/src/lib/chainlink-feeds.ts, shared/lib/cron-jobs.tsKeeps the five-feed overlay inside the sync-fx-rates three-connection cron metadata while preserving bounded parallel recovery
DexScreener address augmentationopt-in; 1 request/run, 30 addresses/requestworker/src/lib/address-price-providers/dexscreener.tsExact /tokens/v1/{chain}/{addresses} lane; no symbol search; stops on hard upstream refusal
DexPaprika address augmentation60 token-detail requests/runworker/src/lib/address-price-providers/index.tsPublic exact token-detail lookup
CoinGecko Onchain address augmentation5 requests/run, 30 addresses/requestworker/src/lib/address-price-providers/index.tsKeyed exact onchain token lookup, separate from the serialized GeckoTerminal pool probe
Alchemy Prices address augmentation20 requests/run, 25 addresses/requestworker/src/lib/address-price-providers/index.tsOptional keyed lookup, grouped by Alchemy network id
Moralis address augmentation3 requests/run, 100 addresses/requestworker/src/lib/address-price-providers/moralis.tsOptional keyed EVM batch lookup; capped below the free-plan 40k CU/day envelope for the 15-minute sync cadence
Birdeye address augmentation10 Solana requests/run, 1000 ms between requestsworker/src/lib/address-price-providers/index.tsOptional keyed Solana-only targeted gap lookup; known no-price payloads count as missing-quote, while 429 or provider-wide quota/compute-unit exhaustion stops the remaining request tail immediately
CoinMarketCap fallbackUp to 2 requests in an eligible hour: 1 category request plus 1 targeted request for at most 25 rotated unresolved slugs; 10 s timeout/request, 0 retriesworker/src/cron/sync-stablecoins/enrich-prices-cmc-pass.tsUsable returned category rows survive an unseen tail. Targeted quotes require exact identity, active status, freshness, positive volume, required supplied-contract agreement for known deployments, and peg validation. Verified targeted rows preserve their upstream timestamp through the one-hour cooldown; 429 still honors Retry-After.
DEX primary source JSON reads30 s per attempt through fetchJsonWithRetry(); Curve API fan-out capped at 4 chain requestsworker/src/cron/dex-liquidity/fetch-primary.ts, worker/src/lib/concurrency.tsDeFiLlama Yields and Protocols are consumed before the bounded Curve phase starts, keeping Curve below the declared DEX job peak. The defillama-protocols cache stores a compact slug/category snapshot for yield coverage audits, and raw Curve response trees are released once their derived lookup maps are built
Direct DEX API fetch phase1 protocol family at a time, 5 nested connection static peak within a provider, 90 s provider timeout wrapping 15 s per-request timeouts, deterministic page caps (50 default); Fluid ticker retries cap provider Retry-After sleeps at 5 s per retryworker/src/cron/dex-liquidity/orchestrator-phases/direct-api.ts, worker/src/cron/dex-liquidity/direct-api-policy.ts, worker/src/cron/dex-liquidity/direct-api-paginated.ts, protocol fetchers, shared/lib/cron-jobs.tsRuns hourly at :10 inside sync-dex-liquidity-stage through a direct-API-local circuit/timeout wrapper; mapWithConcurrency(..., 1, ...) owns serial provider execution, and each result is reduced to tracked pools plus compact raw counts and authoritative exact keys before the next family starts. check:cron-connections enforces the declared 5/6 peak because one provider can use the nested width.
Measured DEX execution RPC lanesActive: 3 EVM chain lanes every 30 minutes with the existing 1,300 request, 6,400 quote-subcall, and 8-minute caps. Shadow: shadow-only EVM once daily.worker/src/cron/measured-execution/sync.ts, shared/lib/cron-jobs.tsThe isolated 0,30 * * * * trigger runs the active 3/6 EVM lane. Shadow target inventories publish at 06:16 UTC; the shadow EVM collector runs at 08:10 UTC (the Solana and Tron native lanes were removed in Liquidity Score v6.0). Shadow generations never enter the active scorer. Quote publications omit manifest-proven budget-deferred rows and reconstruct them on read, while measured outcomes and real failures remain durable.
Shared retry JSON/text response body16 MiB default; configurable per request with maxResponseBytesworker/src/lib/fetch-retry.ts, worker/src/lib/response-body.tsfetchJsonWithRetry() and fetchTextWithRetry() reject declared or streamed overflow, cancel the body, and retry through the existing warning path; JSON is parsed only after a complete in-cap body. Raw fetchWithRetry() responses remain caller-owned and uncapped.
Generic circuit breakeropens after 3 consecutive failures, probes every 30 minutesworker/src/lib/circuit-breaker.tsUsed to stop hammering degraded upstreams

JSON/text fetch callers that need per-request timeout coverage across body consumption should use fetchJsonWithRetry() or fetchTextWithRetry() rather than calling fetchWithRetry() and then consuming the returned Response separately. Provider execution wrappers (providerJson() and providerTextBounded()) keep their provider timeout active through body reads and record body-read timeouts as provider failures.

What this means operationally

  • sync-dex-liquidity-stage consumes discovery output, loads external source families, constructs the ordered pool graph, and stores it as generation-fenced 192-KiB chunks hourly at :10.
  • sync-dex-liquidity is D1-only. At :16 it refreshes prices hourly and publishes liquidity scores/history on even UTC hours; at :46 it returns the exact current generation for V9 preparation without rewriting DEX surfaces.
  • Rate-bearing Curve StableSwap-NG enrichment runs only after the source graph and direct-provider phase have completed. It serializes chains, pins a fresh head/header, reads bounded pool batches (get_balances, stored_rates, A, fee, offpeg_fee_multiplier, and ordered coins) at that block, then confirms the same header hash before accepting a model. RPC, freshness, token-order, rate, fee, or dynamic-fee failures publish no model and retain the capability gate; this sequential phase does not increase the source trigger's declared five-of-six connection peak.
  • Active sync-cl-exit-depth consumes the previously published active retained-pool target generation and atomically publishes the next sparse measured-quote generation every 30 minutes. The even-hour :16 scoring consumer joins only fresh score-eligible evidence and then publishes the next active target inventory; the daily evm-shadow generation never enters the active scorer.
  • EVM admission may reserve one currently published score-bearing direction packet closest to adapter-specific expiry, capped at 20 estimated requests within the 1,220-request admission ceiling. The reviewed legacy 3pool directions remain atomic; the reservation does not advance the cursor, and all remaining targets keep whole-coin rotation.
  • Hook-free Ethereum Uniswap V4 shares the EVM lane without widening its limits: source enrichment is serialized after the existing subgraph families, PoolManager/StateView/Quoter verification is deduplicated per deployment, and state/quote calls use the existing eight-call Multicall batches under the 1,300-request and 16-MiB JSON-body ceilings. A transport-failed quote batch recursively fragments within the 80-request reserve; recovered sub-batches remain usable, while terminal singleton failures stay degraded.
  • The exact legacy Curve 3pool adapter shares one Ethereum block and one deployment/registry verification across its direction packet. Its two output directions join atomically, including when reconstructing a retained route-only packet. The measured packet becomes score-facing only after both directions have three complete and three successful fresh cycles; until then the reserve simulation stays score-facing. Its last-known-good quote and history use a two-hour ceiling so three half-hour observations survive scheduler jitter; the retained profile keeps its original quote block and timestamp, then falls back to the reserve model on expiry. Exact absent bytecode is semantic drift and cannot retain last-known-good evidence, while an unavailable RPC response remains an operational failure. Other measured adapters retain their one-hour ceiling. This does not widen the EVM request/runtime ceilings.
  • sync-dex-discovery is deliberately best-effort. Short per-source request timeouts and the 12-minute shared budget are there to force a partial degraded result before the platform can hard-kill the invocation. Lower-priority tier-2/tier-3 candidates are deterministically sharded across their cadence windows so one modulo run does not inherit the entire tier queue at once.
  • Missing-price fallback is intentionally time-bounded so a bad upstream day cannot consume the whole sync-stablecoins slot.
  • Replay-safe price continuity has a hard six-hour ceiling and also obeys any shorter per-source maxTrustedAgeSec. Low/fallback or non-replay-safe sources are never eligible. The separate verified-CMC provider cache is limited to the original quote's one-hour age, preserves its upstream timestamp, stays fallback confidence, and revalidates identity and peg bounds on reuse.
  • activePriceCoverage is independent from cron execution and cache publication. A missing active price degrades public health while the valid remainder of the stablecoins payload stays available and a successfully published cron run remains ok.
  • Exact adapters are fail-closed availability paths. A stale head, RPC/provider outage, identity drift, missing parent, depleted bridge, insufficient pool depth, or excessive quote impact produces no price; operators must not compensate by increasing replay age or substituting nominal parity.
  • Any new provider added to discovery or price enrichment should come with both a throttle and a hard stop budget.

Request Timeouts Worth Preserving

AreaCurrent timeoutSource
CoinMarketCap price fallback10_000 msworker/src/cron/sync-stablecoins/enrich-prices-cmc-pass.ts
Jupiter price fallback5_000 msworker/src/cron/sync-stablecoins/enrich-prices-jupiter-pass.ts
DexScreener price fallback requestsup to 5_000 ms per requestworker/src/cron/sync-stablecoins/enrich-prices-dexscreener-pass.ts
Direct DEX API requests15_000 ms per requestworker/src/cron/dex-liquidity/direct-api-policy.ts
Measured DEX execution RPC requestsEVM up to 15_000 ms, clipped to the run's absolute 8 minute deadlineworker/src/cron/measured-execution/sync.ts
Ops admin proxy reads20_000 ms for /api/status and /api/status-history; 45_000 ms for /api/audit-depeg-historyshared/lib/api-endpoints/definitions.ts (opsProxyTimeoutMs)
Live reserve adapter attempt20_000 msworker/src/cron/sync-live-reserves-config.ts
Live reserve D1 finalize timeout30_000 msworker/src/cron/sync-live-reserves-config.ts
Public dataset snapshot outer deadline10 * 60_000 msworker/src/lib/public-dataset-snapshot-budget.ts, worker/src/lib/cron-timeouts.ts
Blacklist explorer / RPC reads15_000 msworker/src/lib/fetch-retry.ts (default timeout)
Daily digest LLM call (outer)12 * 60_000 msworker/src/lib/constants.ts
Daily digest per-attempt fetch11 * 60_000 msworker/src/cron/digest/platform.ts (DIGEST_FETCH_PER_ATTEMPT_TIMEOUT_MS)

Live reserve timeout values are resolved through LiveReserveSyncBudgetConfig. Production uses the checked-in defaults above; tests and operational wrappers can inject smaller or larger positive finite values to validate deferred-tail and D1-finalize behavior without changing cron code.

/api/audit-depeg-history is also hard-capped at 25 items per request (limit default and max) so the CoinGecko-backed admin audit stays page-sized over the Pages proxy.


Anthropic / Digest Runtime

Current digest generation constraints that are actually encoded in repo code:

  • model: per-job typed configuration, with both DAILY_DIGEST_LLM_CONFIG and WEEKLY_RECAP_LLM_CONFIG defaulting to claude-opus-5. Opus 5 adds safety classifiers, so the request path treats stop_reason=refusal as policy rather than infrastructure.
  • thinking: adaptive (thinking.type = "adaptive")
  • reasoning effort: xhigh (output_config.effort = "xhigh"). This is deliberate: Opus 5 at high omitted the documented, mandated forward-look line on both measured daily editions, while xhigh preserved the incumbent's zero-soft-issue quality. The 16k ceiling, rather than lower effort, answers the cost constraint.
  • Anthropic outer timeout: 12 * 60_000 ms (12 min), bound by AbortSignal.timeout(ANTHROPIC_TIMEOUT_MS) in platform.ts
  • per-attempt fetch timeout: 11 * 60_000 ms (11 min), local to requestDigestCopy; safety net so a single stalled attempt cannot consume the outer budget
  • retry depth for the digest Anthropic call: 2 (max 3 HTTP attempts); every HTTP attempt is recorded independently
  • corrective retry skip: if first-pass elapsed >= 50% of the outer budget (6 min), the in-process retry after quality failures is skipped; the parse is accepted with qualityIssues flagged degraded
  • daily cron lease (wrapper timeout): 14 * 60_000 ms (14 min), leaves ~2 min under Cloudflare's 15-min scheduled-event ceiling for D1 persistence, Twitter/Telegram delivery, and cron_runs logging.
  • daily-digest heartbeat override: heartbeatSec = 30, maxRenewFailures = 3 (see worker/src/handlers/scheduled/context.ts — default policy unchanged for other jobs)
  • weekly cron lease: 12 * 60_000 ms (12 min)
  • max_tokens: 16000 daily, 16000 weekly. The old 64k value was a runaway guard, not a spend guard — and neither is max_tokens on its own, because a fetch-level timeout after Anthropic produced output is billed and still retried (up to 3 HTTP attempts per leg, 2 legs, so 6 billable generations worst case). Spend is bounded instead by DIGEST_MAX_EDITION_OUTPUT_TOKENS (24000), an aggregate per-edition output budget enforced by reservation: a request starts only when its entire max_tokens still fits, an unknown post-submit failure is charged the full ceiling, and an HTTP-status rejection is charged nothing (no generation occurred, so overload retries stay functional). Worst-case blended cost is then about $1.10/day, below the $1.15 ceiling. A consequence worth knowing: after an unusually large first generation the corrective retry is deliberately skipped rather than overspending, which surfaces as a blocked edition. stop_reason=max_tokens remains a hard pre-parser failure and therefore a loud truncation tripwire.
  • measured token/cost telemetry (production prompts, editions 2026-08-21 and 2026-08-07 plus the weekly): incumbent Opus 4.8 at xhigh/64k emitted 18,258 and 10,146 daily output tokens and 7,806 weekly, with $0.550/day blended cost and $1.104/day at the measured worst-case retry rate (96% of the $1.15 ceiling), with zero soft issues. Opus 5 at xhigh/16k emitted 10,857 and approximately 4,300 daily output tokens and 4,808 weekly, with $0.354/day blended cost and $0.712/day worst case (62% of ceiling), with zero daily soft issues. Opus 5 at high/16k emitted 4,879 and 2,981 daily output tokens and 2,616 weekly and cost $0.197/day, but both dailies raised missing-forward-look; that quality regression rejects high despite the additional roughly $0.16/day saving.
  • refusal fallback: requests set fallbacks: "default" with beta header server-side-fallback-2026-07-01. Server-side fallback preserves one streaming request and one outer timeout; a manual second model call could exceed the 12-minute Anthropic budget, the 14-minute wrapper, or Cloudflare's 15-minute scheduled-trigger ceiling after HTTP retries. If fallback still ends in stop_reason=refusal, all partial text is discarded, stop_details.category is retained, no edition is published, and the policy outcome neither damages nor heals the Anthropic circuit breaker.
  • cadence: daily scheduled run plus deferred manual admin trigger (see "Manual trigger runtime model" below)
  • cost accounting: pricing is external and can change independently of this repository. The worker now records every original, corrective, and HTTP attempt in cron progress/run metadata and stores successful-edition provenance in daily_digest.digest_meta.llm: requested and served model, effort, max tokens, input/cache-read/cache-write/output tokens, attempt identities, stop reason, refusal category, latency, HTTP status, and computed USD cost. Provider billing remains authoritative if the checked-in price table drifts.

Manual trigger runtime model

POST /api/trigger-digest does not execute the digest synchronously. It writes a bounded intent record (pending, attempts, nextAttemptAt, and lastError) into the digest:force-run-request cache row and returns 202. A dedicated */5 * * * * cron slot (digestTriggerPoll) reads due intents and runs the digest under scheduled-event wall-clock (15 min). Transient network, timeout, 5xx, rate-limit, and D1 failures retry with 2 * poll interval * attempts backoff for at most three attempts; validation, authorization, and other permanent failures dead-letter immediately, and exhausted retries remain as retained dead_letter state. A poll with no pending intent is a neutral idle slot and cannot create a synthetic not-started daily-digest failure; if a digest created durable progress and then lost ownership, stale-slot reconciliation still records that real abandoned attempt. The existing daily-digest lease remains authoritative: lease contention leaves the intent untouched for the next poll. Outcome is persisted to digest:last-trigger-result for D1 inspection and future ops-UI surfacing; the current admin panel still shows only the enqueue result from the browser session.

This two-step model exists because the repo treats long HTTP-triggered ctx.waitUntil() digest execution as unsafe on Cloudflare Workers. The external platform assumption is that HTTP request tail work can be canceled after a short post-response window, while scheduled events get the full scheduled-event wall-clock; digest runs can take several minutes, so enqueue + scheduled polling is the repo-verified safe path.

Source: worker/src/api/admin-actions.ts (enqueue-only HTTP handler), worker/src/handlers/scheduled/digest-trigger-poll.ts (polling consumer).

Source: worker/src/lib/constants.ts (Anthropic timeout/retries), worker/src/lib/cron-timeouts.ts (CRON_TIMEOUT_MS per-job lease budget), worker/src/cron/digest/platform.ts (model/thinking/effort, per-attempt timeout, corrective-retry skip), worker/src/cron/daily-digest.ts and worker/src/cron/weekly-recap.ts (max_tokens), worker/src/handlers/scheduled/context.ts (PER_JOB_LEASE_OPTIONS heartbeat override)

This doc deliberately does not restate Anthropic account-tier RPM / token-plan numbers because those are not repo-enforced.


Design Guidance

Before adding a worker feature that touches external services:

  1. Pick the trigger slot first. Shared slots are a capacity decision, not just a schedule decision.
  2. Add explicit throttle constants and an overall time budget before writing the fetch loop.
  3. Prefer chunked / batched writes and bounded SQL fan-out.
  4. Add or reuse a circuit breaker when the feature depends on a flaky upstream.
  5. Run npm run check:cron-connections and document the trigger-slot impact for any new outbound I/O.
  6. Update this doc only with limits the repo actually enforces or depends on architecturally.

If you need current provider-plan quotas, verify them outside the repo before relying on them.