# ADR — Marketplace: buy provider packs Status: Proposed. Review: to be agreed. Work item: [#703](https://github.com/pharos-ai-factory/pharos-platform/issues/703). Technical design: [DD](dd.md). ## Decision and flow A buyer buys fixed packs in Pharos: consulting hours, GPU hours per class (for example 10 × H200 hours) or storage. A buyer is a project member whose user has the buyer right. Any user can view the catalogue. A provider fulfils each order behind a provider port. Today the provider is Waldur. Each pack is a Waldur Basic offering with manual fulfilment, so an order is a purchase and provisions nothing. Users never use the provider's interface. The domain speaks of packs, plans and provider orders. Waldur IDs stay opaque references inside one adaptor. Beto's Marketplace context owns the **Order**. One Order is one purchase of one pack. Pharos creates at most one provider order per Order, because the provider accepts duplicates. 1. Beto copies the provider's pack catalogue on a timer. 2. A buyer places an order for a catalogue pack. Beto saves the Order, its receipt and Order Placed in one PostgreSQL transaction. 3. The order is paid, or charged to the project (to be agreed). 4. A send policy looks for the provider order by our order ID, creates it only if it is missing and records the provider's order ID. 5. A timer asks the provider which of our orders finished since the last check and records each answer. ```mermaid flowchart LR B["Buyer"] -->|"PlaceOrder"| API["API"] API -->|"RabbitMQ"| M["Marketplace (Beto)"] M --> DB[("PostgreSQL")] M -->|"Timer: copy catalogue"| P["Provider"] M -->|"Send policy: create once"| P M -->|"Timer: finished orders"| P ``` Marketplace stops before entitlement. It never moves a provider order into a status that provisions anything. Organisation verification, provisioning, entitlement and billing belong to other contexts. A playground pack approved automatically is not yet planned. This builds on Beto's command side and the API's command binding, recorded in [decision 0046](../../../../../docs/decisions/0046-service-communication-and-composition.md). It also builds on the Party governance branch ([#772](https://github.com/pharos-ai-factory/pharos-platform/pull/772)), which carries the change for [#700](https://github.com/pharos-ai-factory/pharos-platform/issues/700): Beto's contract generation and broker provisioning handle contexts other than Project, a context can register without a database or messaging, and the API's generated contracts cover every declared owner. So 1A registers Marketplace without a database or messaging, and 1C adds both. Task 1's pull request targets that branch until it reaches `main`. Task 1 is one pull request, and its subtasks are steps within it. Mark `[x]` when implementation and verification are complete; link evidence and the PR when available. ## Task 1 — Place an order Work item: [#704](https://github.com/pharos-ai-factory/pharos-platform/issues/704). Outcome: a buyer places an order for a catalogue pack. The Order keeps the pack's price, amount and provider plan as they were when it was placed. - [x] **Subtask 1A — Order domain:** register the context without a database or messaging, and add the Order, the rules it checks itself and Order Placed, with unit tests. [Design](place-order.md) and [verification](place-order.md#verification). - [x] **Subtask 1B — PlaceOrder handler:** add the PlaceOrder contract and handler. Refuse packs outside the catalogue, callers outside the project and callers without the buyer right. Tests use **test-only catalogue and buyer ports**. [Design](place-order.md) and [verification](place-order.md#verification). - [x] **Subtask 1C — Marketplace in Beto:** add the context's database, migration manifest, restricted role, private Order Placed event, broker permissions and composition. A RabbitMQ request returns the recorded outcome. Uses **stand-in catalogue and buyer ports**. [Design](place-order.md) and [verification](place-order.md#verification). - [x] **Subtask 1D — Project's orders:** expose a project's orders as a paginated query over the stored Orders. Only the project's buyers may list them, since other members can't see costs. [Design](place-order.md) and [verification](place-order.md#verification). - [x] **Subtask 1E — Order routes:** expose PlaceOrder as a POST route through the API's command binding, and ListProjectOrders as a paginated GET route. The buyer is the session's subject, never a body field. [Design](place-order.md) and [verification](place-order.md#verification). ## Task 2 — Pay for the order Outcome: an order is paid, or its failed payment is recorded, before anything is sent to the provider. Not yet planned. Open: a pay step in Pharos, or charging the project. If the project is charged, this task may disappear. ## Task 3 — Send the order to the provider Outcome: each paid order creates exactly one provider order. A retry finds the order it already made. A refused order is recorded as send failed. Not yet planned. ## Task 4 — Record the provider's answer Outcome: completed, rejected and failed orders are recorded from one batched check per tick. "What the project bought" is built from completed orders. Not yet planned. ## Task 5 — Copy the provider's catalogue Outcome: Beto keeps a copy of our pack category, which PlaceOrder checks and any signed-in user can list. Not yet planned. Replaces the catalogue stand-in in Task 1. ## Task 6 — Use the real buyer source Outcome: PlaceOrder checks project membership and the buyer right against their real source instead of a stand-in. Not yet planned. Waits on where the buyer right is set and how Beto reads it. Replaces the buyer stand-in in Task 1. ## Verification Each PR includes its relevant contracts, migrations and transaction, delivery and recovery tests. Name every stand-in in its subtask and replace it in its own subtask. Explicitly record any checks not run.