Appearance
Settlement orchestration, treasury & system of record
Tonder splits settlement across three services: the ledger (usrv-finances), the report/cycle engine (usrv-batch-transaction-report), and usrv-settlement — the orchestration + system-of-record + treasury bridge ported here. In Vecnet they live in one Supabase project, but the responsibilities stay distinct.
usrv-settlement does three things:
- Record a settlement — receives settlement info, computes
force_decrease, persists to thesettlementssystem-of-record. (Tonder:settlementHandler.) - Bridge to finances — receives the settlement payload (without internal calc fields) and synchronously triggers the §8.4 accounting. (Tonder:
changeStatusFinancesHandler.) - Query settlements — filtered, paginated read API over the records. (Tonder:
getSettlementsHandler.)
Position in the chain
The treasury control: calculateForceDecrease
Reconciles the rolling-reserve amount the settlement intends to release against what the finances ledger reports as releasable.
diff = rolling_reserve_release − rolling_reserve_release_finances # ALWAYS this order| Condition | force_decrease | effective rolling_reserve_release |
|---|---|---|
diff > 0 (intended > finances) | true | diff (release only the surplus) |
diff == 0 | false | original rolling_reserve_release |
diff < 0 (finances > intended) | — | reject — error ES007 |
Never invert the subtraction
It is always release − finances. Inverting it caused a production incident (Tonder HU-003). The resulting force_decrease feeds the force_decrease_rolling_reserve_release flag in the §8.4 settlement request; the effective amount feeds rolling_reserve_release.
This is the treasury guardrail: it refuses to disburse when the ledger believes more reserve is releasable than the settlement intends — a discrepancy that must be investigated, never auto-resolved.
settlement_type / cadence
settlement_type ∈ {batch (= T+0), t1 (= T+1), t2 (= T+2)}, aligned with the cycles. Tonder guards via isSupportedSettlementType() (only batch enabled today). Vecnet validates settlement_type against an allowlist and rejects unsupported types early.
Two payloads — keep them separate
| Payload | Carries rolling_reserve_release_finances, settlement_type, s3? | Used for |
|---|---|---|
| internal settlement (record) | yes — all three required | recording + force_decrease |
| change-status to finances | no — those three are internal-only | triggering §8.4 accounting |
The s3/statement reference and the internal calc fields never leak into the finances accounting call.
Query API + Decimal128 simplification
GET /settlements — dynamic filters + pagination. In Vecnet: a Postgres query over settlements with WHERE filters, ORDER BY, LIMIT/OFFSET (or keyset), and count(*) OVER () for the total.
Whole bug class vanishes
Tonder must normalize Mongo Decimal128 ({"$numberDecimal":"1000"}) to plain numbers before every HTTP response. Postgres numeric serializes cleanly — the normalizeDecimalFields helper, the $facet pagination, and the constructor.name esbuild guard all disappear.
settlements table (system of record)
sql
create table settlements (
id uuid primary key default uuid_generate_v7(),
settlement_id text not null unique, -- business settlement id
entity_id text not null,
settlement_type text not null, -- batch | t1 | t2
currency_code text not null,
window_from timestamptz not null,
window_to timestamptz not null,
acquirers text[] not null, -- rails settled
gross_amount numeric(20,4) not null,
fee_amount numeric(20,4) not null,
iva_amount numeric(20,4) not null,
net_amount numeric(20,4) not null,
rolling_reserve_release numeric(20,4) not null default 0, -- intended
rolling_reserve_release_finances numeric(20,4) not null default 0, -- ledger figure
force_decrease boolean not null default false, -- output of calculateForceDecrease
status text not null default 'recorded', -- recorded | approved | confirmed | failed
statement_path text, -- Supabase Storage (was the `s3` field)
metadata jsonb,
created_at timestamptz not null default now(),
modified_at timestamptz not null default now()
);
create index on settlements (entity_id, created_at desc);
create index on settlements (settlement_type, status);Preserve Retain semantics
Tonder's SettlementTable has DeletionPolicy: Retain. Never hard-delete a settlement record.
Treasury ops framing
Settlement orchestration is where treasury decisions live: the force_decrease control above, plus the §8.4 accounting it triggers moves funds through the treasury accounts — BUSINESS_SETTLEMENT_PENDING → BANK on confirm, RESERVE_* on reserve release, WITHDRAWAL_FUNDS for rail-payout funding. Treasury reconciliation (actual bank/rail movements vs ledger) belongs in the System Control Panel. Deeper treasury (bank-account funding, liquidity management, multi-currency FX) is out of v0.1 scope.