You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is the final gate in the METAGRAPH_EXTRINSICS_SOURCE cutover sequence (ADR 0013 §Sequencing step 4: "Cut serving over to Hyperdrive→Postgres tier-by-tier (blocks → extrinsics → accounts → metagraph)"). METAGRAPH_BLOCKS_SOURCE already flipped to "postgres" (wrangler.jsonc:87); METAGRAPH_EXTRINSICS_SOURCE remains "d1" (wrangler.jsonc:88) specifically because a manual 105-call-type sweep across extrinsics.call_args found systemic shape divergence between D1's decoder (fetch-events.py, Python/substrate-interface) and indexer-rs's generic dynamic-SCALE dump. That sweep is one-shot and manual — it will not survive a runtime upgrade, a new pallet, or a new call variant. We need a harness that keeps re-asking the question this issue's parent audit asked once.
Divergence categories the harness must exercise (not exhaustive re-derivation — full detail lives in #4669 and this round's audit; summarized for the harness's test-matrix design):
AccountId32 (~45 of 105 call types): D1 "hotkey":"5H1nRfbCbDGh3t17er9Y8hwFEsXCrjBbaN6jLrnez8KpUKju" vs Postgres "hotkey":[[218,242,207,184,...]] — confirmed on SubtensorModule.register block 8556317 extrinsic_index 20 and SubtensorModule.add_stake block 8587451 extrinsic_index 20.
MultiAddress::Id (Balances/Proxy/Contracts dest fields, ~60k+ extrinsics/365d): D1 "dest":"5FQsrbnp2dEvemXG41JmjnrsSaSQyfGAoe5hK3EHBhu8Z1sT" vs Postgres "dest":{"name":"Id","values":[[[148,23,68,...]]]} — Balances.transfer_all block 8587450 extrinsic_index 20.
Hash/H256 newtype (double-wrapped) vs raw [u8;32] (single-wrapped, Multisig call_hash only) — two distinct wrap depths that a naive "32 ints = hash" heuristic would conflate. SubtensorModule.commit_weights block 8587046 extrinsic_index 19 vs Multisig.approve_as_multi block 8587346 extrinsic_index 18.
Option: D1 unwraps to the value or null; Postgres keeps {"name":"Some","values":[x]} / {"name":"None","values":[]} — AdminUtils.sudo_set_mechanism_emission_split block 8559935 extrinsic_index 32 (maybe_split).
Ethereum U256/H160 (Ethereum.transact, 218,926 extrinsics/365d — 6–9% of all in-window extrinsics): D1 gives plain JS numbers ("nonce":69392); Postgres gives 4-limb newtype arrays ("nonce":[[69392,0,0,0]]) — block 8587453 extrinsic_index 9. The same extrinsic also carries a live correctness bug independent of the flip: D1's input field is force-decoded as UTF-8/Latin1 producing mojibake ("aF\u0019T") while Postgres holds the exact bytes ([97,70,25,84]) — Postgres is strictly more correct here and the harness must not flag this as a regression.
Raw byte blob / Vec (~15+ call types, several of the highest-volume in the dataset — Commitments.set_commitment 214,695/365d, Drand.write_pulse 288,975/365d, SubtensorModule.commit_timelocked_mechanism_weights 389,473/365d): D1 is internally inconsistent (hex string for opaque blobs, decoded UTF-8 for identity/remark fields) vs Postgres's uniform flat integer array — SubtensorModule.register block 8556317 extrinsic_index 20 (work), System.remark_with_event block 8512299 extrinsic_index 12 (remark).
u64/u128 beyond Number.MAX_SAFE_INTEGER: D1 already serves a rounded value (nonce=9131459485341369000), Postgres holds the exact one (9131459485341369597) — SubtensorModule.register block 8556317 extrinsic_index 20. A true u64::MAX sentinel diverges the same way: D1 18446744073709552000 vs Postgres 18446744073709551615 on SubtensorModule.set_children block 8585337 extrinsic_index 19. The audit found that the shared formatExtrinsic() (src/extrinsics.mjs:154) JSON-parses whichever backend's text and, for these two sampled values, JS's own float64 rounding happens to converge on the value D1 already serves — but this was checked on exactly two values, not proven as a general property, and Postgres's extra precision is exposed by neither serving path today.
Matched, no-op categories (confirm the harness doesn't over-alarm): Weight {ref_time,proof_size}, Timepoint {height,index}, C-like enums matched only in the unwrapped-vs-tagged sense (see divergence list — actually diverges, correcting: matched categories are Weight/Timepoint/plain scalars/flat primitive Vecs), and — critically — the single highest-volume call type in the whole window, SubtensorModule.set_weights (1,019,157 extrinsics/365d), has no account/byte/enum fields at all and is byte-identical (dests/weights/version_key verified block 8587431 extrinsic_index 28).
Why this needs more caution than the blocks-tier precedent, not less (per this same audit round, re-checking the already-flipped METAGRAPH_BLOCKS_SOURCE):
Postgres has real completeness gaps the audit did not expect: ~9,000 missing blocks (8471001–8479999), a missing runtime-upgrade boundary block (8513820, the first block of spec_version 424 — present in Postgres, absent from D1), and 1,380 rows with an empty-string author in three backfilled ranges (blocks 8460000–8460299, 8470000–8471000, 8480000–8480099) where D1 has the correctly decoded SS58 author.
Worse: 15 out of 15 cache-busted GET /api/v1/blocks/8513820 requests (a block that exists only in Postgres) returned "block":null in live production, despite METAGRAPH_BLOCKS_SOURCE=postgres being set — wrangler tail showed the DATA_API service-binding subrequest outcome as "canceled" on most invocations. This means the earlier "verified byte-identical" spot-check for blocks was very likely comparing D1-against-D1 (silent fallback), not genuinely exercising live Postgres serving. Any parity check that only diffs what the live API returns is at risk of the same false-negative masking — the harness must query both stores directly, not just diff API responses.
chain_events.args (a structurally identical but unconditional, unflagged, already-live route — workers/api.mjs:1195-1224 → handleChainEventsProxy → workers/data-api.mjs:320-324,368-384) has the exact same AccountId32-as-raw-array bug in production today, confirmed live: GET /api/v1/chain-events?limit=3 → TransactionPayment.TransactionFeePaidargs.who=[[216,18,224,27,...]]. This was independently hit and partially patched client-side only (commit 6eb2c1ae, closes New chain-events Args column shows raw undecoded byte arrays, hard-clipped at the viewport edge with no way to read or copy the value #3984, merged 2026-07-09) — the API/MCP responses themselves are still unfixed. It's a strong signal that "ship the flip, patch the UI later" is exactly the failure mode to avoid this time.
The MCP surface (src/mcp-server.mjs:5407list_extrinsics, :5449get_extrinsic) never calls tryPostgresTier — it is D1-only, always, unlike the REST route (workers/request-handlers/entities.mjs:3723,3973). Once the flag flips, REST and MCP will silently serve different shapes for the same extrinsic ref unless this is explicitly checked and either normalized or deliberately documented.
Requirements
Build a repeatable comparison harness, not another one-off manual sweep. It must independently query D1 and Postgres directly (psql on the indexer box / D1 binding — not just diff two live API responses), to avoid the fallback-masking false-negative risk found in the blocks-tier re-check above.
Cover every category from the audit's type matrix — both the diverged ones (AccountId32, MultiAddress::Id, Hash/H256 vs raw [u8;32], Option, nested RuntimeCall, C-like enums, enum-with-data, Ethereum U256/H160, byte blobs, BTreeSet, generic newtype-scalar) and the matched ones (Weight, Timepoint, plain scalars, flat primitive Vecs, set_weights), so the harness proves absence of regression on the high-volume matched path as well as presence of expected/normalized divergence on the rest.
Dedicated u64/u128 edge-case sampling — the harness must generate or select extrinsics with values near/above 2^53-1 and near u64::MAX/u128::MAX boundaries across a real window (not the two hand-picked samples in the audit), and assert whether D1's serving value and Postgres's serving value (as actually emitted by formatExtrinsic()/the Postgres route, not raw DB values) match byte-for-byte, not just "happen to round the same."
REST/MCP consistency check — for the same extrinsic ref, assert what GET /api/v1/extrinsics/:ref (Postgres, post-flip) and list_extrinsics/get_extrinsic (MCP, D1-only per src/mcp-server.mjs:5407,5449) each return, and surface any shape divergence as a first-class finding rather than an incidental one — this needs an explicit decision (normalize MCP too, or document the split) before the flip, not after.
Run as CI or a scheduled check, not a manual script someone remembers to run. Given chain_events and the blocks-tier reliability findings, this cannot be a single pre-flip gate — it should be capable of running on a schedule so drift (a new runtime upgrade, a new pallet, a new call variant) is caught continuously, both before and after the flip.
The flip itself is explicitly gated: METAGRAPH_EXTRINSICS_SOURCE may only change from "d1" to "postgres" in wrangler.jsonc after (a) all sibling normalization sub-issues in this roadmap have landed (server-side equivalents of the client-side normalizers in apps/ui/src/lib/metagraphed/extrinsics.ts for AccountId32/MultiAddress::Id → SS58, Hash/byte-blob → hex, Option unwrapping, enum tagging), and (b) this harness has run green across a real sampling window (not a handful of spot-checked blocks) that includes at least one runtime-upgrade boundary and one high-volume window for Ethereum.transact, Commitments.set_commitment, and SubtensorModule.commit_timelocked_mechanism_weights.
Treat the two accepted gaps from Roadmap: complete the D1 -> Postgres serving cutover for chain data (ADR 0013) #4669 as documented exceptions, not failures — the harness must special-case (a) Multisig.as_multi's unrecoverable computed call_hash, and (b) ambiguous bare 32-byte arrays nested inside call args that degrade to null/raw-array rather than asserting equality on them.
During any live-serving validation window, monitor wrangler tail (or equivalent) for DATA_API service-binding outcomes of "canceled" or non-"ok" — the blocks-tier check missed exactly this signal and it invalidated that check's conclusion.
Deliverables
A parity-check script/module (e.g. scripts/parity/extrinsics-postgres-parity.mjs or tests/extrinsics-postgres-parity.test.mjs, following the existing *-parity.test.mjs convention used by tests/pagination-parity.test.mjs / tests/pipeline-validator-parity.test.mjs) that:
Samples a configurable window of blocks/extrinsics directly from both stores.
Applies the normalization rules landed by the sibling sub-issues and asserts D1-serving-shape == normalized-Postgres-serving-shape per call type.
Reports per-category pass/fail counts (mirroring the audit's typeCategories structure) so a failure immediately tells you which decode rule regressed, not just "mismatch."
A scheduled workflow (pattern after .github/workflows/backfill-*.yml) or equivalent operational check that runs this harness on a cadence against the indexer box, with alerting on regression — this is what makes the check "not manual" per Requirement 5.
A REST-vs-MCP shape-consistency test for GET /api/v1/extrinsics/:ref vs get_extrinsic/list_extrinsics, with an explicit resolution (fix or documented split) landed before the flip.
The actual wrangler.jsonc change flipping METAGRAPH_EXTRINSICS_SOURCE from "d1" to "postgres", submitted only once the above are green, together with a short rollback note (how to flip back to "d1" and what triggers doing so) given the blocks-tier precedent of silent-fallback masking.
The harness can be run on demand and on a schedule, and produces a per-category report (matched / normalized-diverged / accepted-gap / unexplained mismatch) — "done" means zero unexplained mismatches across a real sampling window, not zero mismatches overall (several are expected and documented).
The u64/u128 edge-case check runs across a real range of large values (not two hand-picked ones) and its result is recorded, resolving the currently-open question of whether Postgres's extra precision ever surfaces a value D1's rounding disagrees with.
The REST/MCP consistency check either passes (both surfaces normalized) or is explicitly and intentionally left divergent with a documented reason — it must not be silently forgotten.
During a live validation window, no DATA_API service-binding "canceled" outcomes are observed for extrinsics requests — if they are, the flip is blocked until the blocks-tier reliability issue is independently resolved, since the same binding backs both tiers.
wrangler.jsonc's METAGRAPH_EXTRINSICS_SOURCE reads "postgres", the harness continues running post-flip (not just pre-flip) to catch drift, and Roadmap: complete the D1 -> Postgres serving cutover for chain data (ADR 0013) #4669 is closed referencing this issue as the mechanism that finally executed its "operational, not decode-gap" remaining work.
Links/resources
wrangler.jsonc:87-88 — the two serving-cutover flags (METAGRAPH_BLOCKS_SOURCE, METAGRAPH_EXTRINSICS_SOURCE).
workers/request-handlers/entities.mjs:3414-3430 (tryPostgresTier) and its call sites at :3479,3545 (blocks) and :3723,3973 (extrinsics).
workers/data-api.mjs:230-300 (extrinsics feed/detail routes, call_args::text cast) and :320-324,368-384 (chain_events.args, unconditional, no flag).
src/extrinsics.mjs:29,150-171 (formatExtrinsic, the shared D1/Postgres JSON-parse path) and :354-367 (call_hash LIKE-filter over call_args).
src/mcp-server.mjs:5407 (list_extrinsics), :5449 (get_extrinsic) — D1-only, no tryPostgresTier call.
apps/ui/src/lib/metagraphed/extrinsics.ts — existing client-side normalizers (callArgValue, hashBytesToHex, normalizeIndexerRsCall/asDecodedCall) to port server-side.
High — this is the sole remaining gate before METAGRAPH_EXTRINSICS_SOURCE can flip, and the blocks-tier re-check in this same audit round found live-serving reliability and completeness problems that make an ungated flip materially riskier than the blocks precedent suggested.
Context
This is the final gate in the
METAGRAPH_EXTRINSICS_SOURCEcutover sequence (ADR 0013 §Sequencing step 4: "Cut serving over to Hyperdrive→Postgres tier-by-tier (blocks → extrinsics → accounts → metagraph)").METAGRAPH_BLOCKS_SOURCEalready flipped to"postgres"(wrangler.jsonc:87);METAGRAPH_EXTRINSICS_SOURCEremains"d1"(wrangler.jsonc:88) specifically because a manual 105-call-type sweep acrossextrinsics.call_argsfound systemic shape divergence between D1's decoder (fetch-events.py, Python/substrate-interface) and indexer-rs's generic dynamic-SCALE dump. That sweep is one-shot and manual — it will not survive a runtime upgrade, a new pallet, or a new call variant. We need a harness that keeps re-asking the question this issue's parent audit asked once.Divergence categories the harness must exercise (not exhaustive re-derivation — full detail lives in #4669 and this round's audit; summarized for the harness's test-matrix design):
"hotkey":"5H1nRfbCbDGh3t17er9Y8hwFEsXCrjBbaN6jLrnez8KpUKju"vs Postgres"hotkey":[[218,242,207,184,...]]— confirmed onSubtensorModule.registerblock 8556317 extrinsic_index 20 andSubtensorModule.add_stakeblock 8587451 extrinsic_index 20."dest":"5FQsrbnp2dEvemXG41JmjnrsSaSQyfGAoe5hK3EHBhu8Z1sT"vs Postgres"dest":{"name":"Id","values":[[[148,23,68,...]]]}—Balances.transfer_allblock 8587450 extrinsic_index 20.[u8;32](single-wrapped, Multisigcall_hashonly) — two distinct wrap depths that a naive "32 ints = hash" heuristic would conflate.SubtensorModule.commit_weightsblock 8587046 extrinsic_index 19 vsMultisig.approve_as_multiblock 8587346 extrinsic_index 18.null; Postgres keeps{"name":"Some","values":[x]}/{"name":"None","values":[]}—AdminUtils.sudo_set_mechanism_emission_splitblock 8559935 extrinsic_index 32 (maybe_split).Multisig.as_multi,Proxy.proxy,Utility.batch*,Sudo.sudo*, ~55k+ extrinsics/365d viaUtility.batch*alone): partially normalized client-side already (apps/ui/src/lib/metagraphed/extrinsics.ts'snormalizeIndexerRsCall/asDecodedCall, Roadmap: complete the D1 -> Postgres serving cutover for chain data (ADR 0013) #4669/fix(ui): render indexer-rs's nested-call shape in the extrinsic detail page #4676), but the recomputed nestedcall_hash(the join key for 4.3 — Render Multisig.asMulti/approveAsMulti approval chain #4322's multisig-approval linking) is permanently unrecoverable from Postgres per Roadmap: complete the D1 -> Postgres serving cutover for chain data (ADR 0013) #4669's own accepted-gap note —Multisig.as_multiblock 8587390 extrinsic_index 13.Ethereum.transact, 218,926 extrinsics/365d — 6–9% of all in-window extrinsics): D1 gives plain JS numbers ("nonce":69392); Postgres gives 4-limb newtype arrays ("nonce":[[69392,0,0,0]]) — block 8587453 extrinsic_index 9. The same extrinsic also carries a live correctness bug independent of the flip: D1'sinputfield is force-decoded as UTF-8/Latin1 producing mojibake ("aF\u0019T") while Postgres holds the exact bytes ([97,70,25,84]) — Postgres is strictly more correct here and the harness must not flag this as a regression.Commitments.set_commitment214,695/365d,Drand.write_pulse288,975/365d,SubtensorModule.commit_timelocked_mechanism_weights389,473/365d): D1 is internally inconsistent (hex string for opaque blobs, decoded UTF-8 for identity/remark fields) vs Postgres's uniform flat integer array —SubtensorModule.registerblock 8556317 extrinsic_index 20 (work),System.remark_with_eventblock 8512299 extrinsic_index 12 (remark).Number.MAX_SAFE_INTEGER: D1 already serves a rounded value (nonce=9131459485341369000), Postgres holds the exact one (9131459485341369597) —SubtensorModule.registerblock 8556317 extrinsic_index 20. A trueu64::MAXsentinel diverges the same way: D118446744073709552000vs Postgres18446744073709551615onSubtensorModule.set_childrenblock 8585337 extrinsic_index 19. The audit found that the sharedformatExtrinsic()(src/extrinsics.mjs:154) JSON-parses whichever backend's text and, for these two sampled values, JS's own float64 rounding happens to converge on the value D1 already serves — but this was checked on exactly two values, not proven as a general property, and Postgres's extra precision is exposed by neither serving path today.{ref_time,proof_size}, Timepoint{height,index}, C-like enums matched only in the unwrapped-vs-tagged sense (see divergence list — actually diverges, correcting: matched categories are Weight/Timepoint/plain scalars/flat primitive Vecs), and — critically — the single highest-volume call type in the whole window,SubtensorModule.set_weights(1,019,157 extrinsics/365d), has no account/byte/enum fields at all and is byte-identical (dests/weights/version_keyverified block 8587431 extrinsic_index 28).Why this needs more caution than the blocks-tier precedent, not less (per this same audit round, re-checking the already-flipped
METAGRAPH_BLOCKS_SOURCE):authorin three backfilled ranges (blocks 8460000–8460299, 8470000–8471000, 8480000–8480099) where D1 has the correctly decoded SS58 author.GET /api/v1/blocks/8513820requests (a block that exists only in Postgres) returned"block":nullin live production, despiteMETAGRAPH_BLOCKS_SOURCE=postgresbeing set —wrangler tailshowed theDATA_APIservice-binding subrequest outcome as"canceled"on most invocations. This means the earlier "verified byte-identical" spot-check for blocks was very likely comparing D1-against-D1 (silent fallback), not genuinely exercising live Postgres serving. Any parity check that only diffs what the live API returns is at risk of the same false-negative masking — the harness must query both stores directly, not just diff API responses.chain_events.args(a structurally identical but unconditional, unflagged, already-live route —workers/api.mjs:1195-1224→handleChainEventsProxy→workers/data-api.mjs:320-324,368-384) has the exact same AccountId32-as-raw-array bug in production today, confirmed live:GET /api/v1/chain-events?limit=3→TransactionPayment.TransactionFeePaidargs.who=[[216,18,224,27,...]]. This was independently hit and partially patched client-side only (commit6eb2c1ae, closes New chain-events Args column shows raw undecoded byte arrays, hard-clipped at the viewport edge with no way to read or copy the value #3984, merged 2026-07-09) — the API/MCP responses themselves are still unfixed. It's a strong signal that "ship the flip, patch the UI later" is exactly the failure mode to avoid this time.src/mcp-server.mjs:5407list_extrinsics,:5449get_extrinsic) never callstryPostgresTier— it is D1-only, always, unlike the REST route (workers/request-handlers/entities.mjs:3723,3973). Once the flag flips, REST and MCP will silently serve different shapes for the same extrinsic ref unless this is explicitly checked and either normalized or deliberately documented.Requirements
[u8;32], Option, nested RuntimeCall, C-like enums, enum-with-data, Ethereum U256/H160, byte blobs, BTreeSet, generic newtype-scalar) and the matched ones (Weight, Timepoint, plain scalars, flat primitive Vecs,set_weights), so the harness proves absence of regression on the high-volume matched path as well as presence of expected/normalized divergence on the rest.2^53-1and nearu64::MAX/u128::MAXboundaries across a real window (not the two hand-picked samples in the audit), and assert whether D1's serving value and Postgres's serving value (as actually emitted byformatExtrinsic()/the Postgres route, not raw DB values) match byte-for-byte, not just "happen to round the same."GET /api/v1/extrinsics/:ref(Postgres, post-flip) andlist_extrinsics/get_extrinsic(MCP, D1-only persrc/mcp-server.mjs:5407,5449) each return, and surface any shape divergence as a first-class finding rather than an incidental one — this needs an explicit decision (normalize MCP too, or document the split) before the flip, not after.chain_eventsand the blocks-tier reliability findings, this cannot be a single pre-flip gate — it should be capable of running on a schedule so drift (a new runtime upgrade, a new pallet, a new call variant) is caught continuously, both before and after the flip.METAGRAPH_EXTRINSICS_SOURCEmay only change from"d1"to"postgres"inwrangler.jsoncafter (a) all sibling normalization sub-issues in this roadmap have landed (server-side equivalents of the client-side normalizers inapps/ui/src/lib/metagraphed/extrinsics.tsfor AccountId32/MultiAddress::Id → SS58, Hash/byte-blob → hex, Option unwrapping, enum tagging), and (b) this harness has run green across a real sampling window (not a handful of spot-checked blocks) that includes at least one runtime-upgrade boundary and one high-volume window forEthereum.transact,Commitments.set_commitment, andSubtensorModule.commit_timelocked_mechanism_weights.Multisig.as_multi's unrecoverable computedcall_hash, and (b) ambiguous bare 32-byte arrays nested inside call args that degrade tonull/raw-array rather than asserting equality on them.wrangler tail(or equivalent) forDATA_APIservice-binding outcomes of"canceled"or non-"ok"— the blocks-tier check missed exactly this signal and it invalidated that check's conclusion.Deliverables
scripts/parity/extrinsics-postgres-parity.mjsortests/extrinsics-postgres-parity.test.mjs, following the existing*-parity.test.mjsconvention used bytests/pagination-parity.test.mjs/tests/pipeline-validator-parity.test.mjs) that:typeCategoriesstructure) so a failure immediately tells you which decode rule regressed, not just "mismatch."Ethereum.transactinput-field mojibake-vs-correct-bytes case as known/expected, not failures..github/workflows/backfill-*.yml) or equivalent operational check that runs this harness on a cadence against the indexer box, with alerting on regression — this is what makes the check "not manual" per Requirement 5.GET /api/v1/extrinsics/:refvsget_extrinsic/list_extrinsics, with an explicit resolution (fix or documented split) landed before the flip.wrangler.jsoncchange flippingMETAGRAPH_EXTRINSICS_SOURCEfrom"d1"to"postgres", submitted only once the above are green, together with a short rollback note (how to flip back to"d1"and what triggers doing so) given the blocks-tier precedent of silent-fallback masking.docs/adr/0013-hybrid-deployment-topology.md's Sequencing/step-4 status and to Roadmap: complete the D1 -> Postgres serving cutover for chain data (ADR 0013) #4669's tracking issue closing it out, once the flip lands.Expected outcomes
DATA_APIservice-binding"canceled"outcomes are observed for extrinsics requests — if they are, the flip is blocked until the blocks-tier reliability issue is independently resolved, since the same binding backs both tiers.wrangler.jsonc'sMETAGRAPH_EXTRINSICS_SOURCEreads"postgres", the harness continues running post-flip (not just pre-flip) to catch drift, and Roadmap: complete the D1 -> Postgres serving cutover for chain data (ADR 0013) #4669 is closed referencing this issue as the mechanism that finally executed its "operational, not decode-gap" remaining work.Links/resources
wrangler.jsonc:87-88— the two serving-cutover flags (METAGRAPH_BLOCKS_SOURCE,METAGRAPH_EXTRINSICS_SOURCE).workers/request-handlers/entities.mjs:3414-3430(tryPostgresTier) and its call sites at:3479,3545(blocks) and:3723,3973(extrinsics).workers/data-api.mjs:230-300(extrinsics feed/detail routes,call_args::textcast) and:320-324,368-384(chain_events.args, unconditional, no flag).src/extrinsics.mjs:29,150-171(formatExtrinsic, the shared D1/Postgres JSON-parse path) and:354-367(call_hashLIKE-filter overcall_args).src/mcp-server.mjs:5407(list_extrinsics),:5449(get_extrinsic) — D1-only, notryPostgresTiercall.apps/ui/src/lib/metagraphed/extrinsics.ts— existing client-side normalizers (callArgValue,hashBytesToHex,normalizeIndexerRsCall/asDecodedCall) to port server-side.docs/adr/0013-hybrid-deployment-topology.md§Sequencing (step 4: tier-by-tier serving cutover).b0c554c9— UI rendering fix for indexer-rs's nested-call shape.6eb2c1ae— thechain_events.argsAccountId32-as-raw-array bug, independently hit in production and only partially (client-side) patched; same bug family, separate (unflagged) route.register), 8587451/20 (add_stake), 8587450/20 (transfer_all), 8587346/18 (approve_as_multi), 8587046/19 (commit_weights), 8559935/32 (sudo_set_mechanism_emission_split), 8587390/13 (as_multi), 8587453/9 (Ethereum.transact), 8512299/12 (remark_with_event), 8585337/19 (set_children), 8587431/28 (set_weights), 8513820 (missing-runtime-upgrade-block reliability finding).Priority
High — this is the sole remaining gate before
METAGRAPH_EXTRINSICS_SOURCEcan flip, and the blocks-tier re-check in this same audit round found live-serving reliability and completeness problems that make an ungated flip materially riskier than the blocks precedent suggested.