Skip to content

MCP slice 5: read tools — customers, orders, balances, with money rendered at the boundary #808

Description

@mforce

Slice 5 of #789. Blocked on #788 and slice 3 (#806). Can run in parallel with slice 4 (#807).

Read tools for the sales side: customers, orders, payments / balances.

Money never crosses as minor units

Cluckwork stores long minor units plus a per-account DefaultCurrencyMinorUnit (2 for PHP, 0 for JPY, 3 for KWD). Handing a model {"minorUnits": 2825000, "minorUnit": 2} invites "₱2,825,000", a 100× error in a number a farmer acts on.

Render once, at the boundary, as Money(Amount, Currency) with a decimal string. The minor-unit representation is an internal decision. Re-exporting it leaks it into every model prompt that ever reads a Cluckwork amount. A guard walks tool reply types and fails any long …MinorUnits on the surface.

The money tier is decided live, not re-derived

list_orders fuses list and get, and decides SettlementScope by evaluating AuthPolicies.SalesAccess through the real IAuthorizationService. It never re-implements the tier. Keep the in-body money gate even though the coarse policy covers it today. A later widening of that policy must not silently widen the money question.

Bounded results

PaymentRepository.ListCustomerBalancesAsync takes no paging arguments and materializes every matching grouped row. The order cap lives in SaleEndpoints, not the repository. So calling the repository directly inherits no bound, and an unbounded read_receivables on a large farm serializes the whole book into a model's context.

This needs a paged balance query. The guard must assert rows consumed from the database, not items returned. The SqlCaptureInterceptor in ReportQueryBoundingTests is the right harness shape. It was written for #311, which had the identical "loaded the whole ledger, walked it in memory" defect. But it records (Sql, Parameters) only, so a page-fetch loop emits perfectly paged SQL and passes. Wrap the DbDataReader and count reads.

Design

docs/plans/770-mcp-server/, guards rows 28, 31, 31b.

Activity

  1. added
    sliceThin vertical work item
    area:apiAPI/endpoint layer
    priority:tier3Real product weight, real cost
    size:MA day or two; migration or a multi-state UI
    epic-789MCP server support (#789)
    on Sep 13, 2026
  2. added this to the MCP server support milestone on Sep 13, 2026
  3. mforce commented on Oct 6, 2026

    @mforce
    OwnerAuthor

    Scope added in the 2026-10-06 re-evaluation:

    • A paged balance method on ICommerceModule. ListCustomerBalancesAsync has no paging, and PaymentRepository.cs:43-63 loads both grouped queries into memory before joining. Guards 31 and 31b need a bounded query, which means a contract method returning a DTO page and a repository change. The [B] #514 slice 5: seam-surface guard — no persistence type crosses a seam #847 seam guard allows that.
    • Money rendering for payments and balances. SalesOrderDetails already exposes Money. PaymentDetails and CustomerBalance return raw long minor units. Read currency and minor unit through IFarmModule.GetSettingsAsync, as PaymentEndpoints.cs:130-138 does.

    Customers and orders are covered by ICommerceModule.SearchCustomersAsync, GetCustomerAsync, ListSalesOrdersAsync and GetSalesOrderAsync. SettlementScope is now in Application/Modules/Commerce/Contracts/. Decide #792 before this ships, because CustomerDetails includes the free-text Note.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiAPI/endpoint layerepic-789MCP server support (#789)priority:tier3Real product weight, real costsize:MA day or two; migration or a multi-state UIsliceThin vertical work item

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions