feat: BDD integration tests + upstream auth injection + tunnel URL#256
feat: BDD integration tests + upstream auth injection + tunnel URL#256
Conversation
Adds the full sell→discover→buy BDD test suite and the upstream auth injection mechanism, rebased cleanly on current main. ## BDD Integration Tests (godog/Gherkin) 7 scenarios, 75 steps, following the real user journey: 1. Operator sells inference via CLI + agent reconciles 2. Unpaid request returns 402 with pricing 3. Paid request returns real inference (EIP-712 → verify → Ollama) 4. Full discovery-to-payment cycle 5. Paid request through Cloudflare tunnel 6. Agent discovers registered service through tunnel 7. Operator deletes ServiceOffer + cleanup TestMain bootstrap: obol stack init/up → model setup → sell pricing → agent init → sell http → wait for reconciliation. No kubectl shortcuts. ## Upstream Auth Injection x402-verifier now injects Authorization header on paid requests: - RouteRule.UpstreamAuth field in pricing config - Verifier sets header in 200 response → Traefik copies via authResponseHeaders - monetize.py reads LiteLLM master key → writes upstreamAuth to route - Eliminates manual HTTPRoute RequestHeaderModifier patches ## Tunnel URL Injection `obol tunnel status` auto-sets AGENT_BASE_URL on the obol-agent deployment. monetize.py reads it to publish the tunnel URL in registration JSON. Files: - internal/x402/features/integration_payment_flow.feature (new) - internal/x402/bdd_integration_test.go (new) - internal/x402/bdd_integration_steps_test.go (new) - internal/x402/config.go (UpstreamAuth field) - internal/x402/verifier.go (inject Authorization on 200) - internal/embed/skills/sell/scripts/monetize.py (read master key, upstreamAuth) - internal/tunnel/tunnel.go (InjectBaseURL, auto-inject on status) - internal/embed/infrastructure/.../obol-agent-monetize-rbac.yaml (secrets:get)
The previous commit added secrets:get to the cluster-wide openclaw-monetize-workload ClusterRole, which gave the agent read access to ALL secrets in ALL namespaces. Fix: remove secrets from ClusterRole and add a namespaced Role in the llm namespace scoped to litellm-secrets only via resourceNames restriction. Same pattern as the existing openclaw-x402-pricing Role in the x402 namespace. Verified: - Agent can read litellm-secrets in llm namespace (200 OK) - Agent cannot list kube-system secrets (403 Forbidden) - All 7 BDD scenarios pass with scoped RBAC
Sell → Discover → Buy: Three-Layer ArchitectureThis PR implements the full loop. Here's how the data flows from seller registration to buyer discovery to paid inference. Layer 1: On-Chain (ERC-8004 Identity Registry)When a seller runs
Buyer discovery methods:
Live query (real Base Sepolia): Layer 2: Off-Chain (Registration JSON)The {
"type": "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
"name": "Obol Inference Agent",
"description": "Claude Sonnet 4.5 via LiteLLM, x402 micropayments",
"x402Support": true,
"active": true,
"services": [
{
"name": "web",
"endpoint": "https://cutting-generators-wrap-tab.trycloudflare.com/services/sell-inference"
}
],
"registrations": [
{
"agentId": 1309,
"agentRegistry": "eip155:84532:0x8004A818BFB912233c491871b3d84c89A494BD9e"
}
]
}The buyer checks:
Layer 3: Service Endpoint (x402 Payment Gate)The buyer probes the discovered endpoint. Without payment → 402 with full pricing: {
"x402Version": 1,
"accepts": [{
"payTo": "0x1Ac98fa6E41FBD7ECC7E11961e5762603Aadd1f2",
"maxAmountRequired": "1000",
"network": "base-sepolia",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"extra": {"name": "USDC", "version": "2"}
}]
}With a signed EIP-712 Key Design: Only the Pointer is On-ChainThe full metadata, services, and pricing are off-chain but cryptographically linked via the agent NFT ownership. This means agents can update their services and pricing without on-chain transactions, while buyers can always verify identity through the registry. What This PR AddsThe BDD scenarios validate this full loop:
|
Summary
upstreamAuthfield in pricing configobol tunnel statusauto-setsAGENT_BASE_URLon obol-agent so registration JSON uses tunnel URLlitellm-secretsinllmnamespace (not all secrets cluster-wide)BDD Scenarios
obol sell http→ CRD → reconciliation → Middleware + HTTPRoute + pricing.well-known→ x402Support → probe 402obol sell delete→ CR + route removedTest plan
Files changed
internal/x402/features/integration_payment_flow.feature(new)internal/x402/bdd_integration_test.go(new)internal/x402/bdd_integration_steps_test.go(new)internal/x402/config.go— UpstreamAuth field on RouteRuleinternal/x402/verifier.go— inject Authorization on approved requestsinternal/embed/skills/sell/scripts/monetize.py— read LiteLLM master key, write upstreamAuthinternal/tunnel/tunnel.go— InjectBaseURL + auto-inject on statusinternal/embed/.../obol-agent-monetize-rbac.yaml— scoped Role for litellm-secretsgo.mod/go.sum— godog dependency