Skip to content

fix: align relay payment polling contract with tx-schemas#95

Merged
whoabuddy merged 5 commits intomainfrom
fix/align-relay-payment-polling-contract
Apr 3, 2026
Merged

fix: align relay payment polling contract with tx-schemas#95
whoabuddy merged 5 commits intomainfrom
fix/align-relay-payment-polling-contract

Conversation

@whoabuddy
Copy link
Copy Markdown
Contributor

Summary

  • align relay payment polling responses with @aibtc/tx-schemas so canonical parsing is shared across middleware and test retry logic
  • surface canonical paymentId, status, terminalReason, retryable, and additive checkStatusUrl on paid retry errors when known
  • keep caller-facing submitted collapsed to queued and confirmed-only delivery as the default

Contract Alignment Completed

  • added a shared payment-status adapter backed by @aibtc/tx-schemas for canonical-first parsing and compatibility-only inference
  • updated middleware classification and retry shaping to prefer canonical states and terminal reasons, including checkStatusUrl passthrough
  • added structured payment observability fields so payment logs consistently include paymentId, status, terminalReason, checkStatusUrl_present, and compat-shim markers
  • updated /, x402.json, AX discovery docs, and shared OpenAPI 402 response docs so public docs match runtime fields

Simplifications

  • removed duplicated endpoint retry/signing flow in the test harness by routing through the shared retry helper
  • removed duplicated Cloudflare tokenType parameter schema and reused the shared schema response definition
  • centralized public payment lifecycle metadata for /, x402.json, and docs instead of repeating the state list inline

Compatibility / Rollout Notes

  • canonical-first polling remains primary; legacy error-string/status inference is still present only as a compatibility shim when the relay response is missing canonical fields
  • checkStatusUrl remains additive and depends on upstream relay responses surfacing it; older relay responses still work through the compat path
  • downstream clients in other repos should continue treating paymentId as relay-owned and terminalReason as the normalized terminal signal

Verification

  • npm run check
  • bun test tests/openrouter-validation.unit.test.ts tests/cloudflare-ai-fallback.unit.test.ts tests/model-cache.unit.test.ts tests/payment-status.unit.test.ts tests/payment-observability.unit.test.ts

Residual Risk / Follow-up

  • OpenAPI now documents the canonical paid-retry 402 body, but any external generated clients need to refresh from the updated schema to pick up terminalReason and checkStatusUrl
  • as long as some facilitator responses are still legacy-shaped, the compatibility shim will remain exercised until rollout is complete in the upstream relay/client path

Copilot AI review requested due to automatic review settings April 3, 2026 00:38
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 3, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
x402-api-production 6df332b Apr 03 2026, 07:13 PM

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ddd7824d25

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/middleware/x402.ts Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aligns relay payment polling / retry responses with @aibtc/tx-schemas so middleware classification, retry logic, logs, and public docs all share the same canonical parsing and surfaced fields.

Changes:

  • Added canonical-first payment status adapter + retry-decision helpers (with legacy compat shims) and corresponding unit tests.
  • Updated x402 middleware to classify retry behavior based on canonical states/terminal reasons and to emit structured payment observability fields.
  • Refactored test harness retry flow to reuse a shared helper; updated OpenAPI/docs to document canonical 402 retry bodies + lifecycle metadata.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tests/payment-status.unit.test.ts Adds unit coverage for canonical/compat payment-status parsing and retry context helpers.
tests/payment-observability.unit.test.ts Adds unit coverage for structured payment log fields and instability classification.
tests/_shared_utils.ts Enhances retry parsing/shaping to use canonical retry context; consolidates retry flow.
tests/_run_all_tests.ts Switches endpoint lifecycle tests to the shared retry helper and removes duplicated signing logic.
src/utils/x402-schema.ts Adds lifecycle metadata to the generated x402 manifest.
src/utils/payment-status.ts Introduces canonical-first payment status adapter backed by @aibtc/tx-schemas.
src/utils/payment-observability.ts Adds shared structured payment log helpers + instability derivation.
src/utils/payment-contract.ts Centralizes public payment lifecycle metadata/constants used across docs/manifests.
src/services/pricing.ts Hardens multimodal message token estimation by normalizing message content handling.
src/middleware/x402.ts Prefers canonical status/terminalReason for classification, shapes paid-retry errors, and emits structured payment observability logs.
src/index.ts Surfaces lifecycle metadata in / response + docs text alignment.
src/endpoints/schema.ts Updates shared OpenAPI 402 schema to document unpaid challenge vs canonical paid-retry error bodies.
src/endpoints/inference/openrouter/chat.ts Reuses shared response402 schema in endpoint docs.
src/endpoints/inference/cloudflare/chat.ts Reuses shared tokenTypeParam and shared response402 schema in endpoint docs.
src/endpoints/ax-discovery.ts Updates discovery docs/examples to match x402 v2 structures + canonical lifecycle and retry fields.
package.json Adds @aibtc/tx-schemas dependency for canonical parsing.
package-lock.json Locks @aibtc/tx-schemas and updates zod resolution accordingly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/utils/payment-status.ts
Comment thread src/middleware/x402.ts Outdated
Comment thread src/middleware/x402.ts
Comment thread src/middleware/x402.ts
Comment thread src/utils/payment-observability.ts Outdated
Comment thread tests/_shared_utils.ts Outdated
Copy link
Copy Markdown

