# Larkfield pricing desk

Four small hosts that embed the Chio kernel beside Larkfield's pricing functions, plus a reconnecting MCP client. Each one shows a decision the kernel signs and the effect it leaves behind: a deep discount that waits for Mara Okafor, an ordered price change, quotes kept apart for two customers, and a repricing job that can be refused, cancelled, cut short, or published without an acknowledgement.

Pull it with `chio new pricing-desk --example pricing-desk`, or extract the download anywhere. Rust 1.94.1 is selected by `rust-toolchain.toml`, and Cargo fetches the Chio crates at the revision `Cargo.toml` pins. The Python helpers need Python 3.10 or newer; the trace verifier needs Python 3.11 and `uv`, which installs `chio-sdk` from PyPI. The hosts use Unix file locking.

| Host | Port | State | Guide |
|---|---|---|---|
| `approval-desk` | 4310 | `state/approvals` | [Require human approval](https://chio.computer/docs/guides/require-human-approval) |
| `quote-workflow` | console | `state/approvals` | [Authorize an ordered tool workflow](https://chio.computer/docs/guides/authorize-tool-workflow) |
| `customer-service` | 4311 | `state/customers` | [Serve two customers](https://chio.computer/docs/guides/build-tenant-scoped-service) |
| `repricing-service` | 4312 | `state/repricing` | [Handle tool outcomes](https://chio.computer/docs/guides/handle-tool-outcomes), [Share a worker budget](https://chio.computer/docs/guides/share-worker-budget) |
| `mcp_pricing.py` behind `chio mcp serve-http` | 4313 | `state/mcp` | [Restore an MCP session](https://chio.computer/docs/guides/restore-mcp-session) |

Every host serves the `pricing` server with Larkfield's list prices: `sku-118` USD 24.00, `sku-204` USD 58.00, `sku-301` USD 140.00. Quotes round half up to the cent, as `quote()` does in the pricing project.

## Approval desk

```sh
cargo run --locked --bin approval-desk
```

Open http://127.0.0.1:4310. Use `state/approvals/agent-token.txt` to propose a price change as the pricing agent, and `state/approvals/mara-token.txt` in a second tab to review it as Mara Okafor. The agent credential cannot reach the decision, revoke, or rotate routes.

- `POST /proposals`: `{ "sku": "sku-301", "percent": 80, "customer": "basalt" }`. The host computes the price and freezes the `publish_price` request.
- `GET /proposals/{id}`: the frozen change, its pending binding (`parameter_hash`, `policy_id: quote-approval`, expiry), resolution, and result.
- `GET /proposals`: Mara's queue.
- `POST /proposals/{id}/decision`: `decision` (`approve` or `reject`) and the `parameter_hash` from the preview. Unknown fields are rejected.
- `POST /agent/revoke`: Mara revokes the pricing agent's capability.
- `POST /agent/rotate`: Mara issues the agent a new key and capability and revokes the old grant.

An approved change writes `state/approvals/price-changes/{approval-id}.json`. A rejection produces a deny receipt and no file. A reused decision, a changed hash, replacement arguments, and an expired approval change no price. `APPROVAL_TTL_SECONDS` sets the review window for new proposals (default 900, maximum 3600).

`src/approvals.rs` uses Chio's `ApprovalGuard`, a governed approval token signed with Mara's key, and atomic `resume_with_decision` consumption, then submits the saved request through the kernel. Resolving an approval does not dispatch anything by itself. If the host stops during dispatch, reconcile under the approval ID: the dispatch marker, the exact request, the receipt store, and `price-changes/`. A consumed approval is never retried automatically.

## Ordered price change

Stop the approval desk first, then run:

```sh
cargo run --locked --bin quote-workflow -- order-77 15
```

The host runs `read_order`, `quote`, and `publish_price` in that order through the kernel, prints the exact price change, and asks you, as Mara, to type `approve`. It records each tool receipt and signs one workflow receipt. Add `--try-publish-first` to try step 2 before step 0; the workflow authority refuses it before any tool runs.

## Customer quotes

```sh
cargo run --locked --bin customer-service
```

Open http://127.0.0.1:4311. A local issuer writes one-hour credentials for Priya Raman (`harbor-goods`), Tomás Ibarra (`ostrander`), and the operator into `state/customers/*-token.txt`. The host checks the signature, algorithm, issuer, audience, expiry, and membership, derives the tenant from the verified claims, and binds it to the Chio session. The request body cannot name a tenant.

- `POST /quotes`: `{ "sku": "sku-118", "percent": 10 }`.
- `GET /quotes`: this customer's quotes.
- `GET /receipts`: a scoped page with `next_cursor`.
- `GET /receipts/{id}`: one receipt inside the caller's read boundary.
- `GET /export`: every receipt in the caller's boundary, as NDJSON.

## Repricing service

```sh
cargo run --locked --bin repricing-service
```

Open http://127.0.0.1:4312 with `state/repricing/client-token.txt`. A job applies one discount to `sku-118`, `sku-204`, and `sku-301` in turn, writing one row of the price sheet about every 700 ms, then publishes `state/repricing/price-sheets/{job-id}.csv`. Every worker shares `workers-capability.json`: USD 0.80 per job and USD 2.00 in total, in local credits that move no money.

- `POST /jobs`: `percent` plus `mode`: `complete`, `refuse`, `fail`, or `uncertain`.
- `GET /jobs/{id}`: active status, the partial sheet, publication, and the exact result.
- `POST /jobs/{id}/cancel`: request cooperative cancellation.
- `GET /budget`: exposed, spent, committed, and available cents.

The job ID is also the kernel request ID. A cancellation that lands in the 500 ms queue window never reaches the kernel. A cancellation during the job keeps the rows already written. `fail` stops after `sku-118` because the orders database stops responding. `uncertain` publishes the sheet and then loses the acknowledgement, so a deny result sits beside `published: true`.

Three concurrent jobs on fresh state publish two sheets, leave three receipts, spend USD 1.60, and leave USD 0.40; the third job needs USD 0.80 and is refused. A restart keeps the balance. Select fresh state for an experiment with `CHIO_APP_STATE`.

### Export traces

```sh
docker compose up -d collector
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://127.0.0.1:4318/v1/traces \
  cargo run --locked --bin repricing-service
```

Each span carries `chio.request.id`, `chio.receipt.id`, and `chio.verdict`, and the signed receipt's metadata carries the trace and span IDs. `uv run trace.py --compare` runs all four modes and checks each trace against its receipt with the published `chio-sdk`.

### External pricing policy

```sh
python3 policy_service.py
export PRICING_POLICY_TOKEN="$(cat state/policy/token.txt)"
PRICING_POLICY_URL=http://127.0.0.1:4314/evaluate cargo run --locked --bin repricing-service
```

The service refuses any repricing deeper than 40 percent. Write `slow` or `overloaded` to `state/policy/mode.txt` to watch the adapter's 300 ms deadline and fail-closed defaults; `healthy` restores normal answers.

## MCP session

Follow [Restore an MCP session](https://chio.computer/docs/guides/restore-mcp-session) with the installed `chio`:

```sh
python3 mcp_client.py setup
export CHIO_AUTH_TOKEN="$(cat state/mcp/auth-token.txt)"
chio --receipt-db state/mcp/receipts.db --session-db state/mcp/sessions.db \
  --authority-seed-file state/mcp/authority.seed \
  mcp serve-http --policy mcp-policy.yaml --server-id pricing \
  --listen 127.0.0.1:4313 -- python3 mcp_pricing.py
```

The client saves its session ID and protocol, generates a fresh request ID per call, and never initializes a replacement session on its own.

## Application checks

```sh
cargo build --locked --bins
python3 application_ci.py
```

The runner starts each host on temporary state and checks HTTP outcomes and files on disk. Keep ports 4310 to 4312 free.
