Skip to content

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:

  1. Record a settlement — receives settlement info, computes force_decrease, persists to the settlements system-of-record. (Tonder: settlementHandler.)
  2. Bridge to finances — receives the settlement payload (without internal calc fields) and synchronously triggers the §8.4 accounting. (Tonder: changeStatusFinancesHandler.)
  3. 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
Conditionforce_decreaseeffective rolling_reserve_release
diff > 0 (intended > finances)truediff (release only the surplus)
diff == 0falseoriginal 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

PayloadCarries rolling_reserve_release_finances, settlement_type, s3?Used for
internal settlement (record)yes — all three requiredrecording + force_decrease
change-status to financesno — those three are internal-onlytriggering §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.

Vecnet — Build Spec v0.2 · Obsidian Terminal