@biwasxyz biwasxyz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phase 6 rollout review

Clean and well-aligned. All 7 review comments were addressed in the follow-up commit, and the canonical vocabulary is used consistently throughout.

What looks good

  • HttpPaymentStatusResponseSchema, TrackedPaymentStateSchema, TerminalReasonSchema all imported from @aibtc/tx-schemas
  • terminalReason preserved as the normalized semantic signal in error responses, logs, and discovery docs
  • paymentId relay-owned throughout, extracted via extractPaymentId()
  • checkStatusUrl additive when present, never required
  • Immediate pay-per-call behavior correctly preserved as the documented service-level exception
  • STATUS_ALIASES map (submittedqueued, queued_with_warningqueued) is appropriate local compat logic
  • PAYMENT_REPO_VERSION now sourced from package.json instead of hardcoded
  • All review feedback resolved in code

Verdict

Ready to merge. No open items.

Copy link
Copy Markdown
Contributor

@arc0btc arc0btc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Centralizes canonical payment status parsing from @aibtc/tx-schemas into dedicated utility modules, replacing ad-hoc error-string matching with schema-validated extraction and a structured compat shim for legacy relay responses.

What works well:

  • payment-status.ts is well-composed — firstExtracted, noneExtracted, ExtractedField<T> make the compat-shim tracking explicit and testable without leaking state
  • isInFlightPaymentState delegates to InFlightPaymentStateSchema.safeParse() rather than reimplementing string matching — schema stays the single source of truth
  • Canonical-first ordering in classifyPaymentError is correct: check canonical.status before falling back to combined-string heuristics, which prevents misclassification when the relay does return structured fields
  • The priority reordering of fallback checks (relay-specific broadcast/pending before generic nonce/expired) is the right call — relay errors are more precise and shouldn't be shadowed by broad string patterns
  • Adding @aibtc/tx-schemas as a direct dependency locks the contract boundary instead of inferring it

[question] Double log on failure path (src/middleware/x402.ts:554–635)
When settleResult fails, the code emits either payment.poll or payment.finalized, then always emits payment.retry_decision after. That's two events per failure — intentional for structured retry observability, or will this double the noise on dashboards? Worth confirming the log consumer expects both events.

[suggestion] getRetryDecisionContext is defined but not wired (src/utils/payment-status.ts)
getRetryDecisionContext is exported from payment-status.ts but x402.ts uses the inline getRetryAction function instead. Either wire getRetryDecisionContext into getRetryAction to keep the logic in one place, or remove getRetryDecisionContext to avoid a dead export that could confuse future callers.

[nit] pricing.ts type cast (src/services/pricing.ts:184)
const content = msg?.content as unknown is a workaround to avoid a TypeScript narrowing error on the union type. The behavior is correct, but the cast is worth a comment explaining why it's needed, or fixing the upstream type if ChatCompletionRequest["messages"] can be narrowed more precisely.

Code quality notes:

  • The test harness deduplication (routing through makeX402RequestWithRetry instead of the inline retry loop) is a real simplification — the removed loop was ~100 lines and the shared helper handles nonce-conflict semantics consistently
  • Removing EXPECTED_ASSETS from _run_all_tests.ts and consolidating into _shared_utils is the right move; asset constants had no business living in the orchestrator

Operational context:
We run x402 payments through this relay in production with effectiveCapacity=1 and have watched ghost nonces (sender_nonce_duplicate, sender_nonce_stale) cascade into opaque 402s. This PR gives clients terminalReason and checkStatusUrl on those failures — exactly what's needed for informed retry decisions instead of blind backoff. isSenderRebuildTerminalReason covers all three nonce terminal reasons we've seen in the wild.

- Add comment on getRetryDecisionContext explaining it's used by test
  utilities, not production middleware (not dead code)
- Add comment on dual payment.poll/payment.retry_decision emission
  explaining dashboard vs alerting consumer split
- Add comment on `as unknown` cast in estimateInputTokens explaining
  OpenRouter SDK type narrowing mismatch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@whoabuddy whoabuddy merged commit 42bc614 into main Apr 3, 2026
5 checks passed
@whoabuddy whoabuddy deleted the fix/align-relay-payment-polling-contract branch April 3, 2026 21:25
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.

4 participants