Skip to content

Add operator-issued usage allowances and owner earnings - #380

Merged
ctfbruce merged 17 commits into
mainfrom
feat/allowances
Oct 5, 2026
Merged

ctfbruce merged 17 commits into
mainfrom
feat/allowances

Conversation

@ctfbruce

@ctfbruce ctfbruce commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

Behaviour

Operator-issued usage allowances for the TEST payment method (#83) and a read-only earnings view for executor owners (#215, operator side), both disabled in every shipped configuration:

  • Dispatcher schema 23 adds allowance_grants: immutable grants (actor, reason, idempotency key) per account; update and delete are refused by triggers. An account's allowance is the sum of its grants; consumed is the price of its TEST orders that have a settlement row, credit or refund alike (a failed run does not return its units); reserved is the price of the other orders of its paid TEST intents.
  • With [allowance] enabled = true, an account's TEST intent is created by one guarded insert inside the existing intent transaction (granted - consumed - reserved >= price); a refusal is HTTP 429 allowance_exceeded naming the remaining and required units and writes no transaction, order or owner row. Before the check, expired unused intents of the account (no claimed order) are released Paid -> Expired; an expired intent cannot be submitted.
  • Nothing is granted at sign-up; grants do not renew; the default grant is default_grant (30,000,000 units, the documented run arithmetic). Allowances are bookkeeping units, never money.
  • Routes: GET /me/allowance (404 while disabled; scope account:read), POST /operator/accounts/{id}/allowance (operator session only: 201 new, 200 for a repeated key with the same body, 409 conflict otherwise, 404 unknown account or disabled), GET /operator/executors/{id}/earnings (owner only; payouts: "disabled" while chain payments are off; scope executors:read). GET /me reports whether allowances are enabled. API stays 1.15; OpenAPI, docs/api.md, wire types and the Go client are extended.

Proof

  • Real SQLite: grants then intents within the allowance with reservations visible; over-limit refusal with no rows written; eight concurrent intents against one grant admit exactly five; credit and refund both move reserved to consumed; an admitted run with an unknown outcome stays reserved across a database reopen; an expired intent is released by the next intent, cannot be submitted and races an admission claim with one winner; grant idempotency and 409; operator-only, validation and unknown-account refusals; disabled mode answers 404 and leaves intents uncapped; the local-development caller is uncapped; earnings for an owned and a foreign executor; route access matrix, OpenAPI contract and error-code pair checks extended.
  • On the composed tree (this branch): gofmt clean, generated files match the pinned generators, vet and build pass, and the payments, Sui, HTTP API, storage, schema and client packages pass under -race.

Upgrade

Dispatcher schema 23 is required: stop the dispatcher, back up the database and run the explicit upgrade before starting this build. The minimum is raised although a deployment with allowances disabled never reads the table: a configuration-dependent minimum would be a new mechanism.

Stacked on #375 (quotes and economics) and #374 (schema 22); both are merged into this branch, so the diff against main shows their commits until they merge.

Limits

  • remaining is negative for an account that used TEST units before any grant; it is reported, not clamped.
  • Expired intents are released when the same account creates its next intent; there is no background sweep.
  • The Go client has no grant method (an operator action; the route answers 201 or 200).
  • Totals are never wrapped or clamped: an account whose grants or orders sum beyond a signed 64-bit integer, or whose remaining allowance cannot be computed without overflow, is answered 409 allowance_out_of_range on the allowance, grant and intent routes (nothing recorded, nothing reserved); an operator reconciles that history.

GitHub issues #83 and #215.

Candidate CI

Run 37292512236 on 768063f: 16 of 16 lanes and required succeeded on attempt 2; attempt 1 failed only in the local lane on TestInstalledRecovery/terminated (the read-only storage inspection after a forced stop, a known failure of that lane on code this branch does not touch). The branch then took the quotes branch's correction by merge (bfafb78): run 37302493328, 16 of 16 lanes and required succeeded. Correction f48d364 (typed range refusal instead of wrapped arithmetic): run 37307311805, 16 of 16 lanes and required succeeded. The branch then took the quotes branch's composed head, which carries the payout branch and the corrected adapter, with its two test fixtures adapted to the constructor that reports a configuration error (8d15c1e): run 37310280152, 16 of 16 lanes and required succeeded.

TheodorAdrienIsaak Mattli added 5 commits October 5, 2026 10:36
A client could only learn a batch's price by creating a payment intent,
and could not see which payment features a dispatcher offers or page its
own intents. POST /payment/quote prices a batch with the same function
the intent uses and stops there: it writes nothing and issues no auth
key; a batch the intent would refuse is quoted with every problem on the
order it concerns and no total. The intent response carries the same
quote of the prices it stored, so a client can compare the two exactly.
GET /me reports the payment methods admitted now, whether chain payments
are enabled and whether allowances are offered; GET /me/orders pages the
account's intents with their orders, settlements and admitted runs. The
API contract moves to 1.15 and the Go client gains Quote and Orders.

GitHub issues #214 and #215.
Brings in the maintenance pass that retries only local settlements and
scans pending orders fairly, so this branch builds on the same settlement
behaviour its order history reads.

GitHub issues #214 and #215.
…work

Compose the corrected receipts and transfers head with the corrected
Sui adapter head (prepared transfers with a persisted identity, coin
selection without repeated objects) as the base of the payout
reservation and refund change, which uses both.

GitHub issue #80.
Compose the quotes and economics head with the corrected receipts and
transfers head (dispatcher schema 22) as the base of the allowance
grants change, which adds schema 23 above it and reports settlements
from the ledger both carry.

GitHub issue #83.
Dispatcher schema 23 adds immutable allowance grants per account. With
allowances enabled, a TEST intent is created by one guarded insert
inside the intent transaction when the sum of grants less the price of
settled orders (credit or refund alike) and of reserved orders covers
the new price; a refusal answers 429 allowance_exceeded and writes
nothing, and expired unused intents of the account are released first.
Nothing is granted at sign-up and grants do not renew. Add the
allowance view, the operator grant route (session only, idempotent by
key) and a read-only earnings view for executor owners, with scopes,
OpenAPI, documentation and client types. Disabled in every shipped
configuration.

GitHub issues #83 and #215.
@ctfbruce ctfbruce added the In progress Implementation is active. label Oct 5, 2026
Executor payouts zeroed the balance after the transfer, so earnings
credited in between were lost; refunds and payouts held a SQL
transaction across the chain call, and a transaction-wide refund wrote
order states unconditionally and refunded credited orders.

Give payouts and refunds one lifecycle: prepare the transfer (a chain
read), commit the local decision together with a reserved chain
transfer row carrying the digest, the signed transaction and its
signature, broadcast the signed transaction under a two-minute bound,
and record the outcome by a conditional write: sent, failed (a failed
payout releases its amount in the same write) or unknown. Reconcile
sent, unknown and stale reserved rows on the payout loop's ticker with
one lookup: confirmed with the expected effects, failed, or resubmitted
once from the identical stored bytes when the chain does not know the
transaction; anything unverified stays unknown. A payout reserves
exactly the balance it read and is refused while another is open;
refunds change the transaction status and each outstanding order
conditionally, with a refund settlement row, before the chain call.
The maintenance pass settles chain refunds of failed runs through the
same lifecycle and never selects an order with a refund transfer again.

GitHub issues #80 and #81.
@ctfbruce ctfbruce added Review Implementation is ready for review and required validation. and removed In progress Implementation is active. labels Oct 5, 2026
@ctfbruce
ctfbruce marked this pull request as ready for review October 5, 2026 10:03
TheodorAdrienIsaak Mattli added 3 commits October 5, 2026 13:05
Bring the adapter branch at its corrected head, which validates a
listed coin's object id before normalizing it, under the payout and
refund lifecycle, so that the composed tree carries the correction the
transfer tests run against.

GitHub issues #80 and #81.
A quote priced an order whose start timestamp was impossible, or whose
policy required ICMP from an executor without it, without any error,
although the submission refuses both deterministically; the quote's
contract is to report every problem the dispatcher would report. Apply
the existing start-timestamp validation and the read-only capability
check in the pricing path, so that a quote and an intent report these
problems alike as per-order field errors; the quote still reserves
nothing and the submission still validates again. Correct the Go
client's description of Quote: the high-level submission does not
expose the intent's quote, so callers compare prices through the quote
itself or the raw intent response.

GitHub issues #214 and #215.
Bring the quotes and economics branch at its corrected head, where a
quote and an intent report start-time and capability problems alike,
under the allowance grants change, so that the composed tree carries
the correction the allowance intent path builds on.

GitHub issues #83 and #215.
@ctfbruce
ctfbruce marked this pull request as draft October 5, 2026 11:22
@ctfbruce ctfbruce added In progress Implementation is active. Review Implementation is ready for review and required validation. and removed Review Implementation is ready for review and required validation. In progress Implementation is active. labels Oct 5, 2026
@ctfbruce
ctfbruce marked this pull request as ready for review October 5, 2026 11:32
@ctfbruce
ctfbruce marked this pull request as draft October 5, 2026 11:58
@ctfbruce ctfbruce added In progress Implementation is active. and removed Review Implementation is ready for review and required validation. labels Oct 5, 2026
TheodorAdrienIsaak Mattli added 5 commits October 5, 2026 14:04
Bring the receipts and transfers branch at its corrected head
(dispatcher schema 22) under the quotes and economics change, keeping
both version notes: the schema 22 paragraph and the API 1.15 paragraph
describe different boundaries and land on the same lines.

GitHub issues #214 and #215.
The remaining allowance was computed by unchecked 64-bit arithmetic
over three separately aggregated totals, so an account whose settled
TEST orders summed to the 64-bit maximum, with a further order
reserved, was shown a wrapped positive balance once allowances were
enabled; such history is reachable, since an executor may announce the
maximum price and an order priced at it is accepted. Check that each
aggregate and the subtraction fit before an allowance or a grant
response is produced, and answer a typed range refusal when the history
cannot be represented; nothing is wrapped or clamped, and the
reservation keeps failing closed on its single sum.

GitHub issues #83 and #215.
After a refund decision was committed, the transfer lifecycle returned
no outcome to its caller, so a buyer refused during maintenance was told
the payment had been refunded even when the transfer failed or its
outcome was unknown, and later requests repeated it. Return the
recorded outcome and word the refusal as sent, pending confirmation, or
recorded but not sent and owed; a repeated request reads the refund
transfer's state. The order stays unspendable after the decision.

A reconciliation lookup ran under the daemon's lifetime context on an
SDK path with no deadline, so one lookup that never completed stopped
every later row and all payouts. Bound each lookup; a timeout is
recorded as an unknown outcome with the reservation kept, and the pass
continues with the next row.

GitHub issues #80 and #81.
Bring the payout and refund branch at its corrected head, which carries
the corrected adapter and the listener fixture built for its payment
handler constructor, under the quotes and economics change, so that the
composed tree builds and tests as one.

GitHub issues #214 and #215.
Bring the quotes and economics branch at its composed head, which now
carries the payout and refund lifecycle and the corrected adapter, under
the allowance grants change, so that the composed tree builds and tests
as one; the allowance tests adapt to the payment handler constructor
that reports a configuration error.

GitHub issues #83 and #215.
@ctfbruce
ctfbruce marked this pull request as ready for review October 5, 2026 13:30
@ctfbruce ctfbruce added the Review Implementation is ready for review and required validation. label Oct 5, 2026
@ctfbruce ctfbruce removed the In progress Implementation is active. label Oct 5, 2026
TheodorAdrienIsaak Mattli added 3 commits October 5, 2026 15:47
Include the accepted runtime, deployment, monitoring and SDK updates alongside payout reservation and refund reconciliation.

GitHub issues #80 and #81.
Keep quote pricing and account economics together with the current payout recovery, runtime, deployment and SDK updates.

GitHub issues #214 and #215.
Keep allowance accounting and owner earnings together with current quote pricing, payout recovery and main updates.

GitHub issues #83 and #215.
@ctfbruce
ctfbruce marked this pull request as draft October 5, 2026 14:03
@ctfbruce
ctfbruce marked this pull request as ready for review October 5, 2026 14:17
@ctfbruce
ctfbruce merged commit 2ecf143 into main Oct 5, 2026
16 checks passed
@vincent10400094
vincent10400094 deleted the feat/allowances branch October 8, 2026 00:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Review Implementation is ready for review and required validation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant