marketplace

package
v1.801.490 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

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

func Shutdown(_ context.Context) error

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 Open

func Open(dir string) (*Store, error)

Open opens (and migrates) the listing store under dir.

func (*Store) CheapestPublicForTool

func (s *Store) CheapestPublicForTool(ctx context.Context, tool string) (Listing, bool, error)

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

func (s *Store) Close() error

Close closes the underlying database.

func (*Store) Create

func (s *Store) Create(ctx context.Context, l Listing) (Listing, error)

Create inserts a listing (id generated).

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, org, id string) (bool, error)

Delete removes a listing for (publisher_org, id). Reports whether a row was removed.

func (*Store) ListByOrg

func (s *Store) ListByOrg(ctx context.Context, org string) ([]Listing, error)

ListByOrg returns a publisher's own listings, newest first.

func (*Store) ListPublic

func (s *Store) ListPublic(ctx context.Context, limit int) ([]Listing, error)

ListPublic returns every public listing (the shop), newest first, bounded.

func (*Store) PublicByTool

func (s *Store) PublicByTool(ctx context.Context) (map[string]Listing, error)

PublicByTool returns the cheapest public listing per tool name — the shop-window overlay discovery paints onto the catalog. A single scan builds the map.

Jump to

Keyboard shortcuts

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