Skip to content

feat: BDD integration tests + upstream auth injection + tunnel URL#256

Merged
OisinKyne merged 2 commits intomainfrom
feat/bdd-upstream-auth
Mar 6, 2026
Merged

feat: BDD integration tests + upstream auth injection + tunnel URL#256
OisinKyne merged 2 commits intomainfrom
feat/bdd-upstream-auth

Conversation

@bussyjd
Copy link
Collaborator

@bussyjd bussyjd commented Mar 5, 2026

Summary

  • BDD integration tests (godog/Gherkin): 7 scenarios, 75 steps following the real user journey — no kubectl shortcuts
  • Upstream auth injection: x402-verifier injects Authorization header on paid requests via upstreamAuth field in pricing config
  • Tunnel URL injection: obol tunnel status auto-sets AGENT_BASE_URL on obol-agent so registration JSON uses tunnel URL
  • Scoped RBAC: Agent can only read litellm-secrets in llm namespace (not all secrets cluster-wide)

BDD Scenarios

Scenario What it tests
Operator sells inference via CLI + agent reconciles obol sell http → CRD → reconciliation → Middleware + HTTPRoute + pricing
Unpaid request returns 402 x402 payment gate
Paid request returns real inference EIP-712 → verify → LiteLLM → Ollama
Discovery-to-payment cycle Parse 402 → sign → pay → 200
Paid request through tunnel Full flow via Cloudflare tunnel
Agent discovers service through tunnel .well-known → x402Support → probe 402
Operator deletes + cleanup obol sell delete → CR + route removed

Test plan

# Against existing cluster (~2min)
OBOL_INTEGRATION_SKIP_BOOTSTRAP=true OBOL_TEST_MODEL=qwen3.5:9b \
  go test -tags integration -v -run TestBDDIntegration -timeout 10m ./internal/x402/

# Full bootstrap from scratch (~15min)
go test -tags integration -v -run TestBDDIntegration -timeout 20m ./internal/x402/

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 RouteRule
  • internal/x402/verifier.go — inject Authorization on approved requests
  • internal/embed/skills/sell/scripts/monetize.py — read LiteLLM master key, write upstreamAuth
  • internal/tunnel/tunnel.go — InjectBaseURL + auto-inject on status
  • internal/embed/.../obol-agent-monetize-rbac.yaml — scoped Role for litellm-secrets
  • go.mod/go.sum — godog dependency

bussyjd added 2 commits March 5, 2026 22:38
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
@bussyjd bussyjd requested a review from OisinKyne March 5, 2026 21:40
@bussyjd
Copy link
Collaborator Author

bussyjd commented Mar 5, 2026

Sell → Discover → Buy: Three-Layer Architecture

This 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)

Contract: 0x8004A818BFB912233c491871b3d84c89A494BD9e (Base Sepolia)
Standard: ERC-721 + ERC-8004 extensions

When a seller runs obol sell http --register, the agent calls register(string agentURI) on the registry. This mints an NFT with:

Field Example Source
tokenId 1309 Auto-incremented by registry
tokenURI https://<tunnel>/.well-known/agent-registration.json Set by seller (uses AGENT_BASE_URL)
ownerOf 0x4fed... Agent's remote-signer wallet
agentWallet 0x4fed... ERC-8004 specific getter

Buyer discovery methods:

  • eth_getLogs(Registered topic) → scan recent registrations (bounded to last 10k blocks)
  • tokenURI(id) → get the URL to the registration document
  • ownerOf(id) → verify agent identity

Live query (real Base Sepolia):

$ cast call 0x8004...BD9e "tokenURI(uint256)(string)" 1336 --rpc-url https://sepolia.base.org
"https://work402.com/api/agents/metadata/0x63f1261baC6C938B4f5B4F9E27726C5316F72dF6"

Layer 2: Off-Chain (Registration JSON)

The tokenURI resolves to a registration document served at /.well-known/agent-registration.json via the Cloudflare tunnel:

{
  "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:

  • x402Support: true → this agent accepts x402 payments
  • services[0].endpoint → the URL to probe for pricing
  • registrations[0] → back-reference to on-chain identity for trust verification

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 X-PAYMENT header → 200 with real inference response.

Key Design: Only the Pointer is On-Chain

On-chain (immutable, verifiable):
  tokenURI → URL pointer
  ownerOf  → agent identity

Off-chain (mutable, fetched at discovery time):
  Registration JSON → name, services, x402Support, active flag

Service layer (real-time):
  402 response → current pricing, payTo, network

The 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 Adds

The BDD scenarios validate this full loop:

  1. Sellobol sell http creates ServiceOffer, agent reconciles (6 stages)
  2. Register → agent mints NFT, publishes .well-known JSON via tunnel
  3. Discover → buyer fetches registration from tunnel, extracts x402 endpoint
  4. Probe → buyer hits endpoint, gets 402 with pricing
  5. Pay → buyer signs EIP-712, sends X-PAYMENT → 200 + inference
  6. Auth injection → x402-verifier reads upstreamAuth from pricing config, injects Authorization: Bearer <litellm-key> on approved requests (no manual HTTPRoute patches)

@OisinKyne OisinKyne merged commit 9ccf33b into main Mar 6, 2026
6 checks passed
@OisinKyne OisinKyne deleted the feat/bdd-upstream-auth branch March 6, 2026 00:21
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.

2 participants