x402 pre-merge follow-up: seller gateway, unsettled metric, verifyOnly warning#345
Open
bussyjd wants to merge 6 commits intofeat/x402-buyer-settlement-hold-signfrom
Open
x402 pre-merge follow-up: seller gateway, unsettled metric, verifyOnly warning#345bussyjd wants to merge 6 commits intofeat/x402-buyer-settlement-hold-signfrom
bussyjd wants to merge 6 commits intofeat/x402-buyer-settlement-hold-signfrom
Conversation
…a metric Post-#343, settlement moved off the Traefik ForwardAuth hop and became the seller's responsibility. The buyer sidecar calls ConfirmSpend on any upstream 2xx regardless of whether X-PAYMENT-RESPONSE is present, so a seller that returns 200 without settling silently consumes the payer's voucher with no observable signal. This matches the W2/W9 gap flagged in the PR #343 review. - Add OnPaymentUnsettled callback to replayableX402Transport. Fires exactly when the upstream returns 2xx but no successful X-PAYMENT-RESPONSE is emitted, logs a WARN, and increments a new counter. - Add PaymentEventUnsettled event type. - Add obol_x402_buyer_payment_unsettled_confirmations_total metric with upstream/remote_model labels. Operators should alert on any non-zero value. - Pin invariant with two new tests: - TestProxy_UpstreamSuccessNoSettlementHeader_IncrementsUnsettledMetric - TestProxy_UpstreamSuccessWithSettlementHeader_DoesNotIncrementUnsettledMetric - Pin mux symmetry invariant that both /chat/completions and /v1/chat/completions route identically — catches the class of regression that produced the PR #343 /v1 add/revert/re-add churn.
…imeout to 5s Addresses W7 and W8 from the PR #343 review. W7 — verifyOnly=false footgun: VerifyOnly is the right name for the flag in the in-process gateway context but is semantically load-bearing for Traefik ForwardAuth, where the auth hop cannot observe the upstream response. If an operator flips x402-pricing.yaml verifyOnly=false believing it enables "real" settlement, the verifier will debit the payer before the upstream serves the request. We cannot remove the flag without a broader refactor of internal/inference/gateway.go, so instead: - NewForwardAuthMiddleware now logs a loud WARNING at construction when VerifyOnly=false, explaining the safe usage. - cmd/x402-verifier/main.go emits the same warning on startup and log-scrub filters will surface it. - ForwardAuthConfig.VerifyOnly documents the invariant ("MUST be true behind Traefik ForwardAuth"), so a contributor flipping it gets the explanation inline. W8 — facilitator timeout: reduce http.Client.Timeout from 30s to 5s. /verify is a cheap signature check; anything beyond 5s is a network problem the caller should see quickly rather than having every paid request hang for half a minute on a slow facilitator. Tests: - TestForwardAuth_VerifyOnlyFalse_EmitsStartupWarning pins the warning text. - TestForwardAuth_VerifyOnlyTrue_NoStartupWarning is the negative control so operators don't train themselves to filter the warning out.
Addresses W4 from the PR #343 review. The /v1 back-and-forth on PR #343 (add → revert → re-add) was consistent with a deployed x402-buyer:latest image lagging behind main, and the fix hardcoded /v1 in the LiteLLM template instead of pinning the image. Same risk applies to x402-verifier and serviceoffer-controller which also ship as :latest. - New internal/embed/embed_image_pin_test.go scans every embedded template and fails when a new :latest appears without an allowlist entry. The allowlist currently covers the three obolnetwork images pending digest pinning; each entry carries a short reason. Removing an entry without replacing :latest in the YAML fails the test (stale-allowlist check). - Inline TODO(image-pin) comments in llm.yaml and x402.yaml explain the policy at the point of violation so contributors who touch the deployment spec see it. This does not pin the images (that requires GHCR access to produce the digest) — it establishes the contract and makes drift visible.
a5eb379 to
f56ed02
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked follow-up to #343. Targets
feat/x402-buyer-settlement-hold-sign; GitHub will auto-retarget tomainonce #343 merges.This PR now also folds in the seller-gateway redesign that restores correct settlement ownership for
sell http.What this fixes
OnPaymentUnsettledcallback +obol_x402_buyer_payment_unsettled_confirmations_totalmetric and tests./v1mux behavior could drift from deployedx402-buyerbehaviorVerifyOnly=falseis unsafe on the Traefik auth hopsell httphad no correct settlement owner onceverifyOnly=truebecame permanentsell httpthrough a shared seller-owned x402 gateway that verifies, proxies, settles after upstream success, and emitsX-PAYMENT-RESPONSE.HTTPRoute -> x402/x402-verifierbackend refs were blocked by Gateway API policyReferenceGrants and grant the controller RBAC to manage them.ForwardAuth -> upstreamshapeRuntime architecture
Old
Traefik -> ForwardAuth verifier -> upstreamNew
Traefik -> shared seller-owned x402 gateway -> upstreamThe shared gateway now owns:
402payment requirement generationX-PAYMENT-RESPONSEemission back to the buyerThis is the important semantic fix in the stack:
sell httpnow has the same seller-sideverify -> fulfill -> settleshape thatsell inferencealready had.What changed
Runtime
HTTPRoutebackends forServiceOffers to the sharedx402-verifierService instead of the raw upstream Service.upstreamURLandstripPrefixso the shared gateway can proxy to the real upstream correctly.internal/x402/verifier.go:402X-PAYMENT-RESPONSE/verifyendpoint, but treatverifyOnly=trueas a legacy ForwardAuth safety setting instead of the hot path forsell http.ReferenceGrants forHTTPRoute -> x402/x402-verifierbackend refs.serviceoffer-controllerRBAC to manageReferenceGrants.Tests and flows
flow-11successfully end-to-end.Docs
What this does not fix
8080during bootstrap.Validation
Unit tests
go test ./internal/x402go test ./internal/serviceoffercontrollerIntegration compile / smoke
go test -tags integration ./internal/openclaw -run TestDoesNotExistFlow tests
flows/flow-11-dual-stack.sh41/41Known local environment limitation
go test -tags integration -v -run TestBDDIntegration -timeout 20m ./internal/x4028080Full report
Successful Flow-11 artifacts
Seller
0xC0De030F6C37f490594F93fB99e2756703c4297Ehttps://ten-municipal-mortgages-offerings.trycloudflare.com5003Buyer
0x57b0eF875DeB5A37301F1640E469a2129Da9490E0x5D01290Fd77EbD7a82bD316dC2d762C30B07D107paid/qwen3.5:9bSeller registration receipts
1. Identity registration
0x32eef92ede6779a3d05e780bf0920f4744b22ec6e85eb81d4d83371644d751b94033169884532)register(string)0x8004A818BFB912233c491871b3d84c89A494BD9e0xC0De030F6C37f490594F93fB99e2756703c4297E5003https://ten-municipal-mortgages-offerings.trycloudflare.com/.well-known/agent-registration.jsonObserved receipt facts:
1183900Registered(agentId=5003, agentURI=..., owner=seller)MetadataSet(agentWallet=0xC0De...)2. x402 metadata write
0xab815b78ab688697ef1e39f479bf83d57b53c7afb26c00c85775105d860b1df840331700setMetadata(uint256,string,bytes)x402{"x402":true}Observed receipt facts:
1575520xC0De030F6C37f490594F93fB99e2756703c4297E0x8004A818BFB912233c491871b3d84c89A494BD9eBuyer settlement receipt
3. x402 settlement transfer
0x847de0118110b4f056c025b9804cd82f87274a2b95926419c34ccfc0eb1216a240331811transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,bytes)84532)0x036CbD53842c5426634e7929541eC2318f3dCF7e0xd744494E28b01073514EBC89987B305001ed257A0x5D01290Fd77EbD7a82bD316dC2d762C30B07D1070xC0De030F6C37f490594F93fB99e2756703c4297E1000micro-USDC (0.001 USDC)Observed receipt facts:
186144AuthorizationUsedemitted by USDCTransferemitted from buyer signer to seller for1000Supported x402 chain recipes in this repo
baseeip155:84530x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913base-sepoliaeip155:845320x036CbD53842c5426634e7929541eC2318f3dCF7eethereumeip155:10xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48polygoneip155:1370x3c499c542cEF5E3811e1192ce70d8cC03d5c3359polygon-amoyeip155:800020x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582avalancheeip155:431140xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6Eavalanche-fujieip155:431130x5425890298aed601595a70AB815c96711a31Bc65arbitrum-oneeip155:421610xaf88d065e77c8cC2239327C5EDb3A432268e5831arbitrum-sepoliaeip155:4216140x75faf114eafb1BDbe2F0316DF893fd58CE46AA4dPublic repo note
This PR body intentionally includes only public or operationally non-secret information:
It does not include any private keys, tokens, or
.envsecrets.