Adjust
An operator-only manual correction posted to the ledger.
Source src/operations/adjust.ts#L49adjustsrc/contract.ts#L160kind 'adjust'src/operations/registry.ts#L68REGISTRYsrc/economy.ts#L1313RESTRICTED_TO_PRIVILEGED
API adjust
adjust is the operator’s manual correction. It moves one account by a signed amount and books the opposite entry to a platform account, so the books stay balanced.
It exists for cases no ordinary operation covers, such as closing a gap found during reconciliation.
You name the account, the signed amount to move it by, and a written reason for the audit trail:
const outcome = await economy.submit({
kind: 'adjust',
idempotencyKey: 'idem_0',
actor: { kind: 'operator', operatorId: 'op_1' },
account: spendable('usr_alice'),
amount: toAmount('CREDIT', 250n),
reason: 'reconciliation: missing genesis lot',
});
// → { status: "committed", transaction: { id: "txn_…", … } }curl -s https://economy.example/submit \
-H 'content-type: application/json' \
-d '{
"kind": "adjust",
"idempotencyKey": "idem_0",
"actor": { "kind": "operator", "operatorId": "op_1" },
"account": "usr_alice:spendable",
"amount": "CREDIT:2.50",
"reason": "reconciliation: missing genesis lot"
}'usr_alice’s spendable balance rose by 250 credits. A negative amount is a valid downward correction. The same call with toAmount(“CREDIT”, -250n) lowers the account by 250 instead.
Parameters
The payload fields, beyond the kind tag, are:
| Field | Type | Default | Description |
|---|---|---|---|
idempotencyKey | string | (required) | A retried submit with the same key runs at most once. See idempotency. |
actor | Principal | (required) | Who is asking. Must be an operator; see Authorization. |
account | AccountRef | (required) | The account to move. |
amount | Amount | (required) | The signed change. Must be CREDIT and non-zero; negative corrects downward. |
reason | string | (required) | Why the correction was made, recorded on the posting. Must be non-empty after trimming whitespace. |
Returns
adjust resolves to an Outcome.
committed: A fresh correction, with the postedTransaction.duplicate: A retry under the sameidempotencyKey. That duplicate carries the earlier transaction unchanged, so the correction isn’t posted twice.
adjust returns no rejected outcome. A malformed request throws instead; see reason codes.
Postings
adjust posts one balanced double-entry transaction with two legs. One leg moves account by the signed amount. The other posts the opposite to SYSTEM.OPENING_EQUITY, so the two cancel and the books stay balanced.
For the example above (raising spendable(usr_alice) by 250 credits) the legs are:
| Account | Side | Amount |
|---|---|---|
spendable(usr_alice) | credit | 250 (CREDIT) |
SYSTEM.OPENING_EQUITY | debit | 250 (CREDIT) |
The handler works in the account’s natural direction, so the move reads as “raise the account by amount” regardless of its normal side. The offset to OPENING_EQUITY negates this, keeping the posting balanced.
Authorization
adjust is operator-only. It writes a privileged correction to an account the caller need not own, a case the ownership rule that governs ordinary user operations doesn’t cover. So a user actor may never call it; see actors and authorization.
The handler re-checks the actor; a system caller clears the pipeline’s gate but is refused by the handler, so only an operator can adjust.
Reason codes
adjust returns no reason codes: it has no rejected path. It fails only by throwing. Its inputs are either valid or a caller error, so each bad input throws a fault rather than declining as data:
| Code | When |
|---|---|
OP.MALFORMED | The actor isn’t an operator, the reason is blank or whitespace-only, the amount isn’t CREDIT, or the handler received the wrong operation kind. |
MONEY.INVALID_AMOUNT | The amount is zero. |
Preconditions and invariants
The pipeline checks the amount is in range before the handler runs. An adjust may move money in either direction, so it only has to be non-zero, where every other operation’s amount must be strictly positive. The magnitude must still sit within the per-operation ceiling.
The posted transaction is balanced. The account leg and its OPENING_EQUITY offset are exact negations, so debits and credits sum to zero and conservation holds.
Both legs are in CREDIT. The amount must be CREDIT because the OPENING_EQUITY account it balances against is CREDIT, so a single posting never mixes currencies.
Every correction records its reason. The signed amount and the operator’s reason are stored on the posting’s metadata. From there they are hashed into the tamper-evident chain along with the rest of the transaction, so the audit trail keeps what changed and why.