docs(mcp): correct the stock adjust justification and document create's actor

The system prompt justified avoiding the stock_items.list +
stock_items.adjust loop by claiming that path "writes a ledger row with no
actor". That stopped being true when the branch routed adjust through the
audited write service: adjust now requires actor.id and its ledger rows
carry created_by. The instruction itself is unchanged and not weakened —
the signed delta and the missing batch call are still the reasons to
avoid the loop, and the new tool's own advantages are now named.

stock_items.create now also requires actor.id, so an external MCP client
that omits it gets a 500 JSON-RPC error. The generated catalogue row is
truncated at 240 chars before that sentence, so both authoritative docs
get a hand-written row and an input-shape block instead. The generator is
not re-run; the catalogue is still 148 tools.
This commit is contained in:
Dave 2026-11-06 09:01:58 +02:00
parent 0fd3d2f34e
commit 2a0f0a97f7
3 changed files with 33 additions and 1 deletions

View file

@ -134,6 +134,7 @@ specific** tool that fits the task.
| `stock_purchase_items.bulk_update` | Batch patch stock_purchase_items (≤100 rows). Same whitelist + hand-edit guard as `.update`. Per-row independence — one row's failure never affects another. `dry_run: true` returns the same shape with `status: "dry_run"` per row. |
| `stock_purchase_items.assign_driver` | Bind a stock_purchase_item to a driver. Transitions line status `null → assigned_to_driver` or `collection_pool → assigned_to_driver`. Wrong_status (409) when the line is already past the assignment step. |
| `stock_purchase_items.mark_collected` | Mark a line as physically delivered. Status → `collected`, bumps `quantity_received`. Does NOT book into stock — that's `stock_purchases.receive`. Header auto-flips to `received` when every line on the PO has `quantity_received >= quantity`. |
| `stock_items.create` | Create a new POS/till stock_item. `name` is required. **Requires `actor.id`** — a 15-char WFDASH user id — so the opening-balance `stock_ledger` row is attributed to a user; a call without one is refused with a JSON-RPC error before anything is written. When `current_qty > 0` the opening balance is booked through the audited write path: the ledger row is written first and the quantity second, and the row is reversed if the quantity write fails. `supplier` is a PocketBase record id (resolve it with `suppliers.list`), not free text. |
| `stock_items.update` | Patch catalogue fields on a master stock_item: `sku`, `description`, `notes`, `unit`, `selling_price`, `markup`. For inventory qty use `stock_items.set_quantities`. Returns `{ok: true, item, previous}` on success or `{ok: false, reason, message, field?}` on rejection. |
| `stock_items.set_pricing` | Set `cost_price` / `selling_price` / `markup` on existing stock_items in one batch (max 100 per call). The POS till reads `selling_price` (not `markup`), so set both consistently: selling = cost × (1 + markup/100). Cost changes are audited in `stock_ledger` with the `actor`; quantities are NOT changed (use `stock_items.set_quantities`). Requires `actor.id`. Returns per-row `{ok, item, previous}` / `{ok: false, reason, message}`. |
| `stock_items.set_quantities` | Set or change the stock quantity on up to 100 pasted lines. `mode: "set"` (the number is the new quantity on hand) or `mode: "add"` (the number is added to the current quantity). `lines`: 1–100 entries of `{ sku?, description?, qty }` — `qty` is required, and each line needs a `sku` or a `description` to match on. `dry_run: true` plans the change and writes nothing at all. Quantities may be negative — the till oversells on purpose. A line that cannot be resolved is reported, never guessed. Every `stock_ledger` row is stamped with the acting user. Requires `actor.id`. **This is the tool for quantities.** |

View file

