From efc98179092280e72c3159ab137f722763c38ddd Mon Sep 17 00:00:00 2001 From: DROOdotFOO Date: Fri, 9 Oct 2026 03:50:01 +0200 Subject: [PATCH 1/5] Pin raxol_payments parity to @riddler/spec Conformance vectors and the vendored fee oracle are now read from the published @riddler/spec release pinned in test/fixtures/RIDDLER_SPEC_VERSION, never a sibling checkout. Under CI a missing artifact fails instead of passing over zero vectors. CI stays red until @riddler/spec@0.8.0 is published to npm. --- .github/workflows/ci-unified.yml | 25 +++++ .../test/fixtures/RIDDLER_SPEC_VERSION | 1 + .../test/raxol/payments/fee_schedule_test.exs | 36 +++++- .../test/support/conformance_fixture.ex | 103 +++++++++--------- .../test/support/riddler_spec.ex | 40 +++++++ packages/raxol_payments/test/test_helper.exs | 10 +- 6 files changed, 154 insertions(+), 61 deletions(-) create mode 100644 packages/raxol_payments/test/fixtures/RIDDLER_SPEC_VERSION create mode 100644 packages/raxol_payments/test/support/riddler_spec.ex diff --git a/.github/workflows/ci-unified.yml b/.github/workflows/ci-unified.yml index e55c25c5f..8684bd8b0 100644 --- a/.github/workflows/ci-unified.yml +++ b/.github/workflows/ci-unified.yml @@ -575,6 +575,31 @@ jobs: sudo apt-get update sudo apt-get install -y tmux + # raxol_payments' EIP-712 conformance vectors and its vendored fee oracle + # (priv/fee-oracle/schedule.json) are held to ONE published @riddler/spec + # release, pinned in test/fixtures/RIDDLER_SPEC_VERSION -- never a sibling + # checkout. Under CI both suites fail when these variables are unset, so a + # broken fetch is a red cell rather than a green run over zero vectors. + - name: Set up Node (pinned @riddler/spec) + if: matrix.package == 'raxol_payments' + uses: actions/setup-node@v7 + with: + node-version: '22' + + - name: Fetch pinned @riddler/spec + if: matrix.package == 'raxol_payments' + working-directory: packages/raxol_payments + run: | + version="$(cat test/fixtures/RIDDLER_SPEC_VERSION)" + dest="$RUNNER_TEMP/riddler-spec" + mkdir -p "$dest" + npm pack "@riddler/spec@${version}" --pack-destination "$dest" + tar -xzf "$dest/riddler-spec-${version}.tgz" -C "$dest" + test -f "$dest/package/conformance/conformance.json" + test -f "$dest/package/fee/schedule.json" + echo "RIDDLER_SPEC_DIR=$dest/package" >> "$GITHUB_ENV" + echo "CONFORMANCE_FIXTURE_PATH=$dest/package/conformance/conformance.json" >> "$GITHUB_ENV" + # `--check-locked` rather than a bare `deps.get`: a plain fetch REWRITES # a stale mix.lock in place and carries on, so a package whose committed # lock does not match what resolution produces builds green in CI and diff --git a/packages/raxol_payments/test/fixtures/RIDDLER_SPEC_VERSION b/packages/raxol_payments/test/fixtures/RIDDLER_SPEC_VERSION new file mode 100644 index 000000000..a3df0a695 --- /dev/null +++ b/packages/raxol_payments/test/fixtures/RIDDLER_SPEC_VERSION @@ -0,0 +1 @@ +0.8.0 diff --git a/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs b/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs index e42e72779..a801ea3ed 100644 --- a/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs +++ b/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs @@ -11,11 +11,10 @@ defmodule Raxol.Payments.FeeScheduleTest do use ExUnit.Case, async: true alias Raxol.Payments.FeeSchedule + alias Raxol.Payments.Test.RiddlerSpec - @oracle "fee-oracle/schedule.json" - |> then(&Path.join(:code.priv_dir(:raxol_payments), &1)) - |> File.read!() - |> Jason.decode!() + @oracle_path Path.join(:code.priv_dir(:raxol_payments), "fee-oracle/schedule.json") + @oracle @oracle_path |> File.read!() |> Jason.decode!() defp tier_atom(name), do: String.to_existing_atom(name) @@ -71,6 +70,35 @@ defmodule Raxol.Payments.FeeScheduleTest do end end + # The oracle is a vendored copy, so the parity above only proves this module + # agrees with the copy. This proves the copy is the pinned release's bytes. + describe "vendored oracle vs the pinned @riddler/spec" do + if is_nil(RiddlerSpec.dir()) and not RiddlerSpec.ci?() do + @tag skip: "RIDDLER_SPEC_DIR unset (the unpacked @riddler/spec tarball root)" + end + + test "priv/fee-oracle/schedule.json is byte-identical to fee/schedule.json" do + version = RiddlerSpec.pinned_version() + + dir = + RiddlerSpec.dir() || + flunk( + "RIDDLER_SPEC_DIR is unset under CI. Unpack npm pack " <> + "@riddler/spec@#{version} and set it to the tarball's package/ directory." + ) + + manifest = dir |> Path.join("package.json") |> File.read!() |> Jason.decode!() + + assert {manifest["name"], manifest["version"]} == {"@riddler/spec", version}, + "RIDDLER_SPEC_DIR=#{dir} is not @riddler/spec@#{version} " <> + "(test/fixtures/RIDDLER_SPEC_VERSION)" + + assert File.read!(Path.join([dir, "fee", "schedule.json"])) == File.read!(@oracle_path), + "priv/fee-oracle/schedule.json drifted from @riddler/spec@#{version} " <> + "fee/schedule.json; copy the release's file over it" + end + end + describe "headline rates" do test "are the sum of the three layers" do # The published totals: stable 22/19/15/12/10, volatile 40/35/29/25/22. diff --git a/packages/raxol_payments/test/support/conformance_fixture.ex b/packages/raxol_payments/test/support/conformance_fixture.ex index d3303f8c0..055971556 100644 --- a/packages/raxol_payments/test/support/conformance_fixture.ex +++ b/packages/raxol_payments/test/support/conformance_fixture.ex @@ -2,23 +2,26 @@ defmodule Raxol.Payments.Test.ConformanceFixture do @moduledoc """ Loader for the shared EIP-712 conformance fixture. - The fixture file lives in the `riddler-client` monorepo at - `packages/spec/conformance/conformance.json` (the `@riddler/spec` - package) and is the byte-equality contract between the Node SDK and - `raxol_payments`. It is generated by `npm run generate:conformance` - in the CLI repo. + The fixture is `conformance/conformance.json` in the published + `@riddler/spec` package (axol-io/riddler-sdk `packages/spec`), the + byte-equality contract between the Node SDK and `raxol_payments`. It is read + from the release pinned in `test/fixtures/RIDDLER_SPEC_VERSION` (see + `Raxol.Payments.Test.RiddlerSpec`), located by exactly one variable: - Resolves the fixture path via: + CONFORMANCE_FIXTURE_PATH=/package/conformance/conformance.json - 1. `CONFORMANCE_FIXTURE_PATH` env var (explicit override) - 2. `RIDDLER_CLI_DIR` env var (CLI repo root) - 3. Sibling fallback `../../../../riddler-client` + There is no sibling-checkout fallback: a checkout's working tree is whatever + branch it happens to be on, not a pinned release. - Conformance tests are tagged `:conformance` (also `:cli_signer` via the - same fixture path requirement) and are skipped by default when no - fixture can be located. + Conformance tests are tagged `:conformance`. Locally they are excluded and + generate zero vectors when the variable is unset; under CI an unset variable + raises, because zero vectors there is a missing download, not coverage. """ + alias Raxol.Payments.Test.RiddlerSpec + + @env "CONFORMANCE_FIXTURE_PATH" + @doc """ Load the entire fixture as a list of vector maps. Raises if the fixture cannot be found. @@ -32,24 +35,26 @@ defmodule Raxol.Payments.Test.ConformanceFixture do @doc """ Filter the fixture by `"protocol"` field. - Returns `[]` when the fixture is absent, rather than raising. The conformance - files call this at test-module COMPILE time (`for vec <- by_protocol(...)` - inside a `describe`), so the `:conformance` moduletag -- a runtime exclusion -- - cannot save a raise here: the whole suite dies before a single test runs. That - is what kept `raxol_payments` out of CI, where riddler-client is not checked - out beside the repo. + Outside CI, returns `[]` when the fixture is absent rather than raising. The + conformance files call this at test-module COMPILE time (`for vec <- + by_protocol(...)` inside a `describe`), so the `:conformance` moduletag -- a + runtime exclusion -- cannot save a raise here: the whole suite dies before a + single test runs, which is why a local run without the fixture warns instead. - The absence is announced rather than swallowed: generating zero vectors is - legitimate here, but it must not look like coverage that passed. + Under CI that compile-time raise is the point: CI fetches the pinned + `@riddler/spec` tarball, so an absent fixture means the fetch or the export + broke, and passing with 0 vectors would report coverage that never ran. """ @spec by_protocol(String.t()) :: [map()] def by_protocol(protocol) do case do_locate() do nil -> + if RiddlerSpec.ci?(), do: raise(not_found_message()) + IO.puts( :stderr, "[conformance] fixture absent -- 0 #{protocol} vectors generated. " <> - "Set CONFORMANCE_FIXTURE_PATH or RIDDLER_CLI_DIR to run them." + "Set #{@env} to the pinned @riddler/spec conformance.json to run them." ) [] @@ -67,7 +72,8 @@ defmodule Raxol.Payments.Test.ConformanceFixture do @doc """ Try to locate the fixture file. Returns `{:ok, path}` or `{:error, :not_found}` - without raising. Useful in `setup` blocks that conditionally skip tests. + without raising when the variable is unset. Useful in `setup` blocks that + conditionally skip tests. """ @spec locate() :: {:ok, Path.t()} | {:error, :not_found} def locate do @@ -81,41 +87,34 @@ defmodule Raxol.Payments.Test.ConformanceFixture do defp locate_path! do case do_locate() do - nil -> - raise "Conformance fixture not found. Set CONFORMANCE_FIXTURE_PATH " <> - "or RIDDLER_CLI_DIR, or place riddler-client beside the raxol repo." - - path -> - path + nil -> raise not_found_message() + path -> path end end + # An explicit path that does not exist is a misconfiguration in every + # environment, never a reason to fall back to "absent". defp do_locate do - candidates = - [ - System.get_env("CONFORMANCE_FIXTURE_PATH"), - cli_dir_path(System.get_env("RIDDLER_CLI_DIR")), - cli_dir_path(sibling_default()) - ] - |> Enum.reject(&is_nil/1) - - Enum.find(candidates, &File.exists?/1) + case System.get_env(@env) do + blank when blank in [nil, ""] -> + nil + + path -> + if File.regular?(path) do + path + else + raise "#{@env}=#{inspect(path)} does not name a file. Point it at " <> + "conformance/conformance.json in the unpacked " <> + "@riddler/spec@#{RiddlerSpec.pinned_version()} tarball." + end + end end - defp cli_dir_path(nil), do: nil - - defp cli_dir_path(dir), - do: Path.join([dir, "packages", "spec", "conformance", "conformance.json"]) - - # axol-io/riddler-sdk, formerly riddler-client. The old name is kept as a - # fallback so a checkout under either directory resolves. - defp sibling_default do - Enum.find( - [ - Path.expand("../../../../../riddler-sdk", __DIR__), - Path.expand("../../../../../riddler-client", __DIR__) - ], - &File.dir?/1 - ) + defp not_found_message do + version = RiddlerSpec.pinned_version() + + "Conformance fixture not found: #{@env} is unset. Fetch the pinned release " <> + "(npm pack @riddler/spec@#{version} && tar xzf riddler-spec-#{version}.tgz) " <> + "and set #{@env}=$PWD/package/conformance/conformance.json." end end diff --git a/packages/raxol_payments/test/support/riddler_spec.ex b/packages/raxol_payments/test/support/riddler_spec.ex new file mode 100644 index 000000000..8b91fc631 --- /dev/null +++ b/packages/raxol_payments/test/support/riddler_spec.ex @@ -0,0 +1,40 @@ +defmodule Raxol.Payments.Test.RiddlerSpec do + @moduledoc """ + The pinned `@riddler/spec` release the cross-repo parity tests read. + + `@riddler/spec` (axol-io/riddler-sdk `packages/spec`) is the byte-equality + contract between Riddler, its Node SDK and `raxol_payments`: the EIP-712 + conformance fixture and the fee schedule both come from it. They are read + from ONE published release, named in `test/fixtures/RIDDLER_SPEC_VERSION`, + never from a sibling checkout whose state nobody pinned. + + CI unpacks `npm pack @riddler/spec@` and exports: + + * `RIDDLER_SPEC_DIR` -- the unpacked tarball root (`.../package`) + * `CONFORMANCE_FIXTURE_PATH` -- `$RIDDLER_SPEC_DIR/conformance/conformance.json` + + Locally both are optional and their tests skip with a warning. Under CI + (`CI` set to anything but `""`, `"0"` or `"false"`) an absent artifact is a + failure, so a missing download cannot pass as zero vectors checked. + """ + + @version_file Path.expand("../fixtures/RIDDLER_SPEC_VERSION", __DIR__) + @external_resource @version_file + + @doc "The pinned `@riddler/spec` version, e.g. `\"0.8.0\"`." + @spec pinned_version() :: String.t() + def pinned_version, do: @version_file |> File.read!() |> String.trim() + + @doc "Whether this run is CI, where an absent spec artifact must fail." + @spec ci?() :: boolean() + def ci?, do: System.get_env("CI") not in [nil, "", "0", "false"] + + @doc "The unpacked `@riddler/spec` tarball root from `RIDDLER_SPEC_DIR`, or nil." + @spec dir() :: Path.t() | nil + def dir do + case System.get_env("RIDDLER_SPEC_DIR") do + blank when blank in [nil, ""] -> nil + dir -> dir + end + end +end diff --git a/packages/raxol_payments/test/test_helper.exs b/packages/raxol_payments/test/test_helper.exs index 9b7390126..9428cc608 100644 --- a/packages/raxol_payments/test/test_helper.exs +++ b/packages/raxol_payments/test/test_helper.exs @@ -15,14 +15,14 @@ live_exclude = [:live_xochi, :live_relay, :live_property] # Each entry excludes a tag by default unless its enabling env var is set. # These read fixtures or spawn a local CLI; none of them move funds. # -# :cli_signer / :conformance -- spawn the riddler-client CLI or read -# its shared EIP-712 fixture (RIDDLER_CLI_DIR / CONFORMANCE_FIXTURE_PATH). +# :cli_signer -- spawn the riddler-sdk CLI from a checkout (RIDDLER_CLI_DIR). +# :conformance -- read the pinned @riddler/spec EIP-712 fixture +# (CONFORMANCE_FIXTURE_PATH; required under CI, see ConformanceFixture). # :stealth_conformance -- match the stealth scheme against a reference SDK # fixture (STEALTH_VECTORS_PATH). gated_tags = [ - {[:cli_signer, :conformance], - System.get_env("RIDDLER_CLI_DIR") || - System.get_env("CONFORMANCE_FIXTURE_PATH")}, + {[:cli_signer], System.get_env("RIDDLER_CLI_DIR")}, + {[:conformance], System.get_env("CONFORMANCE_FIXTURE_PATH")}, {[:stealth_conformance], System.get_env("STEALTH_VECTORS_PATH") || File.exists?(Path.join(__DIR__, "fixtures/stealth_vectors.json"))} From 185baa28ad98c34baa6fb2331e7c4edebbdffa35 Mon Sep 17 00:00:00 2001 From: DROOdotFOO Date: Fri, 9 Oct 2026 17:29:07 +0200 Subject: [PATCH 2/5] Repin parity to @xochi/spec and e2e-signer @riddler/spec moved to xochi-fi/xochi-sdk as @xochi/spec, so the pin, module and env var follow (XOCHI_SPEC_VERSION, XochiSpec, XOCHI_SPEC_DIR) and CI packs @xochi/spec. The taker CLI moved into Riddler as the private packages/e2e-signer; CliSigner runs its built dist/cli.js from a Riddler checkout (RIDDLER_CLI_DIR), still a local-only tag. --- .github/workflows/ci-unified.yml | 18 ++--- packages/raxol_earn/test/test_helper.exs | 2 +- .../lib/raxol/payments/fee_schedule.ex | 2 +- ...IDDLER_SPEC_VERSION => XOCHI_SPEC_VERSION} | 0 .../conformance/wallet_signer_parity_test.exs | 34 +++------ .../test/raxol/payments/fee_schedule_test.exs | 26 +++---- .../raxol_payments/test/support/cli_signer.ex | 72 +++++++++---------- .../test/support/cli_signer_test.exs | 53 +++++--------- .../test/support/conformance_fixture.ex | 20 +++--- .../{riddler_spec.ex => xochi_spec.ex} | 22 +++--- packages/raxol_payments/test/test_helper.exs | 4 +- 11 files changed, 108 insertions(+), 145 deletions(-) rename packages/raxol_payments/test/fixtures/{RIDDLER_SPEC_VERSION => XOCHI_SPEC_VERSION} (100%) rename packages/raxol_payments/test/support/{riddler_spec.ex => xochi_spec.ex} (54%) diff --git a/.github/workflows/ci-unified.yml b/.github/workflows/ci-unified.yml index 8684bd8b0..e1f15f365 100644 --- a/.github/workflows/ci-unified.yml +++ b/.github/workflows/ci-unified.yml @@ -576,28 +576,28 @@ jobs: sudo apt-get install -y tmux # raxol_payments' EIP-712 conformance vectors and its vendored fee oracle - # (priv/fee-oracle/schedule.json) are held to ONE published @riddler/spec - # release, pinned in test/fixtures/RIDDLER_SPEC_VERSION -- never a sibling + # (priv/fee-oracle/schedule.json) are held to ONE published @xochi/spec + # release, pinned in test/fixtures/XOCHI_SPEC_VERSION -- never a sibling # checkout. Under CI both suites fail when these variables are unset, so a # broken fetch is a red cell rather than a green run over zero vectors. - - name: Set up Node (pinned @riddler/spec) + - name: Set up Node (pinned @xochi/spec) if: matrix.package == 'raxol_payments' uses: actions/setup-node@v7 with: node-version: '22' - - name: Fetch pinned @riddler/spec + - name: Fetch pinned @xochi/spec if: matrix.package == 'raxol_payments' working-directory: packages/raxol_payments run: | - version="$(cat test/fixtures/RIDDLER_SPEC_VERSION)" - dest="$RUNNER_TEMP/riddler-spec" + version="$(cat test/fixtures/XOCHI_SPEC_VERSION)" + dest="$RUNNER_TEMP/xochi-spec" mkdir -p "$dest" - npm pack "@riddler/spec@${version}" --pack-destination "$dest" - tar -xzf "$dest/riddler-spec-${version}.tgz" -C "$dest" + npm pack "@xochi/spec@${version}" --pack-destination "$dest" + tar -xzf "$dest/xochi-spec-${version}.tgz" -C "$dest" test -f "$dest/package/conformance/conformance.json" test -f "$dest/package/fee/schedule.json" - echo "RIDDLER_SPEC_DIR=$dest/package" >> "$GITHUB_ENV" + echo "XOCHI_SPEC_DIR=$dest/package" >> "$GITHUB_ENV" echo "CONFORMANCE_FIXTURE_PATH=$dest/package/conformance/conformance.json" >> "$GITHUB_ENV" # `--check-locked` rather than a bare `deps.get`: a plain fetch REWRITES diff --git a/packages/raxol_earn/test/test_helper.exs b/packages/raxol_earn/test/test_helper.exs index e150abe25..abe39796a 100644 --- a/packages/raxol_earn/test/test_helper.exs +++ b/packages/raxol_earn/test/test_helper.exs @@ -10,7 +10,7 @@ # (off-chain intent signature + captured on-chain write). # :live_parity -- re-checks the live Xochi quote shape against the committed # fixture. Read-only, no funds, but it does call out to the network. -# :cli_signer -- spawns the riddler-client CLI; auto-enabled when +# :cli_signer -- spawns Riddler's e2e-signer CLI; auto-enabled when # RIDDLER_CLI_DIR is set. # # A single ExUnit.start sets the full list; a second ExUnit.configure(exclude:) diff --git a/packages/raxol_payments/lib/raxol/payments/fee_schedule.ex b/packages/raxol_payments/lib/raxol/payments/fee_schedule.ex index a85dd6734..9699caf40 100644 --- a/packages/raxol_payments/lib/raxol/payments/fee_schedule.ex +++ b/packages/raxol_payments/lib/raxol/payments/fee_schedule.ex @@ -4,7 +4,7 @@ defmodule Raxol.Payments.FeeSchedule do Authoritative source is `Riddler.Integrations.Xochi.FeePolicy`. This module is its in-code mirror on the raxol side, exactly as - `@riddler/sdk-taker`'s `fee.ts` is on the TypeScript side, and it is pinned + `@xochi/reference`'s `fee.ts` is on the TypeScript side, and it is pinned against the same generated projection both of those are checked against (`priv/fee-oracle/schedule.json`). `fee_schedule_test.exs` fails when the two drift, so a fee rate cannot quietly go stale here while the solver charges diff --git a/packages/raxol_payments/test/fixtures/RIDDLER_SPEC_VERSION b/packages/raxol_payments/test/fixtures/XOCHI_SPEC_VERSION similarity index 100% rename from packages/raxol_payments/test/fixtures/RIDDLER_SPEC_VERSION rename to packages/raxol_payments/test/fixtures/XOCHI_SPEC_VERSION diff --git a/packages/raxol_payments/test/raxol/payments/conformance/wallet_signer_parity_test.exs b/packages/raxol_payments/test/raxol/payments/conformance/wallet_signer_parity_test.exs index 7168823bf..ca4fcd514 100644 --- a/packages/raxol_payments/test/raxol/payments/conformance/wallet_signer_parity_test.exs +++ b/packages/raxol_payments/test/raxol/payments/conformance/wallet_signer_parity_test.exs @@ -1,7 +1,7 @@ defmodule Raxol.Payments.Conformance.WalletSignerParityTest do @moduledoc """ Byte-for-byte parity between the wallet signer and the reference signer - (ethers, via the riddler-client CLI). + (ethers, via Riddler's `packages/e2e-signer` CLI). Both sign the same EIP-712 digest with the same key using deterministic (RFC 6979, low-s) ECDSA, so `r`, `s`, and the recovery byte `v` must be @@ -10,8 +10,9 @@ defmodule Raxol.Payments.Conformance.WalletSignerParityTest do `ecrecover` (ERC-3009 origin pull / Permit2 / x402) rejects. Regression guard for the `v` normalization in `Raxol.Payments.EIP712.pack_signature/1`. - Tagged `:cli_signer` (excluded by default): needs node and the CLI checkout. - Set `RIDDLER_CLI_DIR`, or check the CLI out beside the raxol repo. + Tagged `:cli_signer` (excluded by default): needs node and a Riddler + checkout with `packages/e2e-signer` built. Set `RIDDLER_CLI_DIR` to it, or + check Riddler out beside the raxol repo. """ use ExUnit.Case, async: true @@ -28,12 +29,14 @@ defmodule Raxol.Payments.Conformance.WalletSignerParityTest do setup do System.put_env(@env_var, @key) on_exit(fn -> System.delete_env(@env_var) end) - {:ok, cli_dir: locate_cli()} + {:ok, cli_dir: CliSigner.locate()} end test "Env wallet signs an ERC-3009 digest identically to the reference CLI", %{cli_dir: cwd} do unless cwd do - flunk("CLI not available; set RIDDLER_CLI_DIR or run with --exclude cli_signer") + flunk( + "CLI not available; set RIDDLER_CLI_DIR to a built Riddler checkout or run with --exclude cli_signer" + ) end # acp-buyer-auth signs an ERC-3009 ReceiveWithAuthorization digest with @@ -66,27 +69,6 @@ defmodule Raxol.Payments.Conformance.WalletSignerParityTest do assert :binary.last(sig) in [27, 28] end - # Resolve the CLI checkout: explicit env var, then the two common dev layouts - # (beside the raxol repo under a shared parent, or inside it). Returns nil when - # absent so the test flunks with a clear message under `--only cli_signer`. - # The CLI repo (axol-io/riddler-sdk, formerly riddler-client) became a - # monorepo and its entry point moved to packages/sdk-taker/src/cli.ts. - # Probing for the retired src/index.js + src/acp.js answered nil even with - # RIDDLER_CLI_DIR set correctly, which silently retired this oracle: the test - # is :cli_signer-tagged and excluded by default, so the flunk below was never - # reached. Probe the entry point CliSigner actually spawns. - defp locate_cli do - [ - System.get_env("RIDDLER_CLI_DIR"), - Path.expand("../../../../../../../riddler-sdk", __DIR__), - Path.expand("../../../../../../riddler-sdk", __DIR__), - Path.expand("../../../../../../../riddler-client", __DIR__), - Path.expand("../../../../../../riddler-client", __DIR__) - ] - |> Enum.reject(&is_nil/1) - |> Enum.find(&File.exists?(Path.join([&1, "packages", "sdk-taker", "src", "cli.ts"]))) - end - defp decode_hex("0x" <> hex), do: Base.decode16(hex, case: :mixed) defp decode_hex(_), do: :error end diff --git a/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs b/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs index a801ea3ed..97e7e6ec0 100644 --- a/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs +++ b/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs @@ -3,7 +3,7 @@ defmodule Raxol.Payments.FeeScheduleTest do Parity against the canonical schedule, plus the arithmetic on top of it. The oracle in `priv/fee-oracle/schedule.json` is the generated projection of - `Riddler.Integrations.Xochi.FeePolicy` -- the same artifact `@riddler/sdk-taker` + `Riddler.Integrations.Xochi.FeePolicy` -- the same artifact `@xochi/reference` checks its TypeScript mirror against. Pinning to it is what makes this module a mirror rather than a second opinion: if Riddler reprices, these fail instead of raxol.io quietly advertising a rate nobody charges. @@ -11,7 +11,7 @@ defmodule Raxol.Payments.FeeScheduleTest do use ExUnit.Case, async: true alias Raxol.Payments.FeeSchedule - alias Raxol.Payments.Test.RiddlerSpec + alias Raxol.Payments.Test.XochiSpec @oracle_path Path.join(:code.priv_dir(:raxol_payments), "fee-oracle/schedule.json") @oracle @oracle_path |> File.read!() |> Jason.decode!() @@ -72,29 +72,29 @@ defmodule Raxol.Payments.FeeScheduleTest do # The oracle is a vendored copy, so the parity above only proves this module # agrees with the copy. This proves the copy is the pinned release's bytes. - describe "vendored oracle vs the pinned @riddler/spec" do - if is_nil(RiddlerSpec.dir()) and not RiddlerSpec.ci?() do - @tag skip: "RIDDLER_SPEC_DIR unset (the unpacked @riddler/spec tarball root)" + describe "vendored oracle vs the pinned @xochi/spec" do + if is_nil(XochiSpec.dir()) and not XochiSpec.ci?() do + @tag skip: "XOCHI_SPEC_DIR unset (the unpacked @xochi/spec tarball root)" end test "priv/fee-oracle/schedule.json is byte-identical to fee/schedule.json" do - version = RiddlerSpec.pinned_version() + version = XochiSpec.pinned_version() dir = - RiddlerSpec.dir() || + XochiSpec.dir() || flunk( - "RIDDLER_SPEC_DIR is unset under CI. Unpack npm pack " <> - "@riddler/spec@#{version} and set it to the tarball's package/ directory." + "XOCHI_SPEC_DIR is unset under CI. Unpack npm pack " <> + "@xochi/spec@#{version} and set it to the tarball's package/ directory." ) manifest = dir |> Path.join("package.json") |> File.read!() |> Jason.decode!() - assert {manifest["name"], manifest["version"]} == {"@riddler/spec", version}, - "RIDDLER_SPEC_DIR=#{dir} is not @riddler/spec@#{version} " <> - "(test/fixtures/RIDDLER_SPEC_VERSION)" + assert {manifest["name"], manifest["version"]} == {"@xochi/spec", version}, + "XOCHI_SPEC_DIR=#{dir} is not @xochi/spec@#{version} " <> + "(test/fixtures/XOCHI_SPEC_VERSION)" assert File.read!(Path.join([dir, "fee", "schedule.json"])) == File.read!(@oracle_path), - "priv/fee-oracle/schedule.json drifted from @riddler/spec@#{version} " <> + "priv/fee-oracle/schedule.json drifted from @xochi/spec@#{version} " <> "fee/schedule.json; copy the release's file over it" end end diff --git a/packages/raxol_payments/test/support/cli_signer.ex b/packages/raxol_payments/test/support/cli_signer.ex index 9487cb556..ea0b82bec 100644 --- a/packages/raxol_payments/test/support/cli_signer.ex +++ b/packages/raxol_payments/test/support/cli_signer.ex @@ -1,6 +1,6 @@ defmodule Raxol.Payments.Test.CliSigner do @moduledoc """ - Spawn the `riddler-client` CLI as a black-box signing oracle. + Spawn Riddler's `packages/e2e-signer` CLI as a black-box signing oracle. The CLI emits a single `RESULT_JSON: { ... }` line on stdout for the sign-only and ACP buyer-auth subcommands; this module spawns the CLI @@ -11,17 +11,22 @@ defmodule Raxol.Payments.Test.CliSigner do raxol_earn end-to-end tests that exercise the buyer-side authorization path. - ## Resolving the CLI repo + ## Resolving the CLI - Lookup order: + The taker CLI (formerly axol-io/riddler-sdk `packages/sdk-taker`) now lives + in the private Riddler repo as `packages/e2e-signer`. It is run from its + built output, so build it first (`npm ci && npm run build` in + `packages/e2e-signer`). Riddler is private, so `:cli_signer` stays a + local-only tag. + + Lookup order for the Riddler checkout root: 1. `opts[:cwd]` 2. `System.get_env("RIDDLER_CLI_DIR")` - 3. Sibling-directory fallback assuming `riddler-client` is - checked out beside this monorepo's parent. + 3. A sibling `Riddler` checkout beside this monorepo. - If neither `cwd` nor `RIDDLER_CLI_DIR` is set and the sibling fallback - doesn't exist, every call raises `Raxol.Payments.Test.CliSigner.CliNotFoundError`. + If none holds `packages/e2e-signer/dist/cli.js`, every call raises + `Raxol.Payments.Test.CliSigner.CliNotFoundError`. ## Example @@ -49,8 +54,8 @@ defmodule Raxol.Payments.Test.CliSigner do @result_json_regex ~r/^RESULT_JSON:\s*(\{.*\})\s*$/m - # Path (relative to the riddler-client repo root) of the CLI entry point. - @cli_entry "packages/sdk-taker/src/cli.ts" + # Path (relative to the Riddler repo root) of the built CLI entry point. + @cli_entry "packages/e2e-signer/dist/cli.js" @type flag :: {atom() | String.t(), String.t() | integer() | boolean()} @type opts :: [ @@ -79,12 +84,10 @@ defmodule Raxol.Payments.Test.CliSigner do {:ok, result()} | {:error, {:cli_failed, integer(), String.t()}} def run(subcommand, flags, opts \\ []) do cwd = resolve_cwd(opts) - # riddler-client is now a monorepo; the CLI lives in the riddler-sdk package - # and is TypeScript, so it is run from source via tsx (no build step needed). - args = ["tsx", @cli_entry, subcommand | encode_flags(flags)] + args = [@cli_entry, subcommand | encode_flags(flags)] env = build_env(opts) - {stdout, exit_code} = System.cmd("npx", args, cd: cwd, env: env, stderr_to_stdout: true) + {stdout, exit_code} = System.cmd("node", args, cd: cwd, env: env, stderr_to_stdout: true) case exit_code do 0 -> @@ -127,40 +130,37 @@ defmodule Raxol.Payments.Test.CliSigner do run("xochi-flow", flags, opts) end + @doc """ + The Riddler checkout holding the built CLI (`RIDDLER_CLI_DIR`, then a + sibling `Riddler` checkout), or nil when neither has it. + """ + @spec locate() :: Path.t() | nil + def locate do + Enum.find([System.get_env("RIDDLER_CLI_DIR"), sibling_default()], &cli_present?/1) + end + # -- Internals -- defp resolve_cwd(opts) do - cwd = - opts[:cwd] || - System.get_env("RIDDLER_CLI_DIR") || - sibling_default() + cwd = opts[:cwd] || System.get_env("RIDDLER_CLI_DIR") || sibling_default() - if cwd && File.dir?(cwd) && File.exists?(Path.join([cwd | Path.split(@cli_entry)])) do + if cli_present?(cwd) do cwd else raise CliNotFoundError, message: - "Could not locate riddler-client CLI. Set RIDDLER_CLI_DIR or pass cwd: ... " <> - "(tried #{inspect(cwd)})" + "Could not locate the Riddler e2e-signer CLI (#{@cli_entry}). " <> + "Set RIDDLER_CLI_DIR to a Riddler checkout with packages/e2e-signer " <> + "built, or pass cwd: ... (tried #{inspect(cwd)})" end end - # Best-effort fallback: a sibling checkout of the CLI repo (axol-io/riddler-sdk, - # formerly riddler-client) beside the raxol monorepo, the common dev layout - # where both live under ~/CODE/. - # - # The previous expansion was one level short and landed on - # /riddler-client, inside this repo, so the fallback never resolved - # under either repo name. - defp sibling_default do - Enum.find( - [ - Path.expand("../../../../../riddler-sdk", __DIR__), - Path.expand("../../../../../riddler-client", __DIR__) - ], - &File.dir?/1 - ) - end + defp cli_present?(nil), do: false + defp cli_present?(dir), do: File.regular?(Path.join(dir, @cli_entry)) + + # Best-effort fallback: a Riddler checkout beside the raxol monorepo, the + # common dev layout where both live under ~/CODE/. + defp sibling_default, do: Path.expand("../../../../../Riddler", __DIR__) defp encode_flags(flags) do Enum.flat_map(flags, fn diff --git a/packages/raxol_payments/test/support/cli_signer_test.exs b/packages/raxol_payments/test/support/cli_signer_test.exs index b9e435160..ec0bf3041 100644 --- a/packages/raxol_payments/test/support/cli_signer_test.exs +++ b/packages/raxol_payments/test/support/cli_signer_test.exs @@ -6,44 +6,19 @@ defmodule Raxol.Payments.Test.CliSignerTest do @moduletag :cli_signer setup do - # The CLI repo is axol-io/riddler-sdk (formerly riddler-client), a sibling of - # the raxol monorepo. The previous expansion was one level short and landed - # on /riddler-client, inside this repo, so the fallback never resolved - # under either name and these tests only ran with RIDDLER_CLI_DIR set. - cwd = - System.get_env("RIDDLER_CLI_DIR") || - Enum.find( - [ - Path.expand("../../../../../riddler-sdk", __DIR__), - Path.expand("../../../../../riddler-client", __DIR__) - ], - &File.dir?/1 - ) || - Path.expand("../../../../../riddler-sdk", __DIR__) - - cli_available? = - File.dir?(cwd) and - File.exists?(Path.join([cwd, "packages", "sdk-taker", "src", "cli.ts"])) - - if cli_available? do - # Check whether the CLI on disk has the sign-only/acp-buyer-auth - # subcommands we depend on (acp.ts in the riddler-sdk package). - has_subcommands? = - File.exists?(Path.join([cwd, "packages", "sdk-taker", "src", "acp.ts"])) - - {:ok, cli_dir: cwd, cli_available?: cli_available? and has_subcommands?} - else - {:ok, cli_dir: cwd, cli_available?: false} - end + cwd = CliSigner.locate() + {:ok, cli_dir: cwd, cli_available?: not is_nil(cwd)} end describe "run/3 against a real CLI" do - test "invokes `npx tsx packages/sdk-taker/src/cli.ts help` and exits 0", %{ + test "invokes `node packages/e2e-signer/dist/cli.js help` and exits 0", %{ cli_dir: cwd, cli_available?: cli_available? } do unless cli_available? do - flunk("CLI not available; set RIDDLER_CLI_DIR or run with --exclude cli_signer") + flunk( + "CLI not available; set RIDDLER_CLI_DIR to a built Riddler checkout or run with --exclude cli_signer" + ) end assert {:ok, %{exit_code: 0, stdout: stdout}} = @@ -63,7 +38,9 @@ defmodule Raxol.Payments.Test.CliSignerTest do cli_available?: cli_available? } do unless cli_available? do - flunk("CLI not available; set RIDDLER_CLI_DIR or run with --exclude cli_signer") + flunk( + "CLI not available; set RIDDLER_CLI_DIR to a built Riddler checkout or run with --exclude cli_signer" + ) end flags = [ @@ -96,7 +73,9 @@ defmodule Raxol.Payments.Test.CliSignerTest do cli_available?: cli_available? } do unless cli_available? do - flunk("CLI not available; set RIDDLER_CLI_DIR or run with --exclude cli_signer") + flunk( + "CLI not available; set RIDDLER_CLI_DIR to a built Riddler checkout or run with --exclude cli_signer" + ) end # Force a failure: invoke acp-buyer-auth with missing required flags. @@ -131,9 +110,11 @@ defmodule Raxol.Payments.Test.CliSignerTest do describe "CliNotFoundError" do test "raises with a clear message when the CLI repo can't be found" do - assert_raise CliSigner.CliNotFoundError, ~r/Could not locate riddler-client/, fn -> - CliSigner.run("help", [], cwd: "/definitely/does/not/exist") - end + assert_raise CliSigner.CliNotFoundError, + ~r/Could not locate the Riddler e2e-signer CLI/, + fn -> + CliSigner.run("help", [], cwd: "/definitely/does/not/exist") + end end end end diff --git a/packages/raxol_payments/test/support/conformance_fixture.ex b/packages/raxol_payments/test/support/conformance_fixture.ex index 055971556..479df58b0 100644 --- a/packages/raxol_payments/test/support/conformance_fixture.ex +++ b/packages/raxol_payments/test/support/conformance_fixture.ex @@ -3,10 +3,10 @@ defmodule Raxol.Payments.Test.ConformanceFixture do Loader for the shared EIP-712 conformance fixture. The fixture is `conformance/conformance.json` in the published - `@riddler/spec` package (axol-io/riddler-sdk `packages/spec`), the + `@xochi/spec` package (xochi-fi/xochi-sdk `packages/spec`), the byte-equality contract between the Node SDK and `raxol_payments`. It is read - from the release pinned in `test/fixtures/RIDDLER_SPEC_VERSION` (see - `Raxol.Payments.Test.RiddlerSpec`), located by exactly one variable: + from the release pinned in `test/fixtures/XOCHI_SPEC_VERSION` (see + `Raxol.Payments.Test.XochiSpec`), located by exactly one variable: CONFORMANCE_FIXTURE_PATH=/package/conformance/conformance.json @@ -18,7 +18,7 @@ defmodule Raxol.Payments.Test.ConformanceFixture do raises, because zero vectors there is a missing download, not coverage. """ - alias Raxol.Payments.Test.RiddlerSpec + alias Raxol.Payments.Test.XochiSpec @env "CONFORMANCE_FIXTURE_PATH" @@ -42,19 +42,19 @@ defmodule Raxol.Payments.Test.ConformanceFixture do single test runs, which is why a local run without the fixture warns instead. Under CI that compile-time raise is the point: CI fetches the pinned - `@riddler/spec` tarball, so an absent fixture means the fetch or the export + `@xochi/spec` tarball, so an absent fixture means the fetch or the export broke, and passing with 0 vectors would report coverage that never ran. """ @spec by_protocol(String.t()) :: [map()] def by_protocol(protocol) do case do_locate() do nil -> - if RiddlerSpec.ci?(), do: raise(not_found_message()) + if XochiSpec.ci?(), do: raise(not_found_message()) IO.puts( :stderr, "[conformance] fixture absent -- 0 #{protocol} vectors generated. " <> - "Set #{@env} to the pinned @riddler/spec conformance.json to run them." + "Set #{@env} to the pinned @xochi/spec conformance.json to run them." ) [] @@ -105,16 +105,16 @@ defmodule Raxol.Payments.Test.ConformanceFixture do else raise "#{@env}=#{inspect(path)} does not name a file. Point it at " <> "conformance/conformance.json in the unpacked " <> - "@riddler/spec@#{RiddlerSpec.pinned_version()} tarball." + "@xochi/spec@#{XochiSpec.pinned_version()} tarball." end end end defp not_found_message do - version = RiddlerSpec.pinned_version() + version = XochiSpec.pinned_version() "Conformance fixture not found: #{@env} is unset. Fetch the pinned release " <> - "(npm pack @riddler/spec@#{version} && tar xzf riddler-spec-#{version}.tgz) " <> + "(npm pack @xochi/spec@#{version} && tar xzf xochi-spec-#{version}.tgz) " <> "and set #{@env}=$PWD/package/conformance/conformance.json." end end diff --git a/packages/raxol_payments/test/support/riddler_spec.ex b/packages/raxol_payments/test/support/xochi_spec.ex similarity index 54% rename from packages/raxol_payments/test/support/riddler_spec.ex rename to packages/raxol_payments/test/support/xochi_spec.ex index 8b91fc631..dd66cdfc7 100644 --- a/packages/raxol_payments/test/support/riddler_spec.ex +++ b/packages/raxol_payments/test/support/xochi_spec.ex @@ -1,27 +1,27 @@ -defmodule Raxol.Payments.Test.RiddlerSpec do +defmodule Raxol.Payments.Test.XochiSpec do @moduledoc """ - The pinned `@riddler/spec` release the cross-repo parity tests read. + The pinned `@xochi/spec` release the cross-repo parity tests read. - `@riddler/spec` (axol-io/riddler-sdk `packages/spec`) is the byte-equality + `@xochi/spec` (xochi-fi/xochi-sdk `packages/spec`) is the byte-equality contract between Riddler, its Node SDK and `raxol_payments`: the EIP-712 conformance fixture and the fee schedule both come from it. They are read - from ONE published release, named in `test/fixtures/RIDDLER_SPEC_VERSION`, + from ONE published release, named in `test/fixtures/XOCHI_SPEC_VERSION`, never from a sibling checkout whose state nobody pinned. - CI unpacks `npm pack @riddler/spec@` and exports: + CI unpacks `npm pack @xochi/spec@` and exports: - * `RIDDLER_SPEC_DIR` -- the unpacked tarball root (`.../package`) - * `CONFORMANCE_FIXTURE_PATH` -- `$RIDDLER_SPEC_DIR/conformance/conformance.json` + * `XOCHI_SPEC_DIR` -- the unpacked tarball root (`.../package`) + * `CONFORMANCE_FIXTURE_PATH` -- `$XOCHI_SPEC_DIR/conformance/conformance.json` Locally both are optional and their tests skip with a warning. Under CI (`CI` set to anything but `""`, `"0"` or `"false"`) an absent artifact is a failure, so a missing download cannot pass as zero vectors checked. """ - @version_file Path.expand("../fixtures/RIDDLER_SPEC_VERSION", __DIR__) + @version_file Path.expand("../fixtures/XOCHI_SPEC_VERSION", __DIR__) @external_resource @version_file - @doc "The pinned `@riddler/spec` version, e.g. `\"0.8.0\"`." + @doc "The pinned `@xochi/spec` version, e.g. `\"0.8.0\"`." @spec pinned_version() :: String.t() def pinned_version, do: @version_file |> File.read!() |> String.trim() @@ -29,10 +29,10 @@ defmodule Raxol.Payments.Test.RiddlerSpec do @spec ci?() :: boolean() def ci?, do: System.get_env("CI") not in [nil, "", "0", "false"] - @doc "The unpacked `@riddler/spec` tarball root from `RIDDLER_SPEC_DIR`, or nil." + @doc "The unpacked `@xochi/spec` tarball root from `XOCHI_SPEC_DIR`, or nil." @spec dir() :: Path.t() | nil def dir do - case System.get_env("RIDDLER_SPEC_DIR") do + case System.get_env("XOCHI_SPEC_DIR") do blank when blank in [nil, ""] -> nil dir -> dir end diff --git a/packages/raxol_payments/test/test_helper.exs b/packages/raxol_payments/test/test_helper.exs index 9428cc608..5cd60372f 100644 --- a/packages/raxol_payments/test/test_helper.exs +++ b/packages/raxol_payments/test/test_helper.exs @@ -15,8 +15,8 @@ live_exclude = [:live_xochi, :live_relay, :live_property] # Each entry excludes a tag by default unless its enabling env var is set. # These read fixtures or spawn a local CLI; none of them move funds. # -# :cli_signer -- spawn the riddler-sdk CLI from a checkout (RIDDLER_CLI_DIR). -# :conformance -- read the pinned @riddler/spec EIP-712 fixture +# :cli_signer -- spawn Riddler's e2e-signer CLI (RIDDLER_CLI_DIR, local-only). +# :conformance -- read the pinned @xochi/spec EIP-712 fixture # (CONFORMANCE_FIXTURE_PATH; required under CI, see ConformanceFixture). # :stealth_conformance -- match the stealth scheme against a reference SDK # fixture (STEALTH_VECTORS_PATH). From 4ac9877e4824ce0d69cb10a206c91b514ce35b30 Mon Sep 17 00:00:00 2001 From: DROOdotFOO Date: Tue, 13 Oct 2026 21:19:37 +0200 Subject: [PATCH 3/5] Pin @xochi/spec at 0.8.1, the version xochi-sdk will publish The pin named 0.8.0, which xochi-sdk never published and never will: packages/spec/package.json on its main branch is 0.8.1 and the spec CHANGELOG calls 0.8.0 "the never-published" version that every pre-fix commit declared. The release workflow refuses a spec-v* tag whose version differs from package.json, so there is no path by which `npm pack @xochi/spec@0.8.0` stops answering 404, and the Package Tests (raxol_payments) cell would have stayed red after the publish the PR waits on. 0.8.1 is a PATCH with no vector or schedule change: against a tarball packed from xochi-sdk main, every conformance vector and the fee oracle byte-identity test pass with this pin, and only the manifest version assertion failed under the old one. --- packages/raxol_payments/test/fixtures/XOCHI_SPEC_VERSION | 2 +- packages/raxol_payments/test/support/xochi_spec.ex | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/raxol_payments/test/fixtures/XOCHI_SPEC_VERSION b/packages/raxol_payments/test/fixtures/XOCHI_SPEC_VERSION index a3df0a695..6f4eebdf6 100644 --- a/packages/raxol_payments/test/fixtures/XOCHI_SPEC_VERSION +++ b/packages/raxol_payments/test/fixtures/XOCHI_SPEC_VERSION @@ -1 +1 @@ -0.8.0 +0.8.1 diff --git a/packages/raxol_payments/test/support/xochi_spec.ex b/packages/raxol_payments/test/support/xochi_spec.ex index dd66cdfc7..dc4fbf106 100644 --- a/packages/raxol_payments/test/support/xochi_spec.ex +++ b/packages/raxol_payments/test/support/xochi_spec.ex @@ -21,7 +21,7 @@ defmodule Raxol.Payments.Test.XochiSpec do @version_file Path.expand("../fixtures/XOCHI_SPEC_VERSION", __DIR__) @external_resource @version_file - @doc "The pinned `@xochi/spec` version, e.g. `\"0.8.0\"`." + @doc "The pinned `@xochi/spec` version, e.g. `\"0.8.1\"`." @spec pinned_version() :: String.t() def pinned_version, do: @version_file |> File.read!() |> String.trim() From d70d5966c5e5e4c6b5003566a7cd9f60ca3e3799 Mon Sep 17 00:00:00 2001 From: DROOdotFOO Date: Tue, 13 Oct 2026 21:19:45 +0200 Subject: [PATCH 4/5] Fail the Permit2 and ERC-3009 conformance suites on zero vectors Under CI an absent fixture now raises, but a fixture that loads and carries no vectors for a protocol still produced a green run: the `for vec <- by_protocol(...)` in each conformance module generates one test per vector, so an upstream rename of the "permit2" or "erc3009" protocol key, or a filtered fixture, left those describes empty and the cell green having asserted nothing. The Xochi module already guards against this with a "fixture coverage" test; Permit2 and x402 did not. Each now carries the same guard. Pointed at a fixture holding only xochi vectors, both guards fail and nothing else runs; against the pinned release's conformance.json they pass alongside the 46 generated tests. --- .../conformance/permit2_conformance_test.exs | 13 +++++++++++++ .../payments/conformance/x402_conformance_test.exs | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/raxol_payments/test/raxol/payments/conformance/permit2_conformance_test.exs b/packages/raxol_payments/test/raxol/payments/conformance/permit2_conformance_test.exs index 0bcd9cc5d..34c268c83 100644 --- a/packages/raxol_payments/test/raxol/payments/conformance/permit2_conformance_test.exs +++ b/packages/raxol_payments/test/raxol/payments/conformance/permit2_conformance_test.exs @@ -47,6 +47,19 @@ defmodule Raxol.Payments.Conformance.Permit2ConformanceTest do end end + # `by_protocol/1` answers [] both when the fixture is absent and when it loads + # but carries no permit2 vectors, and a `for` over [] generates no tests at + # all. The moduletag above covers the first case; this covers the second, + # where the suite would otherwise report a green run having asserted nothing + # about Permit2. + describe "fixture coverage" do + test "the loaded fixture yields at least one permit2 vector" do + assert ConformanceFixture.by_protocol("permit2") != [], + "fixture loaded but produced no permit2 vectors -- every generated " <> + "test below was silently skipped out of existence" + end + end + describe "Permit2 EIP-712 conformance vs CLI" do for vec <- ConformanceFixture.by_protocol("permit2") do @vec vec diff --git a/packages/raxol_payments/test/raxol/payments/conformance/x402_conformance_test.exs b/packages/raxol_payments/test/raxol/payments/conformance/x402_conformance_test.exs index 70298cc7b..34b2aadb2 100644 --- a/packages/raxol_payments/test/raxol/payments/conformance/x402_conformance_test.exs +++ b/packages/raxol_payments/test/raxol/payments/conformance/x402_conformance_test.exs @@ -30,6 +30,19 @@ defmodule Raxol.Payments.Conformance.X402ConformanceTest do ] } + # `by_protocol/1` answers [] both when the fixture is absent and when it loads + # but carries no erc3009 vectors, and a `for` over [] generates no tests at + # all. The moduletag above covers the first case; this covers the second, + # where the suite would otherwise report a green run having asserted nothing + # about ERC-3009. + describe "fixture coverage" do + test "the loaded fixture yields at least one erc3009 vector" do + assert ConformanceFixture.by_protocol("erc3009") != [], + "fixture loaded but produced no erc3009 vectors -- every generated " <> + "test below was silently skipped out of existence" + end + end + describe "ERC-3009 (x402) EIP-712 conformance vs CLI" do for vec <- ConformanceFixture.by_protocol("erc3009") do @vec vec From 335312a09170da13a7d2ddf3db94bdd7605b5e81 Mon Sep 17 00:00:00 2001 From: DROOdotFOO Date: Tue, 13 Oct 2026 21:19:51 +0200 Subject: [PATCH 5/5] Name @xochi/client as the fee schedule's TypeScript mirror FeeSchedule's docs pointed the reader at `@xochi/reference`'s `fee.ts` for the TypeScript side of the fee schedule, but that package has no such file: it is a private, unpublished test harness whose `fee.test.ts` only re-checks the published vectors. The mirror itself, `FEE_SCHEDULE` and `computeFeeBreakdown`, lives in `@xochi/client` (`packages/client/ src/fee.ts`), which is also what the spec's `fee/generate.ts` imports to produce `schedule.json`. Both the module doc and the parity test's doc now name the package that actually holds it. --- .../raxol_payments/lib/raxol/payments/fee_schedule.ex | 2 +- .../test/raxol/payments/fee_schedule_test.exs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/raxol_payments/lib/raxol/payments/fee_schedule.ex b/packages/raxol_payments/lib/raxol/payments/fee_schedule.ex index 9699caf40..ce1aa2a41 100644 --- a/packages/raxol_payments/lib/raxol/payments/fee_schedule.ex +++ b/packages/raxol_payments/lib/raxol/payments/fee_schedule.ex @@ -4,7 +4,7 @@ defmodule Raxol.Payments.FeeSchedule do Authoritative source is `Riddler.Integrations.Xochi.FeePolicy`. This module is its in-code mirror on the raxol side, exactly as - `@xochi/reference`'s `fee.ts` is on the TypeScript side, and it is pinned + `@xochi/client`'s `fee.ts` is on the TypeScript side, and it is pinned against the same generated projection both of those are checked against (`priv/fee-oracle/schedule.json`). `fee_schedule_test.exs` fails when the two drift, so a fee rate cannot quietly go stale here while the solver charges diff --git a/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs b/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs index 97e7e6ec0..9032d18b8 100644 --- a/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs +++ b/packages/raxol_payments/test/raxol/payments/fee_schedule_test.exs @@ -3,10 +3,10 @@ defmodule Raxol.Payments.FeeScheduleTest do Parity against the canonical schedule, plus the arithmetic on top of it. The oracle in `priv/fee-oracle/schedule.json` is the generated projection of - `Riddler.Integrations.Xochi.FeePolicy` -- the same artifact `@xochi/reference` - checks its TypeScript mirror against. Pinning to it is what makes this module - a mirror rather than a second opinion: if Riddler reprices, these fail instead - of raxol.io quietly advertising a rate nobody charges. + `Riddler.Integrations.Xochi.FeePolicy` -- the same artifact `@xochi/client` + checks its TypeScript mirror (`fee.ts`) against. Pinning to it is what makes + this module a mirror rather than a second opinion: if Riddler reprices, these + fail instead of raxol.io quietly advertising a rate nobody charges. """ use ExUnit.Case, async: true