Refactor monetize reconciliation into the serviceoffer controller#299
Draft
bussyjd wants to merge 11 commits intofeat/monetize-pathfrom
Draft
Refactor monetize reconciliation into the serviceoffer controller#299bussyjd wants to merge 11 commits intofeat/monetize-pathfrom
bussyjd wants to merge 11 commits intofeat/monetize-pathfrom
Conversation
Resolve CLI/ERC-8004 conflicts for the ServiceOffer controller branch and replace the buyer proxy's x402 retry transport with a replay-safe local implementation so request bodies survive 402 retries under Go 1.26.
Collaborator
Author
|
Validation rerun on Passed: go test ./internal/embed ./internal/serviceoffercontroller ./internal/erc8004 ./internal/kubectl ./internal/schemasgo test ./cmd/obol ./internal/network ./internal/tunnel ./internal/x402/...python3 -m unittest tests/test_sell_registration_metadata.pypython3 -m py_compile internal/embed/skills/sell/scripts/monetize.pyNotes:
|
When OBOL_DEVELOPMENT=true, Docker builds from the project root pick up .workspace/data/ directories that contain root-owned PVC mounts from previous clusters, causing "permission denied" errors during context scanning. Exclude .workspace/ and .worktrees/ from the Docker build context via .dockerignore. Fixes #304
badcfc0 to
98fc024
Compare
Collaborator
Author
|
Follow-up validation after rerunning the flow suite on What I fixed while running the flows:
Flow status after those fixes:
Concrete seller/buyer proof from the successful rerun:
Root cause of the earlier buy failure:
|
fix: exclude .workspace from Docker build context (#304)
6 tasks
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
This PR moves sell-side monetization reconciliation out of the obol-agent runtime and into a dedicated Kubernetes controller.
The main outcome is that
ServiceOfferbecomes the single source of truth for monetized HTTP offers, while the request path (x402-verifier) remains a separate stateless service that derives live routes directly from Kubernetes state instead of a shared mutable ConfigMap.Why
The previous flow relied on
monetize.pyrunning inside the agent runtime, periodic polling, and imperative mutation of shared x402 pricing config. That created a few structural problems:x402-pricing), which introduced race conditions and cleanup complexityThis PR keeps the controller and verifier separate, but gives each a cleaner boundary:
serviceoffer-controllerowns cluster convergence and registration lifecyclex402-verifierowns only the live payment-gating request pathWhat changed
1. Added a dedicated
serviceoffer-controllercmd/serviceoffer-controllerinternal/serviceoffercontrollerServiceOfferand reconciles the Kubernetes resources needed to publish a paid routestatus.conditionsandstatus.observedGeneration2. Kept
ServiceOfferas the source of truthAn earlier design path introduced a separate
PaymentRouteprojection. This PR intentionally does not keep that layer.Instead:
ServiceOfferremains the only dynamic intent objectServiceOfferServiceOfferdirectly and rebuilds its in-memory route table from informer-backed cluster stateThis keeps the model simpler and avoids duplicating routing state in another CRD.
3. Isolated registration side effects with
RegistrationRequestRegistrationRequestCRD4. Simplified
x402-verifierx402-verifierno longer relies on the dynamicx402-pricingConfigMap as the live source for per-offer routesServiceOfferobjects/.well-known/agent-registration.jsonis no longer served by the verifier, which reduces the amount of non-request-path responsibility in that service5. Reduced agent-side monetization responsibilities
internal/embed/skills/sell/scripts/monetize.pyinto a much thinner compatibility layer6. Updated schemas, docs, and tests to match the new model
skillsanddomainsflow through the new control planeKey files
cmd/serviceoffer-controller/main.gointernal/serviceoffercontroller/controller.gointernal/serviceoffercontroller/render.gointernal/embed/infrastructure/base/templates/registrationrequest-crd.yamlinternal/embed/infrastructure/base/templates/serviceoffer-crd.yamlinternal/embed/infrastructure/base/templates/x402.yamlinternal/embed/infrastructure/base/templates/obol-agent-monetize-rbac.yamlinternal/x402/serviceoffer_source.gointernal/x402/verifier.gointernal/embed/skills/sell/scripts/monetize.pyValidation
Passed locally:
go test ./... -run TestDoesNotExistgo test -c -tags integration -o /tmp/obol-integration-compile/openclaw.test ./internal/openclawgo test -c -tags integration -o /tmp/obol-integration-compile/x402.test ./internal/x402Runtime integration note
I also made a real integration attempt with:
go test -tags integration -run TestIntegration_PaymentGate_FullLifecycle -timeout 30m ./internal/x402That now gets past the earlier missing-
k3dbootstrap blocker, creates the cluster, and entersobol stack up, but it did not complete within the session because it was still in the Docker image build/bootstrap path for the x402 stack. I am calling that out here because it is a meaningful improvement over the previous blocked state, but it is not yet a completed end-to-end runtime pass.Follow-up
ServiceOfferdirect watch instead of introducingPaymentRoute); I attempted this through the GitHub app, but the app did not have permission to comment on the issue in this repo