@ -219,6 +219,7 @@ JSON-RPC over HTTP. Same `Authorization` bearer as `/api/v1/*`.
| Tool | Purpose |
| --- | --- |
| `stock_items.create` | Create a new POS/till `stock_item`. `name` is required, and `actor.id` is required so the opening balance is attributed (a 15-char WFDASH user id). When `current_qty > 0` an opening-balance `stock_ledger` row is written first and the quantity second; the row is reversed if the quantity write fails. |
| `stock_items.update` | Patch catalogue fields on a master `stock_item` (`sku`, `description`, `notes`, `unit`, `selling_price`, `markup`). For inventory qty use `stock_items.set_quantities`. |
| `stock_items.set_pricing` | Set `cost_price` / `selling_price` / `markup` on existing `stock_items` in one batch (max 100). The POS till reads `selling_price`, not `markup` — set both. Cost changes are audited in `stock_ledger` with the `actor`; quantities are not changed (use `stock_items.set_quantities`). |
| `stock_items.set_quantities` | Set or change the stock quantity on up to 100 lines, matched by SKU and description. `mode: "set"` (the number is the new quantity on hand) or `mode: "add"` (the number is added to the current quantity). `lines`: 1–100 entries of `{ sku?, description?, qty }` — `qty` is required. `dry_run: true` plans the change and writes nothing at all. Quantities may be negative (the till oversells on purpose). Every `stock_ledger` row is stamped with the acting user. Requires `actor.id`. |
@ -308,6 +309,36 @@ Returns `{ item, previous_quantity_received, new_quantity_received, header_statu
`header_flipped: true` when this call completed the parent PO (auto-flip
from `sent` → `received`).
`stock_items.create` (required: `name`; `actor.id` is required at runtime and must be a 15-char user id — it is written to `stock_ledger.created_by`, a relation to `users`; `actor.name` defaults to the id):
```json
{
"name": "M6 x 40mm hex bolt",
"sku": "M6-40",
"unit": "ea",
"current_qty": 120,
"min_qty": 20,
"cost_price": 3.50,
"selling_price": 6.30,
"markup": 80,
"actor": { "id": "user-id", "name": "Dave" }
}
```
`actor.id` is required: a call without one is refused with a JSON-RPC error
(HTTP 500) before anything is written, so the opening balance is never
booked without an actor. It must be the 15-char id of a WFDASH user (see
`users.lookup`); `actor.name` is optional and defaults to the id. The
in-app Giellie agent injects the signed-in operator's own actor after the
model's arguments, so it wins over anything the model passed and Giellie
never has to supply one. A direct MCP client passes its own `actor.id`.
When `current_qty > 0` the opening balance is booked through the audited
write path: the `stock_ledger` row is written first and the quantity
second, and the row is reversed if the quantity write fails, so
`SUM(change_qty)` still rebuilds `current_qty`. `supplier` is a
PocketBase record id (resolve it with `suppliers.list`), not free text.
`stock_items.update` (required: `id`, `patch`):
```json

View file

@ -30,7 +30,7 @@ Stock items and pricing (POS):
Stock quantity updates (POS):
When the user pastes stock lines (a SKU or description with a quantity):
1. Always ask first whether the numbers are the new quantity on hand or a change to add to the current stock. Do not infer it from the wording, and do not write anything until they answer. Send mode "set" for the new quantity on hand and mode "add" for a change to add to the current stock.
2. Resolve the lines with stock_items.set_quantities. Never loop stock_items.list and stock_items.adjust by hand — that path takes a signed delta, has no batch call, and writes a ledger row with no actor.
2. Resolve the lines with stock_items.set_quantities. Never loop stock_items.list and stock_items.adjust by hand — that path takes a signed delta and has no batch call. stock_items.set_quantities takes the new quantity on hand (or an addition), resolves each SKU or description itself, and previews the whole batch before anything is written.
3. Call it with dry_run: true first and show the user the plan as a table: SKU, item, before, after, status. A dry run writes nothing, so a row whose status is "applied" only means "would be applied" — render it as "would be applied", label the table a plan, and say plainly that nothing has changed yet. Point out the lines that did not match and the ones that were ambiguous.
4. Only when they say to apply, call it again with dry_run: false.
5. Report the outcome: applied, unchanged, unmatched, ambiguous, failed.