jobbroker

package
v0.14.0-rc1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package jobbroker is the stack's async job broker: it accepts paid requests the x402-verifier has already verified + settled, replays them against the offer's real upstream with no client-facing deadline, and serves free status pages plus gated results. One broker per stack, multi-tenant across offers; jobs are runtime data in SQLite on a PVC — deliberately NOT CRDs (etcd write amplification, 1.5MiB caps on result bodies, TTL churn) and NOT k8s Jobs (the work is an HTTP replay against a running Service, not a batch pod).

Index

Constants

View Source
const (
	HeaderUpstreamURL  = "X-Obol-Upstream-Url"
	HeaderOffer        = "X-Obol-Offer"
	HeaderPayTo        = "X-Obol-Pay-To"
	HeaderJobTTL       = "X-Obol-Job-Ttl"
	HeaderVisibility   = "X-Obol-Result-Visibility"
	HeaderPublicPrefix = "X-Obol-Public-Prefix"
	HeaderUpstreamAuth = "X-Obol-Upstream-Auth"

	// Set by the verifier from verified credentials.
	HeaderPaymentPayer   = "X-Payment-Payer"
	HeaderVerifiedWallet = "X-Verified-Wallet"
)

Contract headers between the x402-verifier and the broker. The verifier sets all of them (client-supplied copies never survive the gate), so the broker can stay stateless about offers: everything it needs rides the request.

View Source
const (
	StatePending  = "pending"
	StateRunning  = "running"
	StateComplete = "complete"
	StateFailed   = "failed"
)

Job states.

View Source
const HeaderBrokerSig = "X-Obol-Broker-Sig"

HeaderBrokerSig carries the verifier's HMAC over the contract headers, so the broker can reject forged submits even from a pod the NetworkPolicy allows (defense in depth with the F1 NetworkPolicy). Mirrored in internal/x402 (see authgate.go) — the two packages don't share code.

Variables

This section is empty.

Functions

func NewID

func NewID() string

NewID returns a 128-bit random hex id. Unguessability is load-bearing: in public-visibility mode the id IS the result capability.

Types

type Job

type Job struct {
	ID        string
	Token     string // capability credential returned in the 202 body
	Offer     string // "<namespace>/<name>"
	Payer     string // wallet that paid (lowercase 0x…), may be empty
	PayTo     string // offer's payTo wallet (sellers list their jobs with it)
	State     string
	CreatedAt time.Time
	StartedAt time.Time
	DoneAt    time.Time
	ExpiresAt time.Time

	Method      string
	Path        string // path relative to the offer root, e.g. /submit
	ContentType string
	Body        []byte

	UpstreamURL string // in-cluster base URL to replay against
	CallbackURL string // optional completion webhook
	Visibility  string // payer | public

	ResultStatus      int
	ResultContentType string
	Result            []byte
	Error             string
}

Job is one accepted async request and (eventually) its stored result.

type ListSummary

type ListSummary struct {
	ID        string    `json:"jobId"`
	State     string    `json:"state"`
	Path      string    `json:"path"`
	CreatedAt time.Time `json:"createdAt"`
}

ListSummary is the /jobs listing row (no bodies).

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is the broker's HTTP surface.

func NewServer

func NewServer(store *Store) *Server

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the broker mux. The verifier strips the offer prefix before proxying, so paths arrive offer-relative:

/jobs                → list (wallet-scoped)
/jobs/<id>           → status (free; JSON or HTML by Accept)
/jobs/<id>/result    → stored result (visibility-gated)
anything else        → submit (a paid request the verifier settled)

func (*Server) RunSweeper

func (s *Server) RunSweeper(interval time.Duration, stop <-chan struct{})

RunSweeper deletes expired jobs every interval until stop is closed.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store persists jobs. All methods are safe for concurrent use (database/sql pools; SQLite serializes writers).

func OpenStore

func OpenStore(path string) (*Store, error)

OpenStore opens (creating if needed) the job database at path. Use ":memory:" for tests.

func (*Store) Close

func (s *Store) Close() error

func (*Store) Create

func (s *Store) Create(j *Job) error

func (*Store) Finish

func (s *Store) Finish(id string, status int, contentType string, body []byte, errMsg string, at time.Time) error

Finish records the upstream outcome. status<400 → complete, else failed (money already settled at acceptance — the status page says so plainly).

func (*Store) Get

func (s *Store) Get(id string) (*Job, error)

func (*Store) ListForWallet

func (s *Store) ListForWallet(offer, wallet string, limit int) ([]ListSummary, error)

ListForWallet returns jobs the wallet may see under one offer: all of the offer's jobs when the wallet is the offer's payTo (the seller), else only jobs the wallet paid for.

func (*Store) MarkRunning

func (s *Store) MarkRunning(id string, at time.Time) error

func (*Store) SweepExpired

func (s *Store) SweepExpired(now time.Time) (int64, error)

SweepExpired deletes jobs past their TTL. Returns rows removed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL