From 50149e4b465a4dc78bf9431645c93ac2a0af94ad Mon Sep 17 00:00:00 2001 From: TokenBrice Date: Sat, 5 Sep 2026 17:36:21 +0200 Subject: [PATCH 01/13] fix(nav): shorten System Status label Use Status in shared navigation so the Resources menu label fits on one line beside health metadata. Update the existing navigation assertions. --- src/components/__tests__/top-nav.test.tsx | 6 +++--- src/lib/__tests__/nav-config.test.ts | 2 +- src/lib/nav-config.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/__tests__/top-nav.test.tsx b/src/components/__tests__/top-nav.test.tsx index bbf63b7bc..ae6492d19 100644 --- a/src/components/__tests__/top-nav.test.tsx +++ b/src/components/__tests__/top-nav.test.tsx @@ -181,13 +181,13 @@ describe("TopNav", () => { expect(document.activeElement).toBe(trigger); }); - it("keeps the System Status destination label and shows health as separate metadata", () => { + it("keeps the Status destination label and shows health as separate metadata", () => { installMatchMediaMock(true); render(); fireEvent.click(screen.getByRole("button", { name: "Resources" })); - const statusLabel = screen.getByText("System Status", { selector: "span" }); + const statusLabel = screen.getByText("Status", { selector: "span" }); const state = screen.getByText("Healthy", { selector: "span" }); const statusLink = statusLabel.closest("a"); expect(state.closest("a")).toBe(statusLink); @@ -218,7 +218,7 @@ describe("TopNav", () => { render(); fireEvent.click(screen.getByRole("button", { name: "Resources" })); - expect(screen.getByText("System Status", { selector: "span" })).not.toBeNull(); + expect(screen.getByText("Status", { selector: "span" })).not.toBeNull(); expect(screen.getByText("Unavailable", { selector: "span" })).not.toBeNull(); }); }); diff --git a/src/lib/__tests__/nav-config.test.ts b/src/lib/__tests__/nav-config.test.ts index 930fc635c..37147a861 100644 --- a/src/lib/__tests__/nav-config.test.ts +++ b/src/lib/__tests__/nav-config.test.ts @@ -79,7 +79,7 @@ describe("nav-config", () => { expect(more?.columns?.map((column) => column.items.map((item) => item.label))).toEqual([ ["Learn", "Mechanisms", "Case Studies", "Glossary", "Methodology"], ["Daily Digest", "Timeline", "Alert Bot"], - ["About", "Changelog", "Blog", "API Access", "System Status", "PharosVille"], + ["About", "Changelog", "Blog", "API Access", "Status", "PharosVille"], ]); // `items` must stay the exact flattening, or the mobile drawer and // /sitemap-tree/ silently drop rows the desktop panel still shows. diff --git a/src/lib/nav-config.ts b/src/lib/nav-config.ts index eea03157c..c63f4abed 100644 --- a/src/lib/nav-config.ts +++ b/src/lib/nav-config.ts @@ -173,7 +173,7 @@ const MORE_COLUMNS: readonly NavColumn[] = [ { href: "/changelog/", label: "Changelog", icon: PenLine, description: "Weekly release notes and updates" }, { href: "/blog/", label: "Blog", icon: BookOpen, description: "Product updates and the Pharos story" }, { href: "/api/", label: "API Access", icon: KeyRound, description: "Public API keys and endpoint reference" }, - { href: "/status/", label: "System Status", icon: MonitorCheck, description: "Live health of every data pipeline" }, + { href: "/status/", label: "Status", icon: MonitorCheck, description: "Live health of every data pipeline" }, { href: "https://pharosville.pharos.watch/", label: "PharosVille", From a33d6ba42ccb73aaddb9d43a22a71aceab835c8a Mon Sep 17 00:00:00 2001 From: TokenBrice Date: Sat, 5 Sep 2026 17:47:58 +0200 Subject: [PATCH 02/13] fix(dex): restore evidence refresh and measured-route discovery Refresh supported zero-pool censuses before their evidence expires and let fresh activated targets enter the bounded quote rotation. Requiring an existing scored route prevented new evidence and recovery; measured-route expiry priority and all request limits remain in place. Plan: agents/2026-09-05-safety-score-gap-campaign/README.md Tasks: D1 --- docs/dex-liquidity.md | 18 ++++--- .../__tests__/orchestrator.test.ts | 10 ++-- .../__tests__/sync-dex-discovery.test.ts | 19 +++++++- worker/src/cron/dex-discovery/orchestrator.ts | 9 ++-- .../__tests__/sync-admission.test.ts | 21 ++++---- .../src/cron/measured-execution/admission.ts | 48 ++----------------- worker/src/cron/measured-execution/sync.ts | 22 +++------ 7 files changed, 56 insertions(+), 91 deletions(-) diff --git a/docs/dex-liquidity.md b/docs/dex-liquidity.md index 7337d5bbf..caa3f560f 100644 --- a/docs/dex-liquidity.md +++ b/docs/dex-liquidity.md @@ -38,12 +38,10 @@ carries report-only observability with no formula effect: a fixed-key `fallbackC both the stage and consume runs counting every optimistic default and silent exclusion in the scoring path (unmeasured-balance optimism, durability neutral defaults, the TVL-depth mcap fallback, staged-pool defaults, retained-pool exclusions, Fluid and direct-API measurement-flag -defaults). Half-hourly measured execution remains live, but active admission is capped to target -IDs that match exactly one current published `exitRouteObservations` row with -`scoreEligible=true`, `evidenceKind=measured-executable-depth`, and matching adapter, chain, pool, -and output identity. Targets outside that score-bearing set are rejected before quote admission. -Those intentional exclusions remain visible in failure diagnostics but do not degrade the active -lane; inability to load the published score-bearing set fails closed and degrades the run. +defaults). Half-hourly measured execution admits the fresh active target inventory through its +bounded whole-coin rotation, including new and interrupted routes that need their first proof. +An exact current published measured route remains eligible for the expiry-priority reservation; +inability to load that published route set fails closed and degrades the run. The former daily shadow admission/quote evidence ledger and 240-character `mxLedger*` scalar encoding were removed; shadow target and quote generation persistence remains for compatibility. @@ -348,7 +346,7 @@ Safety Score V9 consumes those evidence fields, plus aggregate `dex_deployment_o Pool, token, and deployment identities use chain-specific casing: EVM addresses remain case-insensitive, while non-EVM native identifiers preserve case. During the rollout from legacy lowercase non-EVM rows, a newer corrected staging or deployment-outcome row supersedes an older lowercase-equivalent row only when the stablecoin, chain, source identity, and native pool/token identity otherwise match; same-time or otherwise ambiguous case-distinct rows remain separate and fail closed rather than being guessed together. -Every active DEX publication row carries explicit route coverage, including zero-scoring-pool placeholders. A placeholder is published as known empty (`populated`, zero retained pools, zero observations) only when its exact current `contracts` plus `tradedContracts` deployment census is unique, no older than that coin's census freshness bound, provider-backed, entirely `verified_no_pools`, and no deployment result predates that deployment's latest attributed discovery attempt. That bound is sweep-aware rather than global: a footprint the discovery crawl finishes in one run keeps the two-dormant-window (two-day) limit exactly, while a footprint whose priced provider queries exceed the 25s per-coin budget is crawled in resumable windows and is therefore allowed its estimated full-sweep period plus half a sweep of slack. `resolveDexDeploymentCensusMaxAgeSec()` (`worker/src/cron/dex-liquidity/deployment-census-coverage.ts`) derives that per-coin value statically - no extra freshness persistence - by replaying the real window selector over the registry footprint to count the windows one sweep needs and pricing each window at the t3 cohort cadence (ten two-hour discovery runs, so 20 hours per window); the six largest footprints currently sweep in 60-120 hours and are allowed 90-180 hours. Without this, the rotating tail of a windowed coin would report a stale or missing outcome forever even though discovery is on schedule. Discovery persists the selected deployments' attempt boundaries before network work without changing backoff counters, so an abort, budget discard, or result-persistence failure supersedes older empty evidence only inside that window. Failed bounded provider crawls also attempt to write an inaccessible outcome for each attempted deployment; an incomplete D1 persistence path retains only those attempt fences and therefore remains a discovery deferral rather than a provider outage. Timeout and 429 misses are retryable: they persist as a bounded-crawl reason and the scoring census treats them as a discovery deferral, not as “all provider queries failed.” A later GeckoTerminal page miss keeps any completed page-1 pools instead of discarding the token. The publication join rejects an older success even if either follow-up write fails. Missing, stale, superseded, malformed, inaccessible, unsupported, or observed-pool outcomes remain `unknown` with generation-bound census counts and reason codes. In particular, an observed pool that is lost before scoring is never converted into known-empty evidence. A persisted empty provider set is a snapshot of a registry fact, not an observation, so the live registry outranks it: an inaccessible row that claims no registered provider supports this chain while `getDexDiscoveryProviders()` resolves one today is counted as a superseded outcome awaiting the next crawl window, never as `deploymentCensusUnsupportedMethod`. Without that rule, every newly registered discovery provider (Aquarius Soroban, the supplemental GeckoTerminal networks) published a solved integration gap as a standing method limit for a full sweep period. `npm run check:dex-census-provider-drift -- --rows=` lists the contradicted rows still waiting for that re-crawl. +Every active DEX publication row carries explicit route coverage, including zero-scoring-pool placeholders. A placeholder is published as known empty (`populated`, zero retained pools, zero observations) only when its exact current `contracts` plus `tradedContracts` deployment census is unique, no older than that coin's census freshness bound, provider-backed, entirely `verified_no_pools`, and no deployment result predates that deployment's latest attributed discovery attempt. That bound is sweep-aware rather than global: a footprint the discovery crawl finishes in one run keeps the two-dormant-window (two-day) limit exactly, while a footprint whose priced provider queries exceed the 25s per-coin budget is crawled in resumable windows and is therefore allowed its estimated full-sweep period plus half a sweep of slack. `resolveDexDeploymentCensusMaxAgeSec()` (`worker/src/cron/dex-liquidity/deployment-census-coverage.ts`) derives that per-coin value statically - no extra freshness persistence - by replaying the real window selector over the registry footprint to count the windows one sweep needs and pricing each window at the weekly t3 cohort cadence. The zero-pool maintenance queue refreshes these censuses faster, targeting an 18-hour full sweep without extending the existing freshness limits. Without this, the rotating tail of a windowed coin would report a stale or missing outcome forever even though discovery is on schedule. Discovery persists the selected deployments' attempt boundaries before network work without changing backoff counters, so an abort, budget discard, or result-persistence failure supersedes older empty evidence only inside that window. Failed bounded provider crawls also attempt to write an inaccessible outcome for each attempted deployment; an incomplete D1 persistence path retains only those attempt fences and therefore remains a discovery deferral rather than a provider outage. Timeout and 429 misses are retryable: they persist as a bounded-crawl reason and the scoring census treats them as a discovery deferral, not as “all provider queries failed.” A later GeckoTerminal page miss keeps any completed page-1 pools instead of discarding the token. The publication join rejects an older success even if either follow-up write fails. Missing, stale, superseded, malformed, inaccessible, unsupported, or observed-pool outcomes remain `unknown` with generation-bound census counts and reason codes. In particular, an observed pool that is lost before scoring is never converted into known-empty evidence. A persisted empty provider set is a snapshot of a registry fact, not an observation, so the live registry outranks it: an inaccessible row that claims no registered provider supports this chain while `getDexDiscoveryProviders()` resolves one today is counted as a superseded outcome awaiting the next crawl window, never as `deploymentCensusUnsupportedMethod`. Without that rule, every newly registered discovery provider (Aquarius Soroban, the supplemental GeckoTerminal networks) published a solved integration gap as a standing method limit for a full sweep period. `npm run check:dex-census-provider-drift -- --rows=` lists the contradicted rows still waiting for that re-crawl. The Stellar Soroban census is intentionally bounded to the eight reviewed Spiko token identities served by the public [Aquarius ticker index](https://amm-api.aqua.network/api/tickers/). Aquarius is registered as a supplemental provider, not a chain-exhaustive census: the 2026-09-04 check returned HTTP 200 JSON but no ticker matched any of the eight exact token identities, so a valid empty response is persisted as `provider_inaccessible` with `Provider census is not exhaustive for this chain` and cannot certify a known-empty footprint. The public [Soroswap pools endpoint](https://api.soroswap.finance/pools) returned HTTP 403 without credentials when checked on 2026-09-04, so no chain-wide alternate is registered. Aquarius transport failures remain retryable and continue through the bounded-pending path; they must never be converted into a fabricated empty census. @@ -368,7 +366,7 @@ The SunSwap V2 adapter (shadow-only since v5.96) was removed in v6.0 along with Fully executable exact reserve-simulation capacity points also retain the realized execution cost from re-running the same invariant at the requested input and valuing its output with the captured token references. The projection is accepted only within the point's cost bound plus a narrow numerical tolerance. Zero capacity, an invalid recomputation, or partial capacity defined by bisection at the 200 bps request ceiling omits it and leaves V9 on the conservative fallback rather than mislabeling the bound as a realized cost. Curve reserve models apply the invariant to full input and deduct the fee from output, matching Curve StableSwap accounting. Ordinary source-only Curve models retain the documented 10 bps source-API fallback where the pools endpoint lacks pool-specific fee state; this is not a claimed per-pool upper bound, and a separately pinned `get_dy` measurement is required for proof-bearing execution cost. This additive V9 field does not affect aggregate liquidity or the standalone Liquidity Score. -Before ordinary whole-coin EVM cursor rotation, the measured lane may reserve one currently published score-bearing direction packet whose adapter-specific expiry is earliest. The reservation is capped at 20 estimated RPC requests, keeps the legacy Curve 3pool directions atomic, shares the 1,220-request admission ceiling, and does not advance the ordinary cursor. All remaining targets retain the existing whole-coin ranking and fairness behavior. +Before ordinary whole-coin EVM cursor rotation, the measured lane may reserve one currently published score-bearing direction packet whose adapter-specific expiry is earliest. The reservation is capped at 20 estimated RPC requests, keeps the legacy Curve 3pool directions atomic, shares the 1,220-request admission ceiling, and does not advance the ordinary cursor. Every fresh target in the active inventory enters the remaining bounded whole-coin rotation, including targets with no prior published observation. Requiring an already measured route created a bootstrap deadlock: new or interrupted routes could never obtain their first proof. The inventory still admits only activated methods and deployments, and all quote, identity, freshness, and publication gates remain mandatory. The 2026-09-05 production inventory of 581 targets estimates 1,134 RPC requests for one rotation, below the unchanged 1,220 admission and 1,300 hard limits; larger inventories rotate behind the same deadline and cursor. The isolated score-bearing `sync-cl-exit-depth` lane retains logical `:00/:30` slots but physically runs at `5,35 * * * *`, before the hourly DEX source stage and consumer. It loads only the latest active target generation, pins one block per chain, verifies reviewed QuoterV2 and factory bytecode, proves each pool through the factory's exact `getPool` binding, and records a $1,000 marginal quote plus the TVL-tiered $100,000/$1 million/$10 million/$25 million ladder with bounded refinement. PancakeSwap, Uniswap V3, and Aerodrome Slipstream target construction admit each direction only when the retained spot does not imply output worth more than `1.02x` the input under independent token references; incoherent directions remain in retained DEX evidence but do not become measured-execution failures. Uniswap V3 target construction resolves each leg's USD reference independently and, when the counter asset is untracked and has no direct reference, pool-implies the output reference from the subgraph candidate's decimal-adjusted spot price times the input leg's direct reference (the same convention the Uni V3 price indexer consumes, mirroring the Raydium pool-implied derivation). Pool-implied references must remain representable by the measured-execution pipeline's 1e8 fixed-point price encoding; smaller values revert to `target-unresolved` and emit a structured diagnostic with the raw pair spot prices instead of creating a guaranteed quote-validation failure. Runtime favorable-output quote mismatches for untracked pool-implied counter assets still persist failed target rows and remain excluded from scoring, but they are diagnostic for cron health because the source spot and pinned quote block can drift after target publication. Identity failures still gate to target-unresolved. Tracked NAV tokens require a trusted live NAV at capture time and never fall back to a static fiat peg for quote sizing. Producers publish target and quote generations atomically in D1. The `:16` hourly consumer joins only a fresh published quote generation from the exact `:10` source-stage graph, publishes DEX prices and challenger snapshots each hour, and publishes the full Liquidity Score plus the next active target generation every two hours. The `:46` invocation retains the hourly CPU-class trigger topology and V9 sequencing but performs no DEX source or scoring work. Score-ineligible EVM targets use separate shadow generations and one serial daily 08:10 UTC collection run; shadow failures cannot degrade score publication. Synthetic `budget-deferred` outcomes are omitted from D1 quote rows only after an exact target-count and target-ID-digest manifest is recorded, then reconstructed on read; any manifest mismatch fails closed. A mature fresh last-known-good profile may remain in the bounded route-only observation set when its physical pool rotates out of the current liquidity/display shortlist, but it never re-enters aggregate TVL, volume, visible pools, price consensus, target publication, or the standalone Liquidity Score. QuoterV2 profiles must pass consumer validation of generation, identity, decimals, price, freshness, provenance, curve monotonicity, cost bracketing, and the `1.5x` retained-TVL capacity ceiling. Score-eligible deployments are the owner-ratified Uniswap V3 cohorts on Ethereum, Polygon, Arbitrum, and Celo; PancakeSwap V3 on Base, BSC, and Ethereum; plus the reviewed Aerodrome Slipstream cohort on Base. For Aerodrome Slipstream alone, the P4 identity check accepts the retained source labels `aerodrome` and `aerodrome-slipstream`; the measured profile must still identify `aerodrome-slipstream`, and exact chain, physical-pool, token, generation, and proof validation remain mandatory. The Solana and Tron native measured-execution lanes and the Fluid measured overlay were removed in v6.0; retained Raydium, Orca, SunSwap, and Fluid pools resolve as shaped evidence outside the strict exact-route denominator. @@ -570,7 +568,7 @@ Discovery and merge staging tables are documented in the [Discovery Cron](#disco - Both providers are registered by chain **and** denom shape (`ibc/<64 uppercase hex>`, a plain lowercase bank denom, or a `factory/...` denom). MANTRA's Cosmos IBC denom shares that shape but not the chain and is never routed to either index, matching the DEX-scoped GeckoTerminal resolver's MANTRA EVM-only rule. - **Deployment outcome schema**: `dex_deployment_outcomes` stores one exact stablecoin/chain/contract row as `observed_pools`, `verified_no_pools`, or `provider_inaccessible`, including the provider set, reason, observation time, per-deployment `last_attempt_at`, pool count, and optional owned waiver. The provider set is derived per deployment by `getDexDiscoveryProviders()`, and the discovery crawl queries exactly the providers that set names. In addition to the general chain registry, the DEX-scoped GeckoTerminal resolver covers Starknet (`starknet-alpha`), Stacks, Hedera, and Injective, plus only 20-byte `0x` deployments on MANTRA EVM; Cosmos IBC denoms sharing the `mantra` repo chain id remain unsupported rather than being sent to the wrong network. Starknet token queries use GeckoTerminal's 64-hex-digit felt form, Hedera `0.0.N` entity ids use their 20-byte long-zero Solidity form, and Injective EVM, Peggy, IBC, and token-factory denoms use GeckoTerminal's provider-native identities. Provider-native token ids are URL-encoded once in the token-pools path while persisted census rows retain the registry address. The Curve discovery stage is scoped to `CURVE_NATIVE_DISCOVERY_CHAINS`, the same registered chains that credit Curve as a provider, so a Curve result can always be attributed to a named provider; its nested getPools requests run at most two chains at a time with a 4 MiB response ceiling. The liquidity stage reads Curve on a wider chain set for scoring; those extra chains are deliberately not crawled for deployment outcomes. A no-pool result is written only after a provider completes that exact token query and is usable only while that deployment's attempt fence has not superseded it. Before network work, the writer reconciles any unmatched legacy coin fence, advances `last_attempt_at` only for the selected rotating window, and atomically marks the coin fence as attributed. Missing, mismatched, or legacy attribution stays fail-closed; failed result persistence supersedes only attempted deployments, while untouched rows keep their prior evidence. Failed provider crawls materialize inaccessible outcomes for the attempted footprint when D1 is available. The canonical registry owns current inaccessible deployments; full-footprint gaps require explicit, expiring waivers while adapters or provider mappings are evaluated. - **Tiered priority**: - - Refresh: coins with admitted supplemental liquidity in the published source mix receive maintenance opportunities before the 24-hour staging expiry. An 18-hour full-sweep target (six hours before expiry) is divided across the existing estimated deployment-window count, rounded down to the existing two-hour tick (minimum one tick). This queue runs before new discovery and resumes the existing cursor; native-only and verified-empty footprints keep their weekly discovery cadence. + - Refresh: coins with admitted supplemental liquidity in the published source mix, and zero-pool footprints with a supported census provider, receive maintenance opportunities before their evidence expires. This includes verified-empty and incomplete censuses: weekly discovery cannot keep their two-day evidence current. An 18-hour full-sweep target (six hours before expiry) is divided across the existing estimated deployment-window count, rounded down to the existing two-hour tick (minimum one tick). This queue runs before new discovery and resumes the existing cursor; native-only footprints with retained pools keep their weekly discovery cadence. - T1: coins with 0 pools (or effectively eligible baseline), every run. - T2: 1–4 pools or 1 chain, every 84th run (one week at the two-hour cron cadence). - T3: `>=5` pools on `>=2` chains, every 84th run (one week), sharded by stablecoin id. @@ -581,7 +579,7 @@ Discovery and merge staging tables are documented in the [Discovery Cron](#disco - 6–9: floor T3 - 10+: dormant (daily gate) - Any discovery hit resets `consecutiveMisses` to 0, removing the backoff floor; the coin's tier is then recomputed from its pool/chain counts on the next run. - - **Verified-empty census cadence hold**: a crawl that finds zero pools always increments `consecutiveMisses`, so a footprint whose correct answer is "no DEX pools anywhere" used to accrue misses forever and decay to dormant — whose 24-hour-plus per-window cadence is slower than the sweep-aware census freshness bound below, turning a correct zero-pool answer into a permanently stale census. A coin whose current census answers every provider-supported tracked deployment as `verified_no_pools` (no `observed_pools`, no provider-supported `provider_inaccessible`, no missing row) therefore stops at the T3 floor instead of falling to dormant. T3 is exactly the cadence the census bound is priced at, so the coin's own correct answer can no longer age itself out of the reviewed scope. Chains with no registered discovery provider are excluded from the test because the census already carries them as a standing unsupported remainder rather than an unanswered deployment. `readDiscoveryCensusSummaries()` aggregates the census in one grouped read per run, `hasVerifiedEmptyCensus()` applies the test, and cron metadata reports `censusCadenceHolds` — the number of coins the hold kept above dormant this run. + - **Verified-empty census cadence hold**: a crawl that finds zero pools always increments `consecutiveMisses`, so a footprint whose correct answer is "no DEX pools anywhere" used to accrue misses forever and decay to dormant — whose 24-hour-plus per-window cadence is slower than the sweep-aware census freshness bound below, turning a correct zero-pool answer into a permanently stale census. A coin whose current census answers every provider-supported tracked deployment as `verified_no_pools` (no `observed_pools`, no provider-supported `provider_inaccessible`, no missing row) therefore stops at the T3 floor instead of falling to dormant. That hold remains the base-tier diagnostic; the zero-pool maintenance queue now schedules refreshes before the two-day single-window freshness bound instead of relying on the weekly T3 cadence. Chains with no registered discovery provider are excluded from the test because the census already carries them as a standing unsupported remainder rather than an unanswered deployment. `readDiscoveryCensusSummaries()` aggregates the census in one grouped read per run, `hasVerifiedEmptyCensus()` applies the test, and cron metadata reports `censusCadenceHolds` — the number of coins the hold kept above dormant this run. - **Chain-aware source routing**: discovery only queries chains with defined entries in a stablecoin’s `contracts` plus optional `tradedContracts` metadata; this avoids unnecessary API calls against un-deployed chains while preserving wrapper/secondary-market discovery addresses. - **Resumable deployment windows**: each coin crawl is bounded by a 25s per-coin budget shared by all provider stages, and the stages run to completion in order, so a footprint whose paced provider queries exceed that budget would let the first stage consume it and permanently starve every chain only a later stage can serve. `selectDiscoveryTargetWindow()` (`worker/src/cron/dex-discovery/target-window.ts`) prices each deployment at every registered serial provider's pacing floor plus request allowance, and when the footprint does not fit it hands the crawl one window at a time, resuming after the last deployment a provider actually reached on the previous run. The resume markers live in one `kv_config` row (`discovery_target_cursors`); an unknown or missing marker restarts the rotation at the first deployment. Footprints that fit the budget are crawled whole, exactly as before. Deployments outside the current window are not classified, so they keep their previous census row instead of being downgraded to a bounded-crawl deferral, and cron metadata reports `windowedCoins` plus `windowedDeploymentsDeferred`. - **Maintenance capacity**: refresh uses the existing serial provider sequence, 25-second per-coin window, 12-minute run budget, and finalization reserve; no trigger or concurrent connection is added. The 18-hour sweep is a scheduling target, not a promise under provider failure, budget exhaustion, or footprints too large for the existing ticks. Deferred or failed observations still expire after 24 hours; they are never relabeled fresh. Monitor the `refresh` tier count, `budgetExhausted`, and deployment-window deferrals after rollout. diff --git a/worker/src/cron/dex-discovery/__tests__/orchestrator.test.ts b/worker/src/cron/dex-discovery/__tests__/orchestrator.test.ts index 9226e1a1d..82540e2ca 100644 --- a/worker/src/cron/dex-discovery/__tests__/orchestrator.test.ts +++ b/worker/src/cron/dex-discovery/__tests__/orchestrator.test.ts @@ -10,7 +10,7 @@ import { computeEffectiveTier, hasVerifiedEmptyCensus, isEligibleThisRun, - isSupplementalRefreshDue, + isDiscoveryEvidenceRefreshDue, } from "../orchestrator"; const nowSec = 1710000000; @@ -20,14 +20,14 @@ describe("computeEffectiveTier", () => { const target = { chain: "ethereum", address: "0x1111111111111111111111111111111111111111", decimals: 18 }; const meta: DiscoveryMeta = { stablecoinId: "coin-a", consecutiveMisses: 0, lastCrawlAt: nowSec - 18 * 3600, lastHitAt: nowSec - 18 * 3600 }; - expect(isSupplementalRefreshDue([target], meta, nowSec - 1)).toBe(false); - expect(isSupplementalRefreshDue([target], meta, nowSec)).toBe(true); + expect(isDiscoveryEvidenceRefreshDue([target], meta, nowSec - 1)).toBe(false); + expect(isDiscoveryEvidenceRefreshDue([target], meta, nowSec)).toBe(true); expect(computeEffectiveTier("coin-a", 20, 4, meta, 1, nowSec, false, true)).toBe("refresh"); expect(computeEffectiveTier("coin-a", 20, 4, meta, 1, nowSec)).toBe("skip"); const footprint = Array.from({ length: 30 }, (_, index) => ({ ...target, address: `0x${(index + 1).toString(16).padStart(40, "0")}` })); - expect(isSupplementalRefreshDue(footprint, { ...meta, lastCrawlAt: nowSec - 2 * 3600 }, nowSec)).toBe(true); - expect(isSupplementalRefreshDue(footprint, { ...meta, lastCrawlAt: nowSec - 3600 }, nowSec)).toBe(false); + expect(isDiscoveryEvidenceRefreshDue(footprint, { ...meta, lastCrawlAt: nowSec - 2 * 3600 }, nowSec)).toBe(true); + expect(isDiscoveryEvidenceRefreshDue(footprint, { ...meta, lastCrawlAt: nowSec - 3600 }, nowSec)).toBe(false); }); it("applies base tiers and weekly cadence gating", () => { diff --git a/worker/src/cron/dex-discovery/__tests__/sync-dex-discovery.test.ts b/worker/src/cron/dex-discovery/__tests__/sync-dex-discovery.test.ts index 9291315a1..4bf8443be 100644 --- a/worker/src/cron/dex-discovery/__tests__/sync-dex-discovery.test.ts +++ b/worker/src/cron/dex-discovery/__tests__/sync-dex-discovery.test.ts @@ -160,6 +160,19 @@ describe("syncDexDiscovery", () => { expect(vi.mocked(crawlCoin).mock.calls[0]?.[1]).toBe("coin-b"); }); + it("refreshes a zero-pool census before two-day expiry despite weekly miss backoff", async () => { + const nowSec = Math.floor(Date.now() / 1000); + vi.mocked(readDiscoveryMeta).mockResolvedValue(new Map([ + ["coin-a", { stablecoinId: "coin-a", consecutiveMisses: 6, + lastCrawlAt: nowSec - 36 * 3600, lastHitAt: null }], + ])); + + const result = await syncDexDiscovery(db, null); + + expect(vi.mocked(crawlCoin).mock.calls.map((call) => call[1])).toEqual(["coin-a"]); + expect(JSON.parse(result.metadata ?? "{}").tierBreakdown).toMatchObject({ refresh: 1, t3: 0 }); + }); + beforeEach(() => { vi.clearAllMocks(); vi.mocked(loadPriceValidationReferences).mockResolvedValue(mockValidationReferences); @@ -208,7 +221,8 @@ describe("syncDexDiscovery", () => { runSeq: 2, failedCoins: [], tierBreakdown: { - t1: 1, + refresh: 1, + t1: 0, t2: 0, t3: 0, dormant: 0, @@ -257,7 +271,8 @@ describe("syncDexDiscovery", () => { finalizationTailBudgetMs: DEX_DISCOVERY_FINALIZATION_TAIL_BUDGET_MS, runSeq: 1, tierBreakdown: { - t1: 1, + refresh: 1, + t1: 0, t2: 0, t3: 0, dormant: 0, diff --git a/worker/src/cron/dex-discovery/orchestrator.ts b/worker/src/cron/dex-discovery/orchestrator.ts index f61de1a33..06db9be6f 100644 --- a/worker/src/cron/dex-discovery/orchestrator.ts +++ b/worker/src/cron/dex-discovery/orchestrator.ts @@ -252,11 +252,11 @@ function discoveryTierPriority(tier: Exclude): number { } } -/** Refresh admitted supplemental evidence inside its 24h lifetime, with an 18h sweep target. +/** Refresh discovery evidence inside its lifetime, with an 18h sweep target. * Existing windows and the run deadline remain hard bounds; oversized footprints * get every existing tick rather than extending the evidence freshness window. */ -export function isSupplementalRefreshDue( +export function isDiscoveryEvidenceRefreshDue( targets: readonly ContractDeployment[], meta: DiscoveryMeta | undefined, nowSec: number, @@ -387,7 +387,10 @@ export async function syncDexDiscovery( runSeq, nowSec, censusVerifiedEmpty, - coverage?.hasSupplementalCoverage === true && isSupplementalRefreshDue(targets, metaById.get(coin.id), nowSec), + (coverage?.hasSupplementalCoverage === true || + (coverage?.poolCount === 0 && targets.some((target) => + getRuntimeDexDiscoveryProviders(target.chain, target.address).length > 0))) && + isDiscoveryEvidenceRefreshDue(targets, metaById.get(coin.id), nowSec), ); if (tier === "skip") { diff --git a/worker/src/cron/measured-execution/__tests__/sync-admission.test.ts b/worker/src/cron/measured-execution/__tests__/sync-admission.test.ts index 74e4c8642..40114a2bc 100644 --- a/worker/src/cron/measured-execution/__tests__/sync-admission.test.ts +++ b/worker/src/cron/measured-execution/__tests__/sync-admission.test.ts @@ -8,7 +8,6 @@ import type { DexExitRouteObservation } from "@shared/types/market"; import { MEASURED_EXECUTION_ADMISSION_RUN_METADATA, admitTargetsWithinBudget, - collectScoreBearingTargetIds, estimateAdmissionCohortRpcRequestBreakdown, estimateAdmissionCohortRpcRequests, estimateAdmissionRotationCycles, @@ -367,23 +366,21 @@ describe("measured execution overflow admission", () => { ), ).toBeNull(); }); - it("rejects targets outside the current score-bearing route set", () => { + it("admits fresh targets without a published route while protecting measured-route priority", () => { const scored = target("coin-scored", 100_000, "scored"); - const outside = target("coin-outside", 100_000, "outside"); - const scoreBearingTargetIds = collectScoreBearingTargetIds( - [scored, outside], - [publishedRoute(scored, 1_000)], - ); - const admission = admitTargetsWithinBudget([scored, outside], { + const fresh = target("coin-new", 100_000, "fresh"); + const priority = selectExpiringScoreBearingPriorityPacket([scored, fresh], [publishedRoute(scored, 1_000)]); + const admission = admitTargetsWithinBudget([scored, fresh], { maxEstimatedRpcRequests: 20, - scoreBearingTargetIds, + priorityTargetIds: new Set(priority?.targetIds), }); - expect(admission.admitted).toEqual(new Set([scored.targetId])); - expect(admission.excluded).toEqual(new Set([outside.targetId])); + expect(admission.priorityAdmitted).toEqual(new Set([scored.targetId])); + expect(admission.admitted).toEqual(new Set([scored.targetId, fresh.targetId])); + expect(admission.estimatedRpcRequests).toBeLessThanOrEqual(20); + expect(estimateAdmissionRotationCycles([fresh])).toBe(1); }); - it("admits one bounded priority without letting it advance the tail cursor", () => { const priority = target("coin-priority", 100_000); const tail = target("coin-tail", 100_000, "coin-tail", { diff --git a/worker/src/cron/measured-execution/admission.ts b/worker/src/cron/measured-execution/admission.ts index bfb33dc20..c00f4f817 100644 --- a/worker/src/cron/measured-execution/admission.ts +++ b/worker/src/cron/measured-execution/admission.ts @@ -525,22 +525,6 @@ function isScoreBearingRoute(row: PublishedScoreBearingDexRoute): boolean { ); } -export function collectScoreBearingTargetIds( - targets: readonly DexMeasuredExecutionTarget[], - publishedRoutes: readonly PublishedScoreBearingDexRoute[], -): Set { - const scoreBearingRoutes = publishedRoutes.filter(isScoreBearingRoute); - const targetIds = new Set(); - for (const target of targets) { - const matches = scoreBearingRoutes.filter((row) => - publishedRouteMatchesTarget(row, target), - ); - if (matches.length === 1) targetIds.add(target.targetId); - } - return targetIds; -} - - export function admitTargetsWithinBudget( targets: readonly DexMeasuredExecutionTarget[], options: { @@ -549,16 +533,10 @@ export function admitTargetsWithinBudget( refinementRounds?: number; priorityTargetIds?: ReadonlySet; priorityMaxEstimatedRpcRequests?: number; - /** - * Active measured execution is admitted only for targets whose exact route - * identity has one current score-bearing published observation. - */ - scoreBearingTargetIds?: ReadonlySet; } = {}, ): { admitted: Set; deferred: Set; - excluded: Set; oversized: Set; priorityAdmitted: Set; oversizedCoinIds: string[]; @@ -567,19 +545,9 @@ export function admitTargetsWithinBudget( estimatedQuoteRpcRequests: number; nextCursor: string | null; } { - const targetsForAdmission = options.scoreBearingTargetIds - ? targets.filter((target) => options.scoreBearingTargetIds!.has(target.targetId)) - : targets; - const excluded = new Set( - options.scoreBearingTargetIds - ? targets - .filter((target) => !options.scoreBearingTargetIds!.has(target.targetId)) - .map((target) => target.targetId) - : [], - ); const deferred = new Set(); const byCoin = new Map(); - for (const target of targetsForAdmission) { + for (const target of targets) { const rows = byCoin.get(target.stablecoinId) ?? []; rows.push(target); byCoin.set(target.stablecoinId, rows); @@ -601,7 +569,7 @@ export function admitTargetsWithinBudget( let estimatedSetupRpcRequests = 0; let estimatedQuoteRpcRequests = 0; const admittedTargets: DexMeasuredExecutionTarget[] = []; - const priorityTargets = targetsForAdmission.filter((target) => + const priorityTargets = targets.filter((target) => options.priorityTargetIds?.has(target.targetId), ); if (priorityTargets.length > 0) { @@ -668,7 +636,6 @@ export function admitTargetsWithinBudget( return { admitted, deferred, - excluded, oversized, priorityAdmitted, oversizedCoinIds, @@ -687,24 +654,19 @@ export function estimateAdmissionRotationCycles( refinementRounds?: number; priorityTargetIds?: ReadonlySet; priorityMaxEstimatedRpcRequests?: number; - scoreBearingTargetIds?: ReadonlySet; } = {}, ): number | null { if (targets.length === 0) return 0; - const targetsForRotation = options.scoreBearingTargetIds - ? targets.filter((target) => options.scoreBearingTargetIds!.has(target.targetId)) - : targets; - const uncovered = new Set(targetsForRotation.map((target) => target.targetId)); - if (targetsForRotation.length === 0) return 0; + const uncovered = new Set(targets.map((target) => target.targetId)); const seenCursors = new Set(); let cursor = options.cursor ?? null; - const maximumCycles = new Set(targetsForRotation.map((target) => target.stablecoinId)).size + 1; + const maximumCycles = new Set(targets.map((target) => target.stablecoinId)).size + 1; for (let cycle = 1; cycle <= maximumCycles; cycle++) { const cursorKey = cursor ?? ""; if (seenCursors.has(cursorKey)) return null; seenCursors.add(cursorKey); - const admission = admitTargetsWithinBudget(targetsForRotation, { + const admission = admitTargetsWithinBudget(targets, { ...options, cursor, }); diff --git a/worker/src/cron/measured-execution/sync.ts b/worker/src/cron/measured-execution/sync.ts index 3c5f5ee38..fe38b695d 100644 --- a/worker/src/cron/measured-execution/sync.ts +++ b/worker/src/cron/measured-execution/sync.ts @@ -78,7 +78,7 @@ import { MAX_ADMISSION_ROTATION_CYCLES, MAX_EXPIRING_PRIORITY_RPC_REQUESTS, MEASURED_EXECUTION_ADMISSION_RUN_METADATA, MEASURED_EXECUTION_ADMISSION_SOURCE_KEY, MEASURED_EXECUTION_REFINEMENT_ROUNDS, MEASURED_EXECUTION_RPC_REQUEST_LIMIT, SHADOW_MEASURED_EXECUTION_ADMISSION_SOURCE_KEY, - admitTargetsWithinBudget, collectScoreBearingTargetIds, estimateAdmissionRotationCycles, + admitTargetsWithinBudget, estimateAdmissionRotationCycles, hasCompleteDexMeasuredQuoteProgress, selectExpiringScoreBearingPriorityPacket, loadPublishedScoreBearingDexRoutes, resolveMeasuredExecutionCronStatus, resolveTargetDeployment, summarizeMeasuredExecutionQuoteFailures, @@ -337,11 +337,6 @@ async function syncDexMeasuredExecutionLane( productivity: { productive: false, reason: "score-bearing-route-load-failed" }, }); } - const scoreBearingTargetIds = lane === "active" - ? scoreBearingRoutes - ? collectScoreBearingTargetIds(targetGeneration.targets, scoreBearingRoutes) - : new Set() - : undefined; const quoteGenerationId = lane === "shadow" ? buildDexShadowMeasuredQuoteGenerationId(startedAt) : buildDexMeasuredQuoteGenerationId(startedAt); @@ -358,7 +353,6 @@ async function syncDexMeasuredExecutionLane( const { admitted, deferred, - excluded, oversized, priorityAdmitted, oversizedCoinIds, @@ -370,13 +364,11 @@ async function syncDexMeasuredExecutionLane( cursor: admissionCursor, priorityTargetIds, priorityMaxEstimatedRpcRequests: MAX_EXPIRING_PRIORITY_RPC_REQUESTS, - scoreBearingTargetIds, }); const admissionRotationCycles = estimateAdmissionRotationCycles(targetGeneration.targets, { cursor: admissionCursor, priorityTargetIds, priorityMaxEstimatedRpcRequests: MAX_EXPIRING_PRIORITY_RPC_REQUESTS, - scoreBearingTargetIds, }); const budgetDeferredCount = deferred.size - oversized.size; const orderedTargets = targetGeneration.targets @@ -408,13 +400,11 @@ async function syncDexMeasuredExecutionLane( curveCompositeProof: null, uniswapV4PoolProof: null, points: [], - failedReason: excluded.has(target.targetId) - ? "score-bearing-route-unavailable" - : oversized.has(target.targetId) - ? "admission-coin-group-oversized" - : deferred.has(target.targetId) - ? "budget-deferred" - : null, + failedReason: oversized.has(target.targetId) + ? "admission-coin-group-oversized" + : deferred.has(target.targetId) + ? "budget-deferred" + : null, stopped: false, bracket: null, })); From 35e690d44d8d7973527445800c7895e366ed53ae Mon Sep 17 00:00:00 2001 From: TokenBrice Date: Sat, 5 Sep 2026 17:54:39 +0200 Subject: [PATCH 03/13] fix(safety-score): consume reviewed transfer and reserve evidence Verify native-token transfer semantics for USDA and USDM and active AZND restriction paths; use KAG's reviewed external-representation exception without erasing bridge risks. Refresh BRLV from its August reserve memo, align Felix's kHYPE producer label, and suspend USDY's scalar NAV feed in favor of its separately dated portfolio review. Fixed-input replay closes five open facts with no grade changes. BRLV's reserve gap is replaced by an unresolved mechanism fact, so it claims no net closure. USDY and Felix require fresh producer readback. Plan: agents/2026-09-05-safety-score-gap-campaign/README.md Tasks: C1, R1, V1 --- docs/live-reserves.md | 2 + .../transfer-review-overlays-v1.json | 101 ++++++++++++++++++ shared/data/stablecoins/coins/brlv-crown.json | 21 +--- .../data/stablecoins/coins/feusd-felix.json | 2 +- .../stablecoins/coins/usdy-ondo-finance.json | 5 + .../domains/mint-authority/kag-kinesis.json | 16 +++ .../domains/reserves/brlv-crown.json | 24 ++--- 7 files changed, 140 insertions(+), 31 deletions(-) diff --git a/docs/live-reserves.md b/docs/live-reserves.md index fb909ebb2..47b24e8f9 100644 --- a/docs/live-reserves.md +++ b/docs/live-reserves.md @@ -545,6 +545,8 @@ Yuzu's Fasanara `mGLO` loop remains a high-risk named reserve slice but carries Chainlink NAV note: `chainlink-nav` now supports both standard AggregatorV3 feeds and Ondo router-style NAV lookups. When `oracleMethod = "getAssetPrice"`, the adapter calls `getAssetPrice(token)` on the router and, when available, follows `tokenToRWAOracle(token) -> getPriceData()` to recover a verified freshness timestamp instead of treating the feed as permanently timestampless. +USDY's scalar NAV configuration is suspended: its price and token-supply reads do not measure the multi-asset holdings published by Ondo. Scoring uses the separately dated reviewed holdings composition with the existing static-evidence admission and freshness limits; resumption requires a producer that consumes the holdings themselves. Felix's kHYPE branch uses the reviewed Kinetiq label so the next live observation joins its existing collateral classification. + Chronicle NAV note: `chronicle-nav` reads Chronicle Labs Proof-of-Asset VAO consumer contracts via `readWithAge()` (value + age), validating the value, rejecting stale or future-dated ages, and reading the coin token's on-chain supply with its actual decimals. The v1 path is scalar-only: it emits one configured 100% NAV slice with verified freshness from the consumer age, mirroring `chainlink-nav`'s single-bucket model. Chronicle's IPFS `UPoked` proof objects (position-level composition) are a documented future enhancement, not consumed today. ACRDX and STAC bind this adapter. diff --git a/shared/data/safety-score-v9/transfer-review-overlays-v1.json b/shared/data/safety-score-v9/transfer-review-overlays-v1.json index 8adcff45d..781d180dd 100644 --- a/shared/data/safety-score-v9/transfer-review-overlays-v1.json +++ b/shared/data/safety-score-v9/transfer-review-overlays-v1.json @@ -5006,6 +5006,107 @@ ] } ] + }, + { + "assetId": "usda-anzens", + "reviewedAt": "2026-09-05", + "reviewer": "OpenAI Codex Safety Score gap campaign", + "deployments": [ + { + "chainId": "cardano", + "contractOrTokenId": "fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae45655534441", + "scope": "canonical", + "posture": "permissionless", + "evidence": "The Cardano Foundation token-registry mapping pinned at commit 8a2cd449b86ac6f27a7de34dd0845160a832266b binds this exact policy ID and asset name to Anzens USDA, its issuer domain, and six decimals. Cardano executes the asset minting policy only when a transaction mints or burns that asset; an ordinary transfer of already-minted native tokens spends the holder UTxO without invoking the policy. The issuer therefore has no token-level holder-transfer hook. Issuer account, custody, and direct-redemption restrictions are separate from transferability of holder-controlled native tokens.", + "sources": [ + { + "label": "Pinned Cardano Foundation USDA identity mapping", + "url": "https://github.com/cardano-foundation/cardano-token-registry/blob/8a2cd449b86ac6f27a7de34dd0845160a832266b/mappings/fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae45655534441.json" + }, + { + "label": "Cardano Foundation minting-policy execution semantics (updated July 1, 2026)", + "url": "https://developers.cardano.org/docs/developers/curriculum/native-tokens/minting-policies/" + } + ] + } + ] + }, + { + "assetId": "usdm-moneta", + "reviewedAt": "2026-09-05", + "reviewer": "OpenAI Codex Safety Score gap campaign", + "deployments": [ + { + "chainId": "cardano", + "contractOrTokenId": "c48cbb3d5e57ed56e276bc45f99ab39abe94e6cd7ac39fb402da47ad0014df105553444d", + "scope": "canonical", + "posture": "permissionless", + "evidence": "The Cardano Foundation token-registry mapping pinned at commit 8a2cd449b86ac6f27a7de34dd0845160a832266b binds this exact policy ID and asset name to Moneta USDM, its issuer domain, and six decimals. Cardano executes the asset minting policy only when a transaction mints or burns that asset; an ordinary transfer of already-minted native tokens spends the holder UTxO without invoking the policy. The issuer therefore has no token-level holder-transfer hook. Issuer account, custody, and direct-redemption restrictions are separate from transferability of holder-controlled native tokens.", + "sources": [ + { + "label": "Pinned Cardano Foundation USDM identity mapping", + "url": "https://github.com/cardano-foundation/cardano-token-registry/blob/8a2cd449b86ac6f27a7de34dd0845160a832266b/mappings/c48cbb3d5e57ed56e276bc45f99ab39abe94e6cd7ac39fb402da47ad0014df105553444d.json" + }, + { + "label": "Cardano Foundation minting-policy execution semantics (updated July 1, 2026)", + "url": "https://developers.cardano.org/docs/developers/curriculum/native-tokens/minting-policies/" + } + ] + } + ] + }, + { + "assetId": "aznd-mu-digital", + "reviewedAt": "2026-09-05", + "reviewer": "OpenAI Codex Safety Score gap campaign", + "deployments": [ + { + "chainId": "monad", + "contractOrTokenId": "0x4917a5ec9fcb5e10f47cbb197abe6ab63be81fe8", + "scope": "canonical", + "posture": "restrictable", + "evidence": "At Monad block 102222519, canonical AZND was a direct ERC20 deployment with a zero EIP-1967 implementation slot. Its verified transfer paths have no pause or blacklist, but burn(owner, amount) grants the immutable PRIMARY_MARKET 0xe64730de04aeaf8d38925825d77d7a4c00340c89 authority over an arbitrary holder balance without allowance. The current PrimaryMarket implementation 0xe5f8ec544310d5ae79da0f921d01ce80ffd528eb burns only the redemption caller, but its UUPS replacement is authorized by the AccessManager default admin. At the same block, EOA 0x8b5f269f68d42490dee19fc4ab275cdd80940068 held that role in manager 0x7b14860ecd88fb65a22a1d259d1248bf199fb47f, and read-only eth_call of upgradeToAndCall to the existing implementation succeeded from that EOA. The live upgrade path can replace the holder consent guard around the privileged burn, so transfer posture is restrictable.", + "sources": [ + { + "label": "MonadScan verified canonical AZND source", + "url": "https://monadscan.com/address/0x4917a5ec9fcb5e10f47cbb197abe6ab63be81fe8#code" + }, + { + "label": "MonadScan verified PrimaryMarket implementation and UUPS authority", + "url": "https://monadscan.com/address/0xe5f8ec544310d5ae79da0f921d01ce80ffd528eb#code" + }, + { + "label": "Monad observation block 102222519", + "url": "https://monadscan.com/block/102222519" + }, + { + "label": "Mu Digital canonical and representation address registry", + "url": "https://docs.mudigital.net/technical-reference/smart-contract-architecture/smart-contract-addresses.md" + } + ] + }, + { + "chainId": "ethereum", + "contractOrTokenId": "0x52c66b5e7f8fde20843de900c5c8b4b0f23708a0", + "scope": "material-bridge", + "posture": "restrictable", + "evidence": "At Ethereum block 25912083, the AZND OFT proxy resolved to verified implementation 0x2e49c14a73210356de9f23a46a98de777b8a0db6. ContractBaseUpgradeable authorizes UUPS replacement through the default-admin role of accessManager 0xe683fcd4411dde6fc2be16c2512f9109c907b520. Address 0x8b5f269f68d42490dee19fc4ab275cdd80940068 held that role and a read-only eth_call of upgradeToAndCall to the existing implementation succeeded from that address. Ordinary ERC20 transfers are currently open, but this active token-logic replacement path can change holder transfer behavior, making the representation restrictable.", + "sources": [ + { + "label": "Blockscout verified Ethereum AZND OFT implementation and bundled dependencies", + "url": "https://eth.blockscout.com/api/v2/smart-contracts/0x2e49c14a73210356de9f23a46a98de777b8a0db6" + }, + { + "label": "Ethereum AZND proxy", + "url": "https://etherscan.io/address/0x52c66b5e7f8fde20843de900c5c8b4b0f23708a0#code" + }, + { + "label": "Ethereum observation block 25912083", + "url": "https://etherscan.io/block/25912083" + } + ] + } + ] } ] } diff --git a/shared/data/stablecoins/coins/brlv-crown.json b/shared/data/stablecoins/coins/brlv-crown.json index 87df5ebe0..f7b1878a2 100644 --- a/shared/data/stablecoins/coins/brlv-crown.json +++ b/shared/data/stablecoins/coins/brlv-crown.json @@ -17,25 +17,10 @@ "proofOfReserves": { "type": "self-reported", "url": "https://www.crown-brlv.com/transparencia/", - "provider": "Fact Finance Ltda technical verification memos (an RWA oracle firm, not an audit practice; signer is a technical lead, not a licensed accountant; the 'daily audits' claim is marketing copy with no daily artefact, and the monthly memo series shows no June or July 2026 report)", + "provider": "Fact Finance Ltda technical verification memos (an RWA oracle firm, not an audit practice; signer is a technical lead, not a licensed accountant; monthly reports are published through August 2026, while the 'daily audits' claim has no daily artefact)", "attestorTier": "none", - "cadence": "ad-hoc", - "attestorJurisdiction": "Brazil", - "latestReport": { - "periodEnd": "2026-05-31", - "publishedAt": "2026-05-31", - "assuranceMethod": "onchain-proof", - "scope": "assets-only", - "liabilityReconciliation": "none", - "reviewer": "PoR verification sweep 2026-08 (memo carries no assurance standard, no opinion, and no accountant signature; series stalled after May 2026)", - "confidence": "probable", - "sources": [ - { - "label": "Fact Finance BRLV proof-of-reserves report - May 2026", - "url": "https://dfg4lo8c2lfcn.cloudfront.net/report_brlv_5_2026_af8cfa373d.pdf" - } - ] - } + "cadence": "monthly", + "attestorJurisdiction": "Brazil" }, "links": [ { diff --git a/shared/data/stablecoins/coins/feusd-felix.json b/shared/data/stablecoins/coins/feusd-felix.json index 90861bef8..2dec30a26 100644 --- a/shared/data/stablecoins/coins/feusd-felix.json +++ b/shared/data/stablecoins/coins/feusd-felix.json @@ -80,7 +80,7 @@ "risk": "very-high" }, { - "name": "kHYPE (KittenFinance)", + "name": "kHYPE (Kinetiq)", "holder": "0xbfd0b103a49faf426f36864d19f5d871bf411a5a", "token": { "chain": "hyperevm", diff --git a/shared/data/stablecoins/coins/usdy-ondo-finance.json b/shared/data/stablecoins/coins/usdy-ondo-finance.json index ea8c58184..26c3a7d58 100644 --- a/shared/data/stablecoins/coins/usdy-ondo-finance.json +++ b/shared/data/stablecoins/coins/usdy-ondo-finance.json @@ -124,6 +124,11 @@ "adapter": "chainlink-nav", "version": 1, "semantics": "single-asset", + "suspended": { + "reason": "The NAV oracle measures USDY price and token supply, not the holdings or weights of its multi-asset reserve portfolio. Use the separately dated reviewed Ondo holdings composition until a holdings producer is integrated.", + "since": "2026-09-05", + "reviewBy": "2026-09-12" + }, "scoring": { "maxSourceAgeSec": 4000000 }, diff --git a/shared/data/stablecoins/domains/mint-authority/kag-kinesis.json b/shared/data/stablecoins/domains/mint-authority/kag-kinesis.json index 163fa9561..8757691a3 100644 --- a/shared/data/stablecoins/domains/mint-authority/kag-kinesis.json +++ b/shared/data/stablecoins/domains/mint-authority/kag-kinesis.json @@ -14,6 +14,22 @@ "Which Kinesis-native deployment and current authority create and govern the parent KAG liabilities?", "What exact Kinesis-side reserve-locking and redemption accounting invariant binds the Ethereum KMS Labs representation to native KAG?" ], + "noLocalIssuance": { + "kind": "external-only-representation", + "reviewedAt": "2026-09-05", + "reviewer": "OpenAI Codex Safety Score gap campaign", + "rationale": "The sole authored deployment is the Ethereum KMS Labs representation. Kinesis identifies that ERC-20 as a 1:1 claim on Kinesis KAG held by KMS Labs S.A., with native KAG issued separately by Kinesis Cayman. The reviewed Ethereum representation route covers the entire authored deployment inventory; its uncapped minter, default-admin and UUPS controls remain scored under Bridge Risk at the opaque-or-unknown route tier. This exception establishes no local canonical issuance, without claiming that the unresolved native Kinesis authority or reserve-locking invariant has been verified.", + "sources": [ + { + "label": "Kinesis Silver product disclosure distinguishing native KAG from the KMS Labs ERC-20 claim", + "url": "https://kinesis.money/silver/" + }, + { + "label": "Authored Ethereum KAG representation contract", + "url": "https://etherscan.io/address/0x56ba8b58b7d1f6d384a1c4dd553f39ebc8741b8e" + } + ] + }, "evidence": "Kinesis official material identifies native KAG as issued by Kinesis Cayman on the Kinesis network and the Ethereum ERC-20 as a KMS Labs 1:1 claim on Kinesis KAG held in KMS Labs reserves. The catalog does not author a Kinesis-native deployment or expose its current issuance authority. The Ethereum representation's ERC20F minter, default-admin, and UUPS upgrade facts are reviewed under Bridge Risk; they do not establish native Kinesis issuance. The semiannual physical-metal audit and redemption wording do not establish a complete KMS Labs ERC-20-supply-to-reserve reconciliation or a mechanical Kinesis-side lock/mint invariant.", "reviewer": "Pharos mint-authority boundary review; FIX-08 migration", "reviewedAt": "2026-08-16", diff --git a/shared/data/stablecoins/domains/reserves/brlv-crown.json b/shared/data/stablecoins/domains/reserves/brlv-crown.json index 58490331c..884384cf2 100644 --- a/shared/data/stablecoins/domains/reserves/brlv-crown.json +++ b/shared/data/stablecoins/domains/reserves/brlv-crown.json @@ -3,7 +3,7 @@ "reserves": [ { "name": "Brazilian federal government bonds", - "pct": 99.99, + "pct": 99.98, "risk": "low", "assetClass": "government-security", "issuerOrObligor": "Federative Republic of Brazil", @@ -12,7 +12,7 @@ }, { "name": "ETFs linked to Brazilian Treasury securities", - "pct": 0.01, + "pct": 0.02, "risk": "low", "assetClass": "fund-share", "issuerOrObligor": "Undisclosed Brazilian Treasury ETF issuers", @@ -21,37 +21,37 @@ } ], "reserveReview": { - "reviewedAt": "2026-08-08", + "reviewedAt": "2026-09-05", "reviewer": "Pharos reserve research", "confidence": "verified", "sources": [ { - "label": "Fact Finance BRLV proof-of-reserves report - May 2026 (re-opened 2026-08-08)", - "url": "https://dfg4lo8c2lfcn.cloudfront.net/report_brlv_5_2026_af8cfa373d.pdf" + "label": "Fact Finance BRLV proof-of-reserves report - August 2026, dated August 31", + "url": "https://dfg4lo8c2lfcn.cloudfront.net/report_brlv_8_2026_1ea3d5e0be.pdf" }, { - "label": "Crown BRLV reserve transparency (re-opened 2026-08-08)", + "label": "Crown BRLV reserve transparency and monthly-report index (observed 2026-09-05)", "url": "https://www.crown-brlv.com/en/transparency/" } ], - "rationale": "Re-opened 2026-08-08. The Crown transparency page still lists report_brlv_5_2026.pdf as the latest BRLV reserve report; no June, July, or August 2026 BRLV PDF is published. The May 31, 2026 Fact Finance report still records Brazilian Treasury Bonds at 99.99% and ETFs linked to Brazilian Treasury securities at 0.01%, with cash rounded to 0.00%. Because the composition as-of date remains 2026-05-31, the curated rows are stale versus the 31-day admission window.", - "compositionBasis": "Fact Finance BRLV proof-of-reserves report as of May 31, 2026", - "compositionAsOf": "2026-05-31", + "rationale": "Verified both pages of Fact Finance's technical verification memo dated August 31, 2026, linked by Crown's current monthly-report index alongside the June and July reports. The memo reports R$380,315,618.44 of reserves against R$378,063,743.00 of circulating BRLV (100.60% coverage). Its two Brazilian Treasury bond rows total R$380,258,017.98 and its two Treasury ETF rows total R$57,540.25; R$60.21 is cash. Aggregating the stated balances and rounding to two decimals produces 99.98% government bonds and 0.02% ETFs, with cash below 0.0001%. The report identifies the tracked Base contract 0xd7Ca0e2C36d647446b782D1b72308E598373E2F5. It is signed by Fact Finance's technical lead and carries no accountant assurance standard or opinion; freshness does not upgrade that engagement classification.", + "compositionBasis": "Fact Finance BRLV technical verification memo dated August 31, 2026; category percentages computed from its stated reserve balances and rounded to two decimals; PDF SHA-256 68325f70157b6d549f534900917e84d40bdcbc9bc9348de2a717c11f8e01f8cd", + "compositionAsOf": "2026-08-31", "scope": "full-composition", - "knownUnknownExposure": "The report identifies the reserve asset categories but does not disclose security identifiers, maturities, custodian names, or provider-level allocation.", + "knownUnknownExposure": "The report identifies the reserve asset categories but does not disclose security identifiers, maturities, custodian names, or provider-level allocation. Its August 31 report date is verified; its posting date is undisclosed (PDF creation metadata is September 1, and public availability was first observed September 5). No structured latestReport publication date is asserted.", "knownUnknownExposurePct": 0, "nonLinkDispositions": [ { "reserveIndex": 0, "reserveName": "Brazilian federal government bonds", - "pct": 99.99, + "pct": 99.98, "disposition": "untracked-exogenous-asset", "rationale": "Brazilian federal government securities are external reserve assets rather than tracked stablecoin dependencies." }, { "reserveIndex": 1, "reserveName": "ETFs linked to Brazilian Treasury securities", - "pct": 0.01, + "pct": 0.02, "disposition": "untracked-exogenous-asset", "rationale": "Brazilian Treasury-linked ETF shares are external reserve assets rather than tracked stablecoin dependencies." } From 03f2cd027de03cd022883308af17332fcac42028 Mon Sep 17 00:00:00 2001 From: TokenBrice Date: Sat, 5 Sep 2026 17:58:32 +0200 Subject: [PATCH 04/13] fix(ci): recognize reviewed Cardano asset identifiers Allow only the two published USDA and USDM policy-and-asset IDs in the transfer-review registry. Keep the default credential rules active for all other values and paths. Plan: agents/2026-09-05-safety-score-gap-campaign/README.md Tasks: V1 --- .gitleaks.toml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.gitleaks.toml b/.gitleaks.toml index 541aada04..7b6957c47 100644 --- a/.gitleaks.toml +++ b/.gitleaks.toml @@ -112,3 +112,16 @@ regexes = [ '''0x6a4854078428eea517e8013827cba9f90583f8a9fdf8610c8791d1b04feb6a3eee428bf506c98ead1e870918f26a1d41f5bfb792b1e4f331679d1ad3e527326f''', ] paths = ['''(?:^|/)shared/data/stablecoins/domains/risk-review/usdcx-movement\.json$'''] + +# Exact Cardano policy-id + asset-name identifiers, pinned to the published +# Cardano Foundation token registry in each transfer review's sources. +[[allowlists]] +description = "Reviewed USDA and USDM Cardano asset identifiers are public, not credentials" +targetRules = ["generic-api-key"] +condition = "AND" +regexTarget = "secret" +regexes = [ + '''^fe7c786ab321f41c654ef6c1af7b3250a613c24e4213e0425a7ae45655534441$''', + '''^c48cbb3d5e57ed56e276bc45f99ab39abe94e6cd7ac39fb402da47ad0014df105553444d$''', +] +paths = ['''(?:^|/)shared/data/safety-score-v9/transfer-review-overlays-v1\.json$'''] From 1212350c2df8f4a48f03734a145034383135a3fb Mon Sep 17 00:00:00 2001 From: TokenBrice Date: Sat, 5 Sep 2026 17:59:19 +0200 Subject: [PATCH 05/13] fix(safety-score): align ZARm evidence with its reserve mechanism Replace the obsolete CDP overlay with a current review of Mento's shared reserve custody and reconciliation disclosures. Keep the three qualitative components limited, preserve the source date and same-day admission guard, and make no claim of independent assurance or dedicated holder recovery. Plan: agents/2026-09-05-safety-score-gap-campaign/README.md Tasks: C1, V1 --- .../mechanism-review-overlays-v1.json | 69 +++++++------------ 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/shared/data/safety-score-v9/mechanism-review-overlays-v1.json b/shared/data/safety-score-v9/mechanism-review-overlays-v1.json index 6712df7ee..e8351f09b 100644 --- a/shared/data/safety-score-v9/mechanism-review-overlays-v1.json +++ b/shared/data/safety-score-v9/mechanism-review-overlays-v1.json @@ -829,64 +829,41 @@ }, { "assetId": "zarm-mento", - "archetype": "cdp", - "reviewedAt": "2026-07-15", + "archetype": "fiat-cash", + "reviewedAt": "2026-09-05", "sources": [ { - "label": "Mento BiPoolManager and FPMM conversion state on Celo", - "url": "https://celoscan.io/address/0x22d9db95e6ae61c104a7b6f6c78d7993b94ec901" + "label": "Mento itemized reserve assets, custody wallets and positions (fetched 2026-09-05)", + "url": "https://mento-analytics-api-12390052758.us-central1.run.app/api/v2/reserve" }, { - "label": "Mento V3 reserve conversion documentation", - "url": "https://docs.mento.org/mento-v3" - } - ], - "notes": "Pinned Celo block 72202985; journaled evidence: shared/data/safety-score-v9/mechanism-measurements/zarm-mento/2026-07-15-block-72202985.json. The producer enumerated all 16 live exchange IDs, found the configured token/USDm pair exactly once, and measured 10000000 USDm of counter-bucket capacity at the journaled 30 bps fee. Conversion capacity is an analogous exit metric rather than dedicated liquidation capital. The bounded virtual USDm counter-bucket provides limited conversion support; pair buckets limit flow but Broker, USDm, reserve, and oracle failure domains remain shared; BreakerBox and trading limits can halt unsafe conversion without supplying an unconditional residual-deficit recapitalization path. Backstop, branch isolation, and shutdown/bad-debt handling are therefore limited.", - "metrics": { - "collateralizationRatio": null, - "liquidationCapacityRatio": null - }, - "metricApplicability": { - "collateralizationRatio": { - "state": "not-applicable", - "rationale": "This reserve/conversion token has no independently collateralized per-token vault system.", - "sourceUrl": "https://docs.mento.org/mento-v3" + "label": "Mento protocol-wide reserve, liquidity and governance risk disclosure", + "url": "https://docs.mento.org/mento-v3/dive-deeper/security/risk-overview.md" }, - "liquidationCapacityRatio": { - "state": "not-applicable", - "rationale": "The measured counter-asset conversion inventory is redemption liquidity, not capital committed to debt-offset liquidation.", - "sourceUrl": "https://docs.mento.org/mento-v3" - } - }, - "analogousMetrics": { - "conversionCapacityCounterUnits": 10000000 - }, - "components": { - "collateralizationParameters": { - "applicability": "not-applicable", - "rationale": "This reserve/conversion token has no independently collateralized per-token vault system.", - "sourceUrl": "https://docs.mento.org/mento-v3" + { + "label": "Mento shared V2/V3 Reserve and StableTokenZAR deployment inventory", + "url": "https://docs.mento.org/mento-v3/build/deployments/addresses.md" }, - "liquidationMechanics": { - "applicability": "not-applicable", - "rationale": "The measured counter-asset conversion inventory is redemption liquidity, not capital committed to debt-offset liquidation.", - "sourceUrl": "https://docs.mento.org/mento-v3" + { + "label": "Sourcify verified ZARm proxy source on Celo", + "url": "https://sourcify.dev/server/v2/contract/42220/0x4c35853a3b4e647fd266f4de678dcc8fec410bf6?fields=abi,metadata,sources" }, - "backstop": { - "applicability": "measured", + { + "label": "Celo finalized block 76721442 (ZARm and Reserve code; ZARm totalSupply reads)", + "url": "https://celoscan.io/block/76721442" + } + ], + "notes": "Reviewed 2026-09-05: replaces the stale CDP overlay to match the existing fiat-cash classification for shared protocol-reserve inventory. The official deployment inventory identifies StableTokenZAR at 0x4c35853a3b4e647fd266f4de678dcc8fec410bf6 and the shared Reserve at 0x9380fa34fd9e4fd14c06305fd7b6199089ed4eb9. The itemized reserve API disclosed $19,959,907.699599963 of collateral across named hot, cold and operational wallets and deployed positions; its CDP rows contain no ZARm debt. At finalized Celo block 76721442, both deployment addresses had contract code and ZARm totalSupply was 10925.176443780387 tokens. These primary disclosures and on-chain anchors support limited claim/segregation, custody continuity, and assurance/reconciliation under the same shared-reserve framing as CADm, KESm and BRLm. Claim/segregation is limited to observable protocol reserve inventory, not a direct legal holder claim or a ZARm-specific partition. Custody continuity is limited because the disclosed reserve wallets and protocol governance remain shared failure domains. Assurance is limited to itemized self-reported assets corroborated by contract and token-supply reads, not an independently attested reserve-to-liability reconciliation or a complete cross-chain balance audit. No independent auditor, enforceable holder seniority, bankruptcy-remoteness, or dedicated ZARm recovery commitment is established. The broad fiat-cash classification is economically broader than ordinary custodial fiat cash. Raw primary responses and block reads are retained in agents/2026-09-05-safety-score-gap-campaign/evidence/zarm-*. No current CDP ratio, liquidation capacity or historical virtual conversion inventory is carried into the fiat-cash overlay.", + "metrics": {}, + "components": { + "claimAndSegregation": { "quality": "limited" }, - "branchIsolation": { - "applicability": "measured", + "custodyContinuity": { "quality": "limited" }, - "shutdownAndBadDebt": { - "applicability": "measured", + "assuranceAndReconciliation": { "quality": "limited" - }, - "structuralRedemption": { - "applicability": "measured", - "quality": "adequate" } } }, From 9c3df04da6ec544c487eee81678560884be42020 Mon Sep 17 00:00:00 2001 From: TokenBrice Date: Sat, 5 Sep 2026 18:01:56 +0200 Subject: [PATCH 06/13] chore(safety-score): pin the refreshed mechanism evidence build Regenerate the evaluation identity after the ZARm mechanism review so production binds the exact curated evidence shipped by this campaign. Plan: agents/2026-09-05-safety-score-gap-campaign/README.md Tasks: V1 --- shared/data/safety-score-v9/evaluation-build-manifest-v1.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shared/data/safety-score-v9/evaluation-build-manifest-v1.ts b/shared/data/safety-score-v9/evaluation-build-manifest-v1.ts index 08d88b87f..78730fbe1 100644 --- a/shared/data/safety-score-v9/evaluation-build-manifest-v1.ts +++ b/shared/data/safety-score-v9/evaluation-build-manifest-v1.ts @@ -10,7 +10,7 @@ export const SAFETY_SCORE_V9_EVALUATION_BUILD_MANIFEST = { }, { "path": "shared/data/safety-score-v9/mechanism-review-overlays-v1.json", - "sha256": "e573e4bd61f70a6ada918cd9c980e4d092de1fda5c8cb7791b3797c7f191fb83" + "sha256": "2da505ee315106f16268ffa1acd16fe27c45d3109dbb9cd467482ba937e6ed2d" }, { "path": "shared/data/safety-score-v9/methodology-policy-candidate-v1.json", @@ -603,7 +603,7 @@ export const SAFETY_SCORE_V9_EVALUATION_BUILD_MANIFEST = { "r2Key": "captures/lusd-liquity/2026-09-03-block-25895460-shock-coverage.json.gz" } ], - "digest": "b2a6be46f7d1369f6f9a27002833d6b3a75344d16a69f5d970d9814b31c0756d" + "digest": "e6b6868f9787a544a8ec28ce685469047ab67cfd73cc53e6a8d5c16f46e78b38" } as const; export const SAFETY_SCORE_V9_EVALUATION_BUILD_DIGEST = From e48f1c2c2dc537fbd989f168714d126f0fb45498 Mon Sep 17 00:00:00 2001 From: TokenBrice Date: Sat, 5 Sep 2026 18:07:26 +0200 Subject: [PATCH 07/13] fix(nav): label compact depeg link clearly Use Depeg instead of DDR in the compact navigation so the destination is understandable at narrow widths. Share that label with the phone navigation and remove its acronym-specific override. --- src/components/__tests__/mobile-bottom-nav.test.tsx | 3 ++- src/components/mobile-bottom-nav.tsx | 9 +-------- src/lib/nav-config.ts | 5 ++--- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/components/__tests__/mobile-bottom-nav.test.tsx b/src/components/__tests__/mobile-bottom-nav.test.tsx index 8e1088c47..ff6d5703a 100644 --- a/src/components/__tests__/mobile-bottom-nav.test.tsx +++ b/src/components/__tests__/mobile-bottom-nav.test.tsx @@ -43,9 +43,10 @@ describe("MobileBottomNav", () => { routeItems.forEach((item, index) => { expect(links[index]?.getAttribute("href")).toBe(item.href); - const expectedLabel = item.shortLabel === "DDR" ? "Depegs" : (item.shortLabel ?? item.label); + const expectedLabel = item.shortLabel ?? item.label; expect(links[index]?.textContent).toContain(expectedLabel); }); + expect(screen.getByRole("link", { name: "Depeg" }).getAttribute("href")).toBe("/depeg/"); }); it("marks the config-derived current route as the current page", () => { diff --git a/src/components/mobile-bottom-nav.tsx b/src/components/mobile-bottom-nav.tsx index 528572c58..c41ef91b6 100644 --- a/src/components/mobile-bottom-nav.tsx +++ b/src/components/mobile-bottom-nav.tsx @@ -9,13 +9,6 @@ import { isRouteActive } from "@/lib/navigation"; const MOBILE_ROUTE_ITEMS = QUICK_NAV_ITEMS.filter((item) => item.href !== "/stability-index/"); -function mobileLabel(item: (typeof MOBILE_ROUTE_ITEMS)[number]): string { - // "DDR" is useful in the constrained desktop rail, but "Depegs" is more - // legible as a persistent phone-nav label. - if (item.shortLabel === "DDR") return "Depegs"; - return item.shortLabel ?? item.label; -} - export function MobileBottomNav() { const pathname = usePathname(); @@ -42,7 +35,7 @@ export function MobileBottomNav() { } >