feat: ServiceOffer controller + PaymentRoute CRD (replaces monetize.py)#298
Open
feat: ServiceOffer controller + PaymentRoute CRD (replaces monetize.py)#298
Conversation
Event-driven controller-runtime reconciler for ServiceOffer CRDs, independent of the obol-agent runtime. Replaces the Python polling loop with proper K8s informer watches, finalizers, and status conditions. Reconciler creates/manages: - Traefik Middleware (ForwardAuth → x402-verifier) - x402-pricing ConfigMap route entries - Gateway API HTTPRoute (/services/<name>/*) On deletion: finalizer removes pricing route; ownerRefs cascade the rest. Status conditions: UpstreamHealthy, PaymentGateReady, RoutePublished, Ready with observedGeneration tracking. Phase 1 (KISS): keeps ConfigMap for pricing (no PaymentRoute CRD yet), skips ERC-8004 registration (stays in monetize.py for now). Includes: - cmd/serviceoffer-controller/ — binary entrypoint - internal/controller/ — reconciler, helpers, resource builders, tests - Dockerfile.serviceoffer-controller — distroless image - K8s manifests (SA, ClusterRole, Deployment in obol-system ns) Refs: #296
Replace ConfigMap string mutation with PaymentRoute CRD. Each ServiceOffer now creates an owned PaymentRoute CR in the x402 namespace. The verifier watches these via dynamic informer instead of polling a file. Changes: - PaymentRoute CRD (obol.org/v1alpha1) with spec and status.admitted - Controller creates PaymentRoute CRs via createOrUpdate (not ConfigMap) - Verifier: --route-source=paymentroute (default) watches CRs, --route-source=configmap falls back to legacy file watcher - PaymentRouteSource (internal/x402/source/) builds route table from CRs and marks status.admitted when loaded - Verifier.Config() accessor for PaymentRouteSource to read globals - Removed .well-known handler from verifier (per issue #296) - 16 unit tests across controller and source packages No backward compat with ConfigMap pricing — this is the new path. monetize.py deletion and CLAUDE.md updates in next commit. Refs: #296
…cture - Delete monetize.py (1927 lines) — replaced by serviceoffer-controller - Delete watcher.go (57 lines) — replaced by PaymentRoute informer - Update CLAUDE.md: document PaymentRoute CRD, serviceoffer-controller, verifier PaymentRoute watch. Remove references to monetize.py and ConfigMap-based pricing. Refs: #296
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.
Summary
Production-ready Kubernetes controller for ServiceOffer CRDs using
controller-runtime. Replaces the 1927-linemonetize.pyPython reconciliation loop with a generation-driven Go operator. IntroducesPaymentRouteCRD to eliminate the ConfigMap mutation race. Refactors the x402-verifier to watch PaymentRoute CRs via dynamic informer.Refs: #296
Architecture
Two Deployments, one binary family:
serviceoffer-controller(obol-system ns)x402-verifier(x402 ns)What changed
monetize.pypolls every 10-60sphasestringobservedGenerationFiles
cmd/serviceoffer-controller/main.gointernal/controller/reconciler.gointernal/controller/helpers.gointernal/controller/resources.gointernal/controller/reconciler_test.gointernal/x402/source/paymentroute.gointernal/x402/source/paymentroute_test.gointernal/x402/verifier.gocmd/x402-verifier/main.gointernal/embed/.../paymentroute-crd.yamlinternal/embed/.../serviceoffer-controller.yamlDockerfile.serviceoffer-controllerinternal/embed/skills/sell/scripts/monetize.pyinternal/x402/watcher.goCLAUDE.mdNet: +1,900 lines Go, -1,984 lines Python
Test plan
go build ./...passesgo test ./internal/controller/— 12 tests pass (spec parsing, conditions, ownerRefs, phases)go test ./internal/x402/source/— 4 tests pass (PaymentRoute conversion, validation)obol sell http→ verify PaymentRoute CR created → verify 402 response