Commit 176758c
authored
x402 pre-merge follow-up: seller gateway, unsettled metric, verifyOnly warning (#345)
* feat(x402/buyer): detect 2xx without X-PAYMENT-RESPONSE and expose via 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.
* fix(x402/forwardauth): warn on verifyOnly=false, shrink facilitator timeout 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.
* chore(embed): lint :latest image tags with pin-by-digest policy
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.
* feat(x402): route sell http through seller gateway
* fix(obolup): harden installer writes and tty prompts
* docs: keep seller gateway report in pr body only
* feat(model): add master token accessor (#347)
* feat(sell): register by default with explicit opt-out (#349)
* feat(sell): warn that --register is off-chain only
* feat(sell): register by default with explicit opt-out
* feat(sell): show registration summary in sell status
* test(sell): cover registration defaults and sync skill docs
* feat(openclaw): surface generated agent wallet (#348)
* fix(stack): preload openclaw image in dev k3d (#352)
* feat(x402-buyer): expose confirm-spend persistence failures (#351)
---------
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>1 parent 96d7f12 commit 176758c
File tree
44 files changed
+1893
-423
lines changed- cmd
- obol
- x402-verifier
- docs
- guides
- flows
- internal
- embed
- infrastructure/base/templates
- skills
- autoresearch-worker
- references
- monetize-guide
- sell
- references
- scripts
- monetizeapi
- openclaw
- serviceoffercontroller
- stack
- x402
- buyer
- features
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
44 files changed
+1893
-423
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
34 | 57 | | |
35 | 58 | | |
36 | 59 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
275 | 275 | | |
276 | 276 | | |
277 | 277 | | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
278 | 308 | | |
279 | 309 | | |
280 | 310 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
0 commit comments