Docs
GitHub

Governed components & display patterns

The Constitution's rules resolve to two kinds of UI. Governed components render an escalation, the stricter flow a rule forces. Display patterns render a clean read. Neither validates anything itself; each renders the decision the gate already made (How it works).

The three governed components

The registry's governed rules escalate to just three components. The rules differ; the destinations are shared, so there is one interaction pattern to test instead of one per flow.

  • SecureWireDialog: forced when a wire needs a second approver. It runs the four-eyes loop: review the exact terms, collect a different approver's identity, confirm with a one-time code, re-check on the server. A denied re-check keeps the dialog open. See the $60k wire.
  • GovernedActionDialog: forced when another authorization-gated action escalates: ACH, P2P, bill pay, FX, card operations, limit changes, account opening, trades. Same shape as the wire dialog, with the terms derived from the action.
  • MandatoryDisclosure: forced when the user must acknowledge required text: margin suitability, a lending (TILA) disclosure, an FX-spread notice. The text comes from the rule, not the model, so the model cannot soften or shorten a required notice.

The same phases every time

All three share one state machine (review, collect, pending, resolved), so there is one set of states to test and one accessible experience across every governed flow.

On a clean pass a governed verb mounts nothing (mount: null); your app owns the confirm step. See Escalation.

Display patterns

A real app spends most of its time showing things. A display rule has no limits or approvals, but it still checks shape (strict parse), bounds (lists are capped), and privacy (identifiers must be masked). Pass all three and the decision mounts a presentational component: TransactionList, BalanceCard, PortfolioHoldings, and their siblings, one per kind of read.

Two properties do the safety work here. Provenance: the model only picks what to show and sends selection hints; your server resolves the actual rows from its own store. A model-authored row would validate and still defeat the purpose, because SINA checks shape, not truth. And reads are read-only: no display component renders a raw action button. Acting from a list (dispute a charge, pay a bill) is a new intent that re-enters the gate.

The intent registry

The model picks a verb from fintechIntentManifest(); the registry maps it to its rule and its component. The proposal format has no field for a component name.

IntentKindMounts / escalates to
wire_transferGovernedSecureWireDialog
ach_transfer · p2p_payment · bill_pay · fx_convert · withdrawGovernedGovernedActionDialog
add_payee · link_account · kyc · change_limit · issue_card · card_control · security_changeGovernedGovernedActionDialog
place_trade · enable_margin · credit_requestGovernedGovernedActionDialog · MandatoryDisclosure
disclosureGovernedMandatoryDisclosure
list_transactionsDisplayTransactionList
account_balanceDisplayBalanceCard
portfolio_holdingsDisplayPortfolioHoldings
clarify_choiceDisplayClarifyChoice
spending_breakdown · budget_progress · list_cards · list_payees · …Displaypresentational reads

The table shows the shape; the shipped registry has 50 verbs, and fintechIntentManifest() returns every one with its props as JSON Schema.

Unknown verbs are denied

An intent that isn't in the registry is rejected as UNKNOWN_INTENT, and still audited.

The @sina-design-system/fintech catalog

The schemas live in @sina-design-system/fintech: pure Zod, no React, no UI. One entry point routes everything (the contract lives on result):

import { evaluateFintechIntent } from "@sina-design-system/fintech";

const decision = evaluateFintechIntent({
  intent: "wire_transfer",
  props: { amount: 6000000 /* $60,000 in minor units */, /* … */ },
});
decision.result;  // { valid, violations, requiredComponent }
decision.mount;   // the component name to render, or null
GroupContents
Governed schemaswire-transfer, ach-transfer, p2p-payment, bill-pay, fx-convert, add-payee, kyc, issue-card, card-control, change-limit, place-trade, credit-request, …
Display schemastransaction-list, account-balance, portfolio-holdings, spending-breakdown, …
Format primitivescurrency (ISO 4217), amount (integer minor units), iban (ISO 13616), bic (ISO 9362), routing (ABA), card (Luhn), masked-account, step-up
Cited thresholdsthresholds.ts: every numeric limit as a named constant with its citation
Fixtureseach schema ships valid + adversarial payloads in fixtures.ts

The authoritative catalog is packages/fintech/PATTERNS.md. To add to it: Write a governance rule.