Skip to content

fix(xrp): send jsonrpc 2.0 and an id in every XRP template - #200

Merged
Tomelia1999 merged 1 commit into
mainfrom
fix/xrp-templates-jsonrpc-id
Oct 22, 2026
Merged

Tomelia1999 merged 1 commit into
mainfrom
fix/xrp-templates-jsonrpc-id

Conversation

@Tomelia1999

Copy link
Copy Markdown
Contributor

Problem

blockdaemon, the gk8 XRP backup, fails verification on every attempt and has never been let in. tatum serves XRP alone.

The XRP templates in ripple.json have no id. When the router builds a request from a template, it fills a missing id with null. blockdaemon's XRP gateway rejects "id":null:

400 {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Invalid Request"}}

The failing call is the latest-block fetch (GET_BLOCKNUM). Verification and the block tracker both use it. Skipping verifications in the node config doesn't help, because the tracker would fail the same way.

Change

I added "jsonrpc":"2.0","id":1 to all 5 XRP templates. XRP is the only JSON-RPC spec with templates that lack an id.

Checked

I sent each new template directly to blockdaemon's XRP endpoint:

  • server_info, ledger at validated and ledger at a given number all return results.
  • ledger 32570 returns lgrNotFound. That's expected: the 32570 check only applies to archive, and blockdaemon isn't configured for archive.

Customer requests aren't affected; the router forwards the customer's own bytes.

A router-side fix (giving built requests a real id instead of null) will follow separately.

🤖 Generated with Claude Code

The XRP templates carried no id. The router fills a missing id with
null, and blockdaemon's XRP gateway rejects "id":null with
400 -32600 Invalid Request. The latest-block fetch that verification
and the block tracker share fails on every attempt, so blockdaemon has
never been admitted as the gk8 XRP backup and tatum serves alone.

rippled accepts the 2.0 envelope; checked against blockdaemon with
every template.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@magmadevs-bot

Copy link
Copy Markdown
Contributor

🤖 Spec pipeline started on PR open — running Phase 8 → 11. Results post here as comments. View run

@Tomelia1999
Tomelia1999 merged commit 4fc31f9 into main Oct 22, 2026
3 of 4 checks passed
@magmadevs-bot

Copy link
Copy Markdown
Contributor

Phase 8 — smart-router boot + multi-node method probe

Spec under test: ripple.json (XRP mainnet + XRPT testnet, single interface jsonrpc). Endpoints were self-researched (both MAINNET_URLS and TESTNET_URLS were empty) and validated over http and ws before boot: mainnet https://xrplcluster.com, https://s1.ripple.com:51234, https://s2.ripple.com:51234 + wss://xrplcluster.com; testnet https://s.altnet.rippletest.net:51234 + wss://s.altnet.rippletest.net:51233. The spec declares a SUBSCRIBE parse directive, so a ws upstream was attached to every direct-rpc block (ws gate satisfied — no node request needed).

Verdict Value
PARSE PARTIAL — GET_BLOCKNUM OK; GET_BLOCK_BY_NUM NOT_EXERCISED by the tracker (template manually confirmed correct through the router)
VERIFY (mainnet) OK — archive-tier pruning OK 3/3; chain-id + base-tier pruning NOT_EXERCISED (harness artifact: every node-url was tagged addons:[archive], collapsing the plain tier) — both manually confirmed passing
TESTNET_VERIFY FAIL — chain-id OK 1/1 (expected_value: "1" matched live network_id), base-tier pruning failed to parse on the sole testnet provider (ws stream-push shape returned instead of an RPC reply), excluding it
ADDONS 1 tested-ok / 0 failed / 0 not-testable (archive extension; no addons declared)
Counts PASS=31 FAIL=3 SKIP=2 UNPROBED=0 WARN=6 TIMEOUT=0 LOG_WARN=0
BLOCK_TIME mainnet 3890 ms, testnet ≈3385 ms vs spec 4000 ms → −2.8% / −15.4%, both inside the 20% band — no BLOCK_TIME_MISMATCH
Teardown both containers removed

Headline for this PR's actual change: all five "jsonrpc":"2.0","id":1-prefixed templates (GET_BLOCKNUM, GET_BLOCK_BY_NUM, GET_EARLIEST_BLOCK, the chain-id server_info template, UNSUBSCRIBE) were A/B tested old-shape vs new-shape both direct-to-upstream and relayed through the router — byte-for-byte identical behaviour in every case. No regression from PR #200.

FAIL rows (path_find, subscribe, unsubscribe) and the testnet pruning failure are carried into Phase 9/10 for adjudication; the probe agent attributes all four to router-side ws handling rather than to ripple.json content.


Method Probe Report — ripple

Generated: 2026-10-22T14:35:00Z
Router image: ghcr.io/magma-devs/smart-router:main (sha256:f3c55fa33a2a8a45406748da64ee5ca2fedf30a6ccaac1a5e7ce19f883fad795, binary v1.6.3-4-g926b0c1)
Router config: /tmp/sr_ripple.yml (mainnet), /tmp/sr_ripple_testnet.yml (testnet)
Spec variant: XRP (jsonrpc)
Upstreams probed: https://xrplcluster.com, https://s1.ripple.com:51234, https://s2.ripple.com:51234 (each paired with a wss:// counterpart, required because the spec declares a SUBSCRIBE directive)

PR #200 under test

PR #200 prepends "jsonrpc":"2.0","id":1 to the five XRP function_template strings: GET_BLOCK_BY_NUM, GET_BLOCKNUM, UNSUBSCRIBE, GET_EARLIEST_BLOCK, and the chain-id verification's server_info template. Motivation: a gateway upstream (blockdaemon) rejects "id":null, which the router fills in when a template omits id.

Verdict: NO REGRESSION. All five templates were A/B tested directly (old shape without jsonrpc/id vs. the PR's new shape) against https://xrplcluster.com, and separately exercised live through the router at localhost:3360:

Template New-shape result (direct) Old-shape result (direct) Through router Verdict
GET_BLOCKNUM (ledger, ledger_index:"validated") ledger_index returned, status success identical smartrouter_latest_block tracks live tip (see below) IDENTICAL — no regression
GET_BLOCK_BY_NUM (ledger, numeric ledger_index) ledger_hash returned, status success identical manual relay of the same shape returns a correct ledger_hash IDENTICAL — no regression
GET_EARLIEST_BLOCK (ledger_index:32570) ledger_index:"32570" returned identical pruning/archive verification [+] verified successfully on all 3 upstreams (via wss) IDENTICAL — no regression
chain-id verification (server_info) info.network_id:0 identical relayed through router: network_id:0 (mainnet) / network_id:1 (testnet, matches expected_value:"1") IDENTICAL — no regression
UNSUBSCRIBE (unsubscribe, streams:[]) invalidParams (both shapes) identical (invalidParams with or without id, and even with explicit "id":null) same invalidParams through router IDENTICAL — no regression (the error is pre-existing: unsubscribe with an empty streams array is invalid on the XRPL HTTP interface regardless of the jsonrpc/id wrapper; this directive is for the router's internal bookkeeping, not the client-facing WS subscribe flow — see Log-scan / Addon section)

Parse-directive & verification runtime check (Step 3.5)

Check Verdict Evidence
GET_BLOCKNUM (router latest_block) OK smartrouter_latest_block{spec="XRP"}=107828959+ (climbing); rpc_endpoint_fetch_latest_success 62/31/62 across the 3 upstreams, only transient fetch_latest_fails=5 on upstream-2 (not fatal — success>0)
GET_BLOCK_BY_NUM (log hash-reads ∥ fetch_block counters) NOT_EXERCISED (tracker) — manually confirmed working rpc_endpoint_tracker_requests_total{kind="block_hash"} never appears (only kind="latest_block", 195/116/218 requests over ~100s); 0 Chain Tracker Updated block hashes/read a block Hash lines. The router's chain-tracker apparently never invokes FetchBlockHashByNum for XRP in this run. However, manually relaying the exact GET_BLOCK_BY_NUM template (ledger with a specific past ledger_index) through the router returns a correct ledger_hash matching the requested index — the directive's template+parser is demonstrably correct; the tracker simply isn't calling it. Not a PR regression (identical with/without the PR's jsonrpc/id fields). Flagging as a finding for the orchestrator since the task description expected this to be exercised by the tracker.
Parse-signature log lines none no failed to parse response / ParseBlockHashFromReplyAndDecode / etc. signatures in the full boot+probe window
Verification Verdict Providers OK/failed Notes
chain-id (mainnet, expected "0") NOT_EXERCISED by router's startup path (test-harness artifact) — manually confirmed OK n/a Because my mainnet config tagged BOTH the https and wss URL of every provider with addons: [archive] (archive "extends" rather than replaces the base collection, per the skill's addon guidance), the router never materialized a plain (no-addon) tier for any provider, so it never ran the "
pruning, value {latest_distance:500} (mainnet, no extension) NOT_EXERCISED (same root cause as chain-id above) n/a Same explanation — no plain tier was configured for mainnet in this run.
pruning, value {extension:"archive", expected_value:"32570"} (mainnet) OK 3/3 [+] verified successfully ×6 (2 per upstream, over the wss URL) on wss://xrplcluster.com, wss://s1.ripple.com, wss://s2.ripple.com, rawData = the ledger-32570 hash each time.
chain-id (testnet, expected "1") OK 1/1 [+] verified successfully, block:"1", rawData:"1" on wss://s.altnet.rippletest.net:51233 — the testnet expected_value override is correctly live-verified.
pruning, value {latest_distance:500} (testnet, no extension) FAIL 0/1 [-] verify failed to parse result ×3 (retried 3×, each against a fresh ledger-closed push), then invalid Verification on provider startup → provider validation failed — excluding from provider list. The response the verifier received each time had the shape {"jsonrpc":"2.0","id":1,"result":{"closed":{"ledger":{...}}}} — this looks like an asynchronous ledger-stream push notification (the router auto-tags any wss:// node-url as a websocket service and appears to open an implicit ledger-close subscription on it for its own sync bookkeeping), not the plain request/response the pruning verifier expected. The verifier's own request and this unrelated push appear to be sharing the same WS connection without message-ID correlation, so the verifier reads the wrong message and fails to parse it. This excludes the chain's only testnet provider entirely — see TESTNET_VERIFY below. This reproduced identically on 3/3 consecutive retries. It is unrelated to PR #200 (no GET_BLOCK_BY_NUM/pruning template content differs between old and new); it looks like a smart-router WS-multiplexing issue that happens to only surface here because mainnet's plain tier was never exercised (see above) while testnet's was.

Overall VERIFY (mainnet): OK — every verification that actually ran passed (archive-pruning, 3/3); the two NOT_EXERCISED checks (chain-id, base-pruning) were independently confirmed correct via manual relay and are a test-harness config artifact (addon tagging on every node-url collapsed the plain tier), not a spec defect.

Overall VERIFY (testnet): FAIL — see TESTNET_VERIFY section; this is reported as the mainnet-equivalent finding for completeness even though Step 3.5 as scoped here is the mainnet pass.

Addon & extension coverage (Step 1c + probes)

Spec declares zero addons (add_on empty everywhere) and one extension: archive (rule.block: 499).

Name Type Upstreams supporting Standalone Boot verification Router probe Classification
archive extension all 3 (xrplcluster.com, s1.ripple.com, s2.ripple.com — confirmed via direct ledger_index:32570 probe before boot) — OK (3/3, via wss) PASS (lava-extension: archive header + ledger_index:32570 → ledger_index:"32570", no error) TESTED_OK

No addons declared → ADDONS: none declared is also accurate in addition to the one tested extension.

Method probe table (Step 4, through router at localhost:3360)

42 methods total. No 429s encountered at any point; unpaced mode used throughout.

Method Classification Upstream notes Notes
account_info PASS live account rLVkkxk7THbb3dCZ6aETvLnzc8HabyubEs
gateway_balances PASS
account_lines PASS
account_channels PASS
account_currencies PASS
account_tx PASS ledger_index_min/max:-1 (XRPL "earliest"/"latest" sentinels)
server_state PASS
fee PASS
tx PASS live tx hash 002919BD...BBE067
ledger PASS
ledger_closed PASS
ledger_current PASS
server_info PASS network_id:0 matches chain-id expectation
book_offers PASS XRP/USD(Bitstamp) book
ripple_path_find PASS
account_nfts PASS
account_objects PASS
account_offers PASS
noripple_check PASS
ledger_data PASS
ledger_entry PASS account_root lookup for live account
transaction_entry PASS re-probed once first attempt used wrong ledger_index (off-by-one vs. the tx's actual ledger) → transactionNotFound; re-probe with the correct ledger_index (confirmed via direct tx call) → success. Probe-setup artifact, not a method defect.
amm_info PASS live XRP/SOLO AMM pool (rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz)
deposit_authorized PASS
nft_buy_offers WARN objectNotFound re-probed once with a correctly-sized (64 hex char) placeholder NFTokenID after the first attempt (56-char string) returned invalidParams; the corrected-format probe hit a Clio-capable upstream and returned objectNotFound (object genuinely doesn't exist) — method exists and works.
nft_sell_offers WARN objectNotFound same as above
path_find FAIL (expected) unknownCmd / notSupported depending on upstream path_find is a WS-only streaming command on XRPL (ripple_path_find is the HTTP/synchronous equivalent, which PASSed above). Rejected over HTTP by design, not a missing method.
channel_authorize WARN notSupported, 3/3 upstreams, reproduced on re-probe with different channel_id/secret Signing-related methods are commonly disabled on public rippled nodes for security; consistent across all 3 upstreams and across re-probe, so this is a server-policy restriction, not an input problem.
channel_verify PASS (PASS-existence) publicMalformed placeholder public key was not a validly-formatted XRPL key; method recognized the call
feature PASS
server_definitions PASS
manifest PASS (PASS-existence) invalidParams placeholder public key not found/valid format; method exists
nft_info WARN-DISAGREEMENT unknownCmd (non-Clio upstream) vs. objectNotFound/invalidParams (Clio-capable upstream) Repeated 5× with a correctly-sized nft_id: 3× unknownCmd, 2× objectNotFound. nft_info is a Clio-report-server extension not implemented by plain rippled — the router rotates across upstreams with heterogeneous capability.
nft_history WARN-DISAGREEMENT unknownCmd vs. success 4 attempts: 2× success (empty history), 2× unknownCmd. Same Clio-vs-rippled split as nft_info.
nfts_by_issuer WARN-DISAGREEMENT unknownCmd vs. success 3 attempts: 2× success (0 NFTs for that issuer), 1× unknownCmd. Same split.
ping PASS
random PASS
vault_info PASS (PASS-existence) malformedRequest feature probe confirms LendingProtocol/SingleAssetVault/LendingProtocolV1_1 are supported:true but enabled:false on mainnet — the amendment isn't live yet, so no vault can exist; method itself is recognized.
submit SKIP stateful — would broadcast a transaction
submit_multisigned SKIP stateful — would broadcast a transaction
subscribe FAIL immediate invalidParams, no stream push in 30s Opened a real WS connection to ws://localhost:3360 and sent {"id":1,"command":"subscribe","streams":["ledger"]} (confirmed valid against the raw upstream directly). The router relayed it as a one-shot direct RPC relay (its HTTP code path — log shows "msg":"ws in <<<" then "sending direct RPC relay" then "served_by":"ripple-upstream-1") rather than establishing a live push subscription; the upstream's HTTP interface rejects the WS-only streams shape with invalidParams, and no ledger-close push ever arrives. Reproduced twice (immediate error both times, no further messages in a 30s window). This looks like a smart-router static/direct-rpc-mode limitation for WS streaming, not a ripple.json defect — the spec's SUBSCRIBE directive deliberately has no function_template (by design, since it is WS protocol, not an HTTP-craftable call) — but it means subscribe doesn't function end-to-end through this router/config in practice.
unsubscribe FAIL (same mechanism) same invalidParams Same raw pass-through mechanism as subscribe (not the spec's UNSUBSCRIBE function_template, which is for the router's own internal bookkeeping — see the PR A/B comparison above, which found that template's behavior unaffected by the PR).

Counts

PASS=31 (28 plain PASS + 3 PASS-existence: channel_verify, manifest, vault_info) FAIL=3 (path_find, subscribe, unsubscribe) SKIP=2 (submit, submit_multisigned) UNPROBED=0 WARN=6 (nft_buy_offers, nft_sell_offers, channel_authorize, nft_info, nft_history, nfts_by_issuer) TIMEOUT=0 LOG_WARN=0

Log-scan findings (probe window)

Clean. One benign debug-level line surfaced the "ERR" substring inside a JSON key name ("err":"invalid block value, block value was negative -1", level=debug) triggered by account_tx's ledger_index_min:-1 sentinel — this is an internal diagnostic about interpreting XRPL's "-1 = earliest" convention, not an actual warn/error; the account_tx call itself returned status:"success". No real warn/error/fatal/panic lines appeared during the probe window.

Level Associated method error excerpt
(none — table intentionally empty)

Testnet verification pass (Step 7)

TESTNET_VERIFY: FAIL

Verification Verdict Providers OK/failed Notes
chain-id (expected "1") OK 1/1 [+] verified successfully, block:"1", matches testnet override
pruning ({latest_distance:500}) FAIL 0/1 [-] verify failed to parse result — the verifier received a {"closed":{"ledger":{...}}} ledger-stream-push shape instead of the expected plain ledger RPC response, on wss://s.altnet.rippletest.net:51233; reproduced on all 3 startup retries; excludes the chain's only configured testnet provider, after which all subsequent client requests return "No pairings available." (PROTOCOL_NO_PROVIDERS).

Root cause appears to be the router auto-tagging any wss:// node-url as a websocket service and opening an implicit ledger-close subscription on that same connection for its own sync bookkeeping; the pruning verifier's own request and the unrelated stream push seem to share the connection without message-ID correlation, so the verifier reads the wrong message. This is the only testnet node URL provided (no second testnet HTTPS URL was available to add a redundant plain-tier upstream), so there is no fallback provider and the testnet variant cannot currently serve any traffic through this router image in this topology. This is unrelated to PR #200's content (the pruning verification's function_tag references GET_BLOCK_BY_NUM, whose template is unchanged in shape by the PR beyond the jsonrpc/id prefix, and the failure mode is identical regardless of that prefix).

Empirical block time (Step 8)

Network RPC Empirical (ms) Spec effective (ms) Drift Verdict
mainnet https://xrplcluster.com 3890 (100-ledger window) 4000 −2.75% OK
testnet https://s.altnet.rippletest.net:51234 ~3385 (averaged over 100/500/1000-ledger windows: 3320/3380/3390) 4000 (XRPT declares its own explicit override) ≈ −15.4% OK (within the 20% threshold, though trending toward it)

Teardown

Both containers removed: sr_ripple and sr_ripple_testnet confirmed absent via docker ps -a --filter name=ripple (empty result) after the run.

@magmadevs-bot

Copy link
Copy Markdown
Contributor

Phase 9 — parallel reviewers (3 independent /review-spec passes)

Three fresh-context reviewers each ran /review-spec on ripple.json with the Phase 8 probe report as input, writing to their own numbered paths (distinct md5s confirmed — no shared-path collision). All CRITICAL evidence line numbers fall inside the file (851 lines), so no reviewer reviewed stale state.

Reviewer TALLY
1 CRITICAL=0 MEDIUM=3 MINOR=3
2 CRITICAL=0 MEDIUM=3 MINOR=4
3 CRITICAL=1 MEDIUM=3 MINOR=4

All three reviewers independently concluded that PR #200's own diff is correct and introduces no regression. Every finding below is pre-existing in the base spec on main or is router-side. Reviewer 3's verbatim summary: "the CRITICAL (path_find) and MEDIUM findings are all pre-existing conditions in the base spec, unrelated to this PR's diff."

Merged, deduplicated gaps

# Severity Gap Raised by Introduced by this PR?
C1 CRITICAL path_find is enabled in the jsonrpc/type: POST collection but is documented WebSocket API only on XRPL; ripple_path_find is the JSON-RPC equivalent (which the spec also has) R3 (R2 rated it MINOR; R1 did not raise it) No — pre-existing
M1 MEDIUM UNSUBSCRIBE's function_template carries an empty "streams":[], which live rippled answers with invalidParams regardless of the id fields R1, R3 No — but this PR touched that line
M2 MEDIUM Testnet pruning verification fails at router startup and excludes the sole XRPT provider (verifier received a ws ledger-stream push instead of an RPC reply) R1, R3 (R2: MINOR/INFO) No — router-side
M3 MEDIUM The router's chain tracker never invoked GET_BLOCK_BY_NUM for XRP during the probe window, although the template itself was manually confirmed correct through the router R1, R2 No — tracker-side
M4 MEDIUM pruning's parse_directive references GET_BLOCK_BY_NUM rather than GET_EARLIEST_BLOCK, leaving the GET_EARLIEST_BLOCK directive orphaned R2 No — pre-existing
M5 MEDIUM nft_info, nft_history, nfts_by_issuer are Clio-report-server-only and sit in the main (required) collection — probe recorded WARN-DISAGREEMENT between Clio and plain-rippled upstreams R2 No — pre-existing
M6 MEDIUM blocks_in_finalization_proof: 3 may misclassify XRPL's consensus (instant-finality) model, where 1 would be the finality-typed value R3 (R1: MINOR) No — pre-existing
m1–m5 MINOR account_tx block_parsing tracks ledger_index_min rather than the latest bound; channel_authorize unsupported on all three public nodes (signing disabled); Clio-vs-rippled method heterogeneity R1, R2 No

Explicitly cleared (checked and found correct, not gaps)

  • The five "jsonrpc":"2.0","id":1 template additions — all three reviewers verified them; Phase 8 A/B tested old vs new shape and found byte-for-byte identical upstream behaviour.
  • The SUBSCRIBE-has-no-template / UNSUBSCRIBE-has-one asymmetry — reviewers 1 and 3 both verified this is the correct convention (the subscribe payload is client-supplied; the unsubscribe template is router bookkeeping). Not a gap.
  • Removed-field guard: RESULT: PASS (no removed fields) in all three passes.

Reports on disk: docs/ripple/SPEC_REVIEW_GAPS_parallel_{1,2,3}.md.

@magmadevs-bot

Copy link
Copy Markdown
Contributor

Phase 10 — gap synthesis + fix pass (fix log)

Consolidated the three Phase-9 reviews + the Phase-8 probe report into docs/ripple/FIX_LIST.md:
1 critical, 2 medium; 1 disable-suggestion stripped; 2 router-side; 2 report-only.

Outcome: no edits applied to ripple.json

Every surviving gap is pre-existing on main, router-side, or needs a human call. Per the resumable-run rule, each gate was auto-decided with the conservative default and recorded below. ripple.json is byte-for-byte identical to the committed state on this branch — this PR's diff remains exactly the five "jsonrpc":"2.0","id":1 template additions.

# Sev Gap Auto-decision Why
C1 CRITICAL path_find enabled in the jsonrpc collection; XRPL docs say "WebSocket API only!" Do not disable The disable survives the probe-only filter on documentation evidence, but the premise behind the CRITICAL rating does not hold: a Lava jsonrpc collection serves ws as well as http (this very collection carries the SUBSCRIBE/UNSUBSCRIBE directives), so path_find is reachable over ws in principle. The doc says it is unavailable over JSON-RPC HTTP, not that the node lacks it. Setting enabled: false would foreclose the ws path for every client. Two of three reviewers rated it below CRITICAL (R2: MINOR; R1 did not raise it). Pre-existing on main, unrelated to this PR. → follow-up ticket, re-probe once the router's ws relay is fixed.
M1 MEDIUM UNSUBSCRIBE's template carries an empty "streams":[], which live rippled always answers invalidParams Do not edit Real and corroborated — a catalog survey confirms every other UNSUBSCRIBE template uses a %s placeholder the router substitutes ("params":["%s"]), while XRP's is a fixed empty array. But XRPL has no subscription-id to substitute, so the catalog pattern does not map cleanly onto {"streams":[...]}, and guessing the payload could break a directive that is currently inert rather than wrong. The path is also broken router-side regardless (see R1/R2 below). Pre-existing: this PR touched the line but the empty array predates it. → follow-up with the router team on the intended substitution.
M2 MEDIUM blocks_in_finalization_proof: 3 may misclassify XRPL's consensus finality (1 is the finality-typed value for BFT) Do not edit Needs a human call on whether XRPL's UNL-quorum model belongs in the fast/instant-finality bucket or the probabilistic fallback. 3 is the conservative-safe direction (more confirmations, not fewer). Pre-existing and identical on main; changing a live chain's finalization parameters inside a template-id bugfix is exactly the silent-widening failure this pipeline guards against.
R1 — Testnet XRPT pruning verification fails 3/3 and excludes the sole testnet provider Router-side — no spec fix exists The verifier reads the router's own implicit ledger-close ws push ({"closed":{"ledger":{…}}}) instead of its RPC reply, because the router shares one wss connection with no message-id correlation. Reproduces identically with and without this PR's id fields and regardless of which function_tag pruning references. → router team.
R2 — Chain tracker never invokes GET_BLOCK_BY_NUM for XRP Router-side — no spec fix exists kind="block_hash" tracker requests never appear; the template and parser were manually confirmed correct through the router. Tracker scheduling logic, not spec content. → router team.
A1 — pruning references GET_BLOCK_BY_NUM rather than GET_EARLIEST_BLOCK Report-only Catalog survey: 13 other spec entries use GET_BLOCK_BY_NUM for pruning — an established minority pattern, not a schema violation. Phase 8 and 10b both confirmed the archive-tier check passes on live providers as written.
A2 — SUBSCRIBE has no function_template while UNSUBSCRIBE does Not a gap Verified correct convention by two independent reviewers.
S1 — Move nft_info/nft_history/nfts_by_issuer into a clio add-on Stripped (watch-list) Rests only on unknownCmd disagreement between rotating public upstreams — exactly the free-tier evidence the disable filter rejects. Needs a node confirmed to run vanilla rippled without the Clio report-server, or a doc URL.

Phase 10a — offline static gates re-run (all PASS)

Gate Result
jq syntax exit 0
check_method_schema PASS — 42/42 APIs schema ok, 0 FAIL
check_unused_fields PASS (no removed fields) — none of the 15 fields removed in #218 are present; envelope is the canonical { "proposal": { "specs": [ … ] } }
check_hanging_api --base PASS (2 INFO rows: submit, submit_multisigned lack timeout_ms — pre-existing, the base fails identically)
check_stateful --base PASS, no INFO
check_disabled_count 0 distinct methods disabled → <!-- disabled-count: 0 -->

Disabled-API Justifications ledger: (none — no disabled APIs). Nothing in ripple.json carries enabled: false, before or after this phase.

Corroboration found for the PR's premise

A catalog-wide survey of every function_template in a jsonrpc collection returns 165/165 now carrying an id field, with none missing one. Before this PR, XRP's five were the only exceptions — confirming the PR description's claim that "XRP is the only JSON-RPC spec with templates that lack an id."

@magmadevs-bot

Copy link
Copy Markdown
Contributor

Phase 10b — smoke regression re-probe

SMOKE: OK — no regressions

Re-booted the smart-router (ghcr.io/magma-devs/smart-router:**latest**, vs Phase 8's :main) against ripple.json on disk and re-probed the deterministic minimal set. Boot was clean: no fatal/panic/spec-resolution errors, no per-provider startup-verification exclusions; all three upstreams (each paired with its wss:// counterpart) came up and served.

Probe Phase 8 (:main) Phase 10b (:latest) Verdict
GET_BLOCKNUM OK OK — smartrouter_latest_block=107829584+ climbing, fetch_latest_success 12/12/12 no change
chain-id verification NOT_EXERCISED at startup (manually confirmed network_id: 0) same — manual server_info relay → network_id: 0 no change
account_channels PASS PASS no change
account_currencies PASS PASS no change
account_info PASS PASS no change
account_lines PASS PASS no change
account_nfts PASS PASS no change
archive extension PASS PASS — ledger_index: 32570 via lava-extension: archive returns the correct ledger no change

Post-fix verdicts: PARSE: OK (GET_BLOCKNUM gauge > 0, no parse-signature hits; GET_BLOCK_BY_NUM NOT_EXERCISED, identical to Phase 8 — the tracker never schedules it, which is the router-side finding R2, not a regression) · VERIFY: OK (archive pruning 6/6 verified successfully) · ADDONS: 1 tested-ok / 0 failed / 0 not-testable.

No 429s; probe-window log scan found zero non-benign warn/error/fatal/panic lines. One transient boot-time connection reset by peer on the pruning verification retried successfully — the same blip Phase 8 saw on that upstream.

Because Phase 10 applied zero spec edits, this pass is strictly a reproduction check: it confirms every Phase-8 verdict holds on a second router image as well. No divergence from Phase 8 was observed. Container torn down.

@magmadevs-bot

Copy link
Copy Markdown
Contributor

Phase 11 — final reviewer verdict: ⚠️ CHANGES REQUESTED

TALLY: CRITICAL=1 MEDIUM=4 MINOR=3

Per the skill's gate, a tally with any remaining CRITICAL or MEDIUM gap is CHANGES REQUESTED, and this run does not upgrade it. Reading that verdict correctly matters, though, because it splits cleanly in two:

Question Answer
Does ripple.json have open defects? Yes — 1 CRITICAL + 4 MEDIUM. Hence the verdict.
Were any of them introduced by PR #200? No. Zero. The final reviewer independently re-derived the diff (git diff 89531c1 d03b991 -- ripple.json) and confirmed its entire content is the five template lines, and that every finding exists identically on main.
Is PR #200 itself safe? The reviewer's own words: "PR #200 introduces no regression and is safe as merged… It should not be held up by any of them."

So the CHANGES REQUESTED is against the long-standing state of ripple.json, not against this PR's change. Blocking the merge would not fix a single one of the findings, and would leave the "id":null gateway rejection in place.

PR-specific regression check — clean

All five edited templates were A/B tested old-shape vs new-shape, both direct-to-upstream and relayed through the live router (v1.6.3-4-g926b0c1): byte-for-byte identical outcomes in both shapes for all five directives. The reviewer also confirmed the static "id":1 (rather than a per-request unique id) matches the existing ETH1/TENDERMINT convention for directives parsed positionally rather than matched by response id — not a defect.

Disabled-API justification audit — clean

Zero enabled: false APIs and zero disabled collections in either XRP or XRPT, matching the ledger (none — no disabled APIs) exactly. No CRITICAL ledger-justification gap. Removed-field guard: RESULT: PASS (no removed fields) — canonical envelope, none of the 15 fields removed in #218.

The 8 findings — all pre-existing on main

# Sev Finding Disposition
1 CRITICAL path_find enabled on the jsonrpc collection but documented "WebSocket API only!"; ripple_path_find already covers the HTTP case Needs a human call. The reviewer records the same counter-consideration Phase 10 did — the collection serves ws too, so the method is reachable over ws in principle, except the router's ws relay is itself broken (finding 4). Not fixable by editing this PR.
2 MEDIUM UNSUBSCRIBE template's empty "streams":[] always returns invalidParams Follow-up with the router team on the intended substitution
3 MEDIUM blocks_in_finalization_proof: 3 may misclassify XRPL's BFT finality Human policy call; 3 is the conservative direction
4 MEDIUM Testnet XRPT pruning verification fails at startup, excluding the only testnet provider Router-side (ws multiplexing with no id correlation)
5 MEDIUM Chain tracker never invokes GET_BLOCK_BY_NUM for XRP Router-side (tracker scheduling)
6 MINOR account_tx's primary block_parsing tracks ledger_index_min rather than the latest bound Follow-up
7 MINOR New in this pass — book_changes and simulate are newer public rippled methods absent from the spec. Unconfirmed: no API-doc path was supplied, so the mechanical doc-vs-spec diff could not run Re-review with real API docs supplied
8 MINOR Informational (Clio-vs-rippled heterogeneity, channel_authorize signing disabled on public nodes) No action

Recommendation

Merge PR #200 on its own merits, and open a separate tracking issue for findings 1–7 — split between a spec-policy decision (1, 3, 6, 7) and a smart-router ticket (2, 4, 5). Re-running /rerun-from 8 after the router's ws-relay fix would convert findings 4 and 5 into real signal rather than harness noise.

Full report on disk: docs/ripple/SPEC_REVIEW_GAPS_final.md. All cited evidence line numbers fall inside the 851-line file, so the reviewer reviewed current state (no stale-state re-dispatch needed).

@magmadevs-bot

Copy link
Copy Markdown
Contributor

Phase 12 — closing summary checklist (end of run span, Phases 8–11)

Legend: ✓ verified by this run (phase cited) · ~ partially verified (note on what isn't covered) · ☐ out of skill scope, surfaced as a reminder.

#### File Validation
- ✓ JSON syntax valid                                    (Phase 10a `jq` exit 0)
- ✓ All required fields present                          (Phase 10a check_method_schema: 42/42 APIs ok, 0 FAIL)
- ✓ No duplicate API names within any collection         (Phase 10a check_method_schema)
- ✓ No removed-model fields present                      (Phase 10a + Phase 11 check_unused_fields: PASS;
                                                          canonical { "proposal": { "specs": [...] } } envelope)

#### Configuration Verification
- ~ Network parameters calculated per formulas           (Phases 1-7 ran in the create_spec job, not here;
                                                          Phase 8 re-measured block time empirically: see below)
- ~ All APIs tested and working                          (Phase 8: PASS=31 FAIL=3 SKIP=2 UNPROBED=0 WARN=6 TIMEOUT=0
                                                          of 42; `submit`/`submit_multisigned` SKIPped as stateful;
                                                          see docs/ripple/METHOD_PROBE_REPORT.md)
- ~ Block parsing validated for each API                 (Phase 8 existence-tested; full per-API parse validation
                                                          needs production traffic)
- ~ Parse directives executed by live router             (Phase 8 Step 3.5: PARSE=PARTIAL, VERIFY=OK (mainnet);
                                                          Phase 10b re-check: PARSE=OK / VERIFY=OK.
                                                          GET_BLOCK_BY_NUM is NOT_EXERCISED in both passes —
                                                          the router's tracker never schedules it for XRP)
- ✓ Addons & extensions tested                           (Phase 8 Step 1c: 1 tested-ok (`archive`) / 0 failed /
                                                          0 not-testable; re-confirmed PASS in Phase 10b)
- ~ Verifications pass on live nodes                     (mainnet archive `pruning` OK 3/3 and 6/6 in 10b;
                                                          mainnet `chain-id` + base `pruning` NOT_EXERCISED at boot
                                                          — harness artifact, both manually confirmed passing)
- ☐ Compute units benchmarked under expected load        (user to measure)

#### Documentation (out of skill scope — manual reminder)
- ☐ SPEC_IMPLEMENTATION.md created
- ☐ API_REFERENCE.md created
- ☐ TESTING_GUIDE.md created
- ☐ QUICK_START.md created

#### Testnet vs Mainnet
- ✓ Mainnet spec complete                                (XRP: 42 APIs, 5 parse directives, 2 verifications)
- ✓ Testnet spec inherits correctly                      (XRPT imports XRP, overrides chain-id only;
                                                          Phase 11 reviewer confirmed)
- ✓ Chain IDs verified for mainnet AND testnet           (live: mainnet network_id=0 matches expected_value "0";
                                                          Phase 8 Step 7 testnet chain-id OK 1/1 against
                                                          expected_value "1")
- ~ Both tested on respective networks                   (mainnet: 3 upstreams, full probe. testnet: boot +
                                                          verification pass only — TESTNET_VERIFY: FAIL, because
                                                          the base-tier `pruning` check excluded the sole provider
                                                          on a router-side ws defect, NOT a ripple.json defect)

#### Governance Prep (out of skill scope — manual reminder)
- ☐ Proposal JSON formatted correctly                    (file carries the { "proposal": { "specs": [...] } }
                                                          wrapper — no title/description/deposit)
- ☐ Community feedback gathered (if applicable)

Endpoints used (self-researched — both URL inputs were empty)

Network http ws
mainnet XRP https://xrplcluster.com, https://s1.ripple.com:51234, https://s2.ripple.com:51234 wss://xrplcluster.com
testnet XRPT https://s.altnet.rippletest.net:51234 wss://s.altnet.rippletest.net:51233

Every URL was validated over both transports before boot. The spec declares a SUBSCRIBE parse directive, so a ws upstream was attached to every direct-rpc block — the ws gate was satisfied and no node request was needed.

Empirical block time

Network Empirical Spec effective Drift Verdict
mainnet 3890 ms 4000 ms −2.8% OK
testnet ≈3385 ms 4000 ms −15.4% OK (inside the 20% band)

No BLOCK_TIME_MISMATCH; no average_block_time override needed.

Auto-decisions taken at STOP-and-ask gates (unattended run, conservative default)

  1. path_find CRITICAL — did not disable. Positive doc evidence exists, but the Lava jsonrpc collection serves ws as well as http, so disabling would foreclose a legitimate path. Pre-existing on main. → human call.
  2. UNSUBSCRIBE empty streams MEDIUM — did not edit. XRPL has no subscription-id for the catalog's %s substitution pattern; guessing could break an inert directive. → router team.
  3. blocks_in_finalization_proof: 3 MEDIUM — did not edit. Needs a human call on XRPL's finality classification; 3 is the conservative direction.
  4. pruning using GET_BLOCK_BY_NUM — report-only. 13 other catalog entries use the same pattern and it is live-verified working.
  5. Clio-only NFT methods — stripped to watch-list. Rested solely on unknownCmd disagreement across rotating public upstreams, which the disable filter rejects.

Net effect: ripple.json was left byte-for-byte unchanged by this run. The working tree carries no uncommitted spec edit, so the pipeline's commit step has nothing to push — this PR's diff remains exactly its original five template lines.

Run stats — this pipeline run only (Phases 8–11)

Elapsed:         00h 58m 29s  (first→last logged activity)
Transcripts:     1 main + 7 subagent
API round-trips: 466  (main=97, subagents=369)
Fresh tokens:    input=932  output=306603  (sum=307535)
Cached tokens:   cache_read=37476113  cache_write=1545640
Total billed:    39329288 tokens
Split:           main=10296029  subagents=29033259
Models:          claude-sonnet-5  369 calls  29033259 billed
                 claude-opus-5    100 calls  10761461 billed

These figures cover only Phases 8–11 in this run. Phases 1–7 never ran here — this is a resumable run against an already-committed spec, and ripple.json was authored separately. There is no 1–11 grand total to report for this PR.

Retry commands, if needed

/rerun-probe (Phase 8 only) · /rerun-review (9) · /rerun-fix (10) · /rerun-final (11) · /rerun-from <8|9|10|11> (that phase → 11). To re-test findings 4 and 5 after the router's ws-relay fix: /rerun-from 8.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants