Build contract — escrow & banking
1 · A balance is a measurement, and it carries the moment it was taken. Every dollar figure on
this surface renders with an as-of stamp beside it, and a figure with no stamp is not a figure. An
escrow_accounts row that has never received a balance webhook has
current_balance_cents_snapshot = 0 because that is the column default and
balance_snapshot_at IS NULL — that is
unmeasured, and rendering it as
$0.00 is
a factual claim the platform cannot support. It renders as
Never measured, it is excluded from every
total, and the exclusion is stated on the total. The shipped
BankConnectionsSection currently formats it straight to
$0.00; that is the first defect
to fix.
2 · A total is only as fresh as its stalest input. A sum over accounts is stamped with the
earliest contributing snapshot, never the latest — mirroring
earliestSnapshotAt in
admin.ts and the
bool_or(balance_snapshot_at is null) test in the escrow-sufficiency
guard. If any contributing account is unmeasured, the total says so rather than quietly shrinking.
3 · Status is derived from evidence, never read off a single column. A connection whose
status = 'active' but whose
last_verified_at is 198 days old is
not healthy, and
the health cell says which of the two facts is wrong. An account that is
open with an unexplained
bank delta is never green. An unmatched debit and its matching balance drift are rendered as one event, because
they are one event. State comes from dates, statement lines, receipts, counts and signatures — never from one
boolean.
4 · No fund movement exists on this surface, in any form. Nothing here proposes, approves,
releases, redirects or reverses a payment. Resolving an exception records a decision and writes an audit row;
it moves nothing. The one resolution kind that would create a fund-movement record —
created_retroactive_proposal — is refused by the server with
409 and
admin.reconciliation_exception.resolution_denied, and the UI shows it struck through with the reason
rather than hiding it. A refusal a user can see is a control a user trusts.
5 · Four eyes, and the approver is never the proposer. Any control that could stop, release or
write off money — a freeze, a closure above the write-off threshold — takes
two authorized officers of
the owning organization: one proposes, a second and different officer approves. A proposer’s own
submission is not an approval and is never counted, displayed or aggregated as one. The exchanger appears
nowhere in this chain: an exchanger in a fund-movement approval or initiation path is
constructive receipt under Treas. Reg. §1.1031(k)-1(g)(6) and would fail the exchange. An exchanger notice about
an escrow review is permitted only as explicitly non-authorizing, non-gating information.
6 · Two projections of one number, shown side by side.
escrow_accounts.current_balance_cents_snapshot is account truth;
exchanges.exchange_funds_held is a trigger-maintained rollup that only sees accounts carrying an
exchange_id. They can disagree, and a pooled account is invisible to the second one entirely. The
cross-check panel is not a nicety — it is the reason these three nav sections belong on one screen.
7 · Segregation is currently an inference and must be labelled as one. The console reads it from
whether
exchange_id is set. That is not an attestation, and under CA SB 1007 and the other state QI
statutes an inference is not evidence. Add
segregation_basis,
attested_by and
attested_at, or keep saying
inferred, not attested on every row. Do not quietly promote the
inference to a claim.
8 · An overdue or unmet obligation never resolves to a satisfied appearance. An unexplained
delta stays alarming until it is explained on the record — it does not go neutral because it was acknowledged,
claimed, snoozed or scrolled past. The same rule that governs a passed 45-day deadline governs a bank that is
short: the alarm state is a fact about the world, not a notification the user can dismiss.
9 · Every row carries its real action and deep-links to the section it concerns. An account row
opens that exchange’s
Escrow hub, an exception opens the
Disbursements or
Identity &
AML section it turns on, a bank row opens the chase. Nothing on this surface drops a user on an Overview to
hunt.
10 · Exactly one next step. One filled primary button exists in this document —
Claim and investigate, on the unexplained debit. Everything else is a secondary or ghost affordance.
Five exceptions across eight accounts is a workload; the surface's job is to name which one is first.
11 · One boundary, both sides of it. All three endpoints require
org_owner or
org_admin; row-level security on
bank_connections is looser than that. Either widen the
API to
org_staff or hide the nav item from them and restrict
assigned_to to roles that
can actually open the page — an exception assigned to someone who receives a 404 is a queue item that can never
be worked.
12 · Provenance is stated, not implied. The read and triage endpoints are shipped; the detection
that fills them is not. No nightly bank-statement job, no
bank_statement_runs table, and no code path
anywhere in the repository writes a
reconciliation_exceptions row. Until that lands, this queue is
permanently empty in production, and an empty exception queue reads as health — which is why the banner and the
markers stay, and why no screenshot of this surface may be narrated as
a running reconciliation.
Fully tokenized against
../tokens.css — no raw hex, and no new token was required: the console rail,
admin, escrow-hub, identity and good/warn/bad roles already carried every job on this page. Light and dark are
both designed. All organizations, banks, accounts, people, references and amounts are synthetic demo data.