Documentation
¶
Overview ¶
Package marketplace is the shop for tools and agents: browse, install into your project, publish your own free or priced.
It serves listing, discovery and install per org/project at /v1/marketplace. A monetized listing declares a price + recipient wallet and enforces through the x402 seam.
It is a THIN layer over the unified tool plane (apps/tools): discovery reads the tool registry (every source, activated flags); "install"/"uninstall" ARE the registry's activation writes (marketplace install == tool activation — one store, one truth); and a monetized listing's price is enforced per call by x402, which this package hands both halves of the door — the price table and the charger (see payments.go). Marketplace never dispatches a tool itself and never moves money itself.
TWO TRANSPORTS, ONE POLICY — because the shipped topology has no co-residency. The three seams this package binds are process-globals (x402.reg, tools.std, and wallets' mounted singleton), so they bind within ONE process, and the fleet runs ONE PROCESS PER APP. That is not a possible future deployment, it is the only one: manifest/apps.go declares marketplace, tools, x402 and wallets as four ordinary prefix-routed rows (only `zen` is Coresident, manifest/apps.go:194); the Dockerfile builds a plugin binary per row and cmd/cloud loads each as its own CHILD PROCESS (cmd/cloud/main.go:257, zip.Load on a binary path); and the fused monolith that linked every subsystem was deleted (cmd/cloud/main.go:1-9).
So the wiring above bound nothing in production, and a listed tool was not merely unbuyable — it was FREE. The tools process refuses a dispatch whose registry ROW declares a price, but a marketplace price lives in the listing store, so a listing on a tool that declares none was dispatched for nothing.
The fix is the internal plane (resource_billing_peer.go is the precedent: the same split turned every priced create free, and the answer was to ASK the owning process). Four ops, each served by the process that owns the answer:
tools → x402 x402_settle settle this tool call (apps/x402/rpc.go) x402 → marketplace market_price what it costs, who is paid (rpc.go here) x402 → wallets wallets_payee resolve the payee wallet (apps/wallets/rpc.go) x402 → commerce finance_credit credit the payee (apps/commerce/credit_rpc.go)
The in-process seam stays the FAST PATH where the owner is co-resident; the plane answers where it is not. Both are the same policy and both fail closed, which is what payments_test.go (one process) and split_test.go (five real processes) assert against each other.
Surface (all org-gated, /v1 only):
GET /v1/marketplace discovery: catalog (tools+agents) + listing overlay + installed flag GET /v1/marketplace/listings the caller org's own published listings POST /v1/marketplace/listings publish a listing (optionally monetized) DELETE /v1/marketplace/listings/:id unpublish POST /v1/marketplace/install install (activate) a tool for the caller's (org,project) POST /v1/marketplace/uninstall uninstall (deactivate) a tool
Index ¶
- func Mount(app cloud.Router, deps cloud.Deps) error
- func Shutdown(_ context.Context) error
- type Listing
- type Store
- func (s *Store) CheapestPublicForTool(ctx context.Context, tool string) (Listing, bool, error)
- func (s *Store) Close() error
- func (s *Store) Create(ctx context.Context, l Listing) (Listing, error)
- func (s *Store) Delete(ctx context.Context, org, id string) (bool, error)
- func (s *Store) ListByOrg(ctx context.Context, org string) ([]Listing, error)
- func (s *Store) ListPublic(ctx context.Context, limit int) ([]Listing, error)
- func (s *Store) PublicByTool(ctx context.Context) (map[string]Listing, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Mount ¶
Mount wires /v1/marketplace/* and closes the payment seam both ways, so a published listing's price is challenged and settled at every call.
func Shutdown ¶
Shutdown detaches the payment seams and closes the store, in that order. Idempotent.
Detaching first is the whole point: both seams close over the store, so leaving them installed past Close would leave a price table answering from a closed database — and a price lookup that errors fails a dispatch closed, turning a clean shutdown into 402s on every tool in the process. Nothing priced, nothing charged, no dangling reader.
Types ¶
type Listing ¶
type Listing struct {
ID string `json:"id"`
PublisherOrg string `json:"publisherOrg"`
Tool string `json:"tool"`
Title string `json:"title"`
Description string `json:"description"`
Category string `json:"category"`
Price money.Amount `json:"price"` // exact per-call price; 0 is free.
Currency string `json:"currency"`
Recipient string `json:"recipient"` // seller payout WALLET ID, in PublisherOrg.
Public bool `json:"public"`
CreatedAt int64 `json:"createdAt"`
}
Listing is one marketplace offer: a tool/agent surfaced for discovery and install, optionally monetized (Price>0) with a seller payout Recipient wallet. The tool name is a registry tool (any source). Isolation is the (publisher_org, id) key — a publisher only ever mutates its OWN listings.
PublisherOrg is also the PAYEE org: a listing is paid into a wallet of the org that published it and no other, which is what makes a cross-org credit unconstructible rather than merely unlikely (wallets resolves an id only within the org that is asked for).
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the marketplace listing store (one SQLite file, publisher_org column, the shared cloud store discipline).
func (*Store) CheapestPublicForTool ¶
CheapestPublicForTool returns the cheapest public MONETIZED listing for a tool — the row the x402 price table answers from. One indexed lookup (ix_listings_public_tool covers public+tool) on the dispatch hot path.
The minimum is taken in Go, on money.Amount, not in SQL. An 18-decimal price is stored as its exact magnitude and $10 is 10^19 — past int64 — so ORDER BY CAST(… AS INTEGER) would silently mis-order the expensive half of the shop. Money is compared by the type that owns comparison, in one place, always.
A query failure is RETURNED, never swallowed: the payment gate must fail closed on a blip, not conclude the tool is free.
func (*Store) Delete ¶
Delete removes a listing for (publisher_org, id). Reports whether a row was removed.
func (*Store) ListPublic ¶
ListPublic returns every public listing (the shop), newest first, bounded.