@venlyfinance/settlement-mcp 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +35 -0
- package/CHANGELOG.md +53 -2
- package/README.md +63 -7
- package/dist/constants.d.ts +2 -1
- package/dist/constants.js +2 -1
- package/dist/frontend.d.ts +21 -1
- package/dist/frontend.js +677 -25
- package/dist/index.js +32 -4
- package/dist/review-cli.d.ts +4 -0
- package/dist/review-cli.js +131 -0
- package/dist/server.js +3 -1
- package/dist/staging-smoke.d.ts +1 -1
- package/dist/staging-smoke.js +1 -0
- package/dist/verify-cli.d.ts +24 -0
- package/dist/verify-cli.js +371 -0
- package/package.json +6 -4
package/AGENTS.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Composition rules for coding agents
|
|
2
|
+
|
|
3
|
+
You are building a financial product UI on `@venlyfinance/react`. These rules exist because the failure modes of money UIs are specific; follow them over generic dashboard instincts.
|
|
4
|
+
|
|
5
|
+
## Non-negotiable
|
|
6
|
+
|
|
7
|
+
1. **Never hand-roll API calls, auth, retries, or transfer state.** Every read is a hook (`useAccounts`, `useTransfers`, `useRampRequests`, …); every regulated lifecycle is a flow machine (`useStagedTransfer`, `useFourEyesApproval`, `useRampLifecycle`). If you are writing `fetch` or a `useEffect` polling loop, stop – the hook exists.
|
|
8
|
+
2. **Wrap the tree once** in `<VenlyProvider environment="mock">`. Mock mode needs zero credentials and zero network; it is the correct default for any demo, test, or first build. Going live is a constructor change, not a rewrite.
|
|
9
|
+
3. **Never place `clientSecret` in browser code.** The provider throws if you try. For browser apps use `proxyClientOptions()` against your own backend route.
|
|
10
|
+
4. **Money movement is stage-then-confirm.** Render a review step showing `state.staged` (the exact request) before calling `confirm()`. Never wire a form submit directly to execution.
|
|
11
|
+
5. **Approval UIs render the rule, not the error.** Use `capability` from `useFourEyesApproval`: when `reason` is `"actor-is-creator"`, say that a second person must approve – do not show buttons that will be refused. On failure `"stale-version"`, refetch and let the operator re-decide; never auto-retry an approval.
|
|
12
|
+
6. **Gate both the screen and its runtime contract, and wire both into CI.** Run `npx @venlyfinance/settlement-mcp review "src/**/*.tsx"` for the design contract and `npx @venlyfinance/settlement-mcp verify "src/**/*.{ts,tsx}"` for package/provider/hook/proxy composition. Each fails (exit 1) on any error-severity finding. A deliberate, justified exception carries `venly-allow:<rule-id>` on the offending line or the line above.
|
|
13
|
+
|
|
14
|
+
## Rendering money states
|
|
15
|
+
|
|
16
|
+
- Use `descriptor` from `useRampLifecycle` for status pills and timelines: `intent` gives the semantic colour, but always pair colour with a glyph or label – state is never carried by colour alone.
|
|
17
|
+
- A waiting state must answer: must I act, who is it waiting on, what still works. `descriptor.waitingOn` and `descriptor.explanation` carry this; render them instead of a bare "Pending".
|
|
18
|
+
- Amounts: tabular figures, currency code after the amount, and never render a debit in red as the only signal. Empty numeric cells are an em dash, not 0.
|
|
19
|
+
- Terminal failure states show the reason from the record; the status field is the explanation field.
|
|
20
|
+
|
|
21
|
+
## Bring your own auth
|
|
22
|
+
|
|
23
|
+
The Venly APIs authenticate machines (OAuth2 client credentials), not people: there is no end-user login, sign-up, session, password, or MFA endpoint in either API, by design. **The Venly APIs never see end-user credentials.** End-user auth is your identity layer's job; the sanctioned browser shape is a backend proxy that inherits YOUR app's session (`proxyClientOptions()`), never Venly's.
|
|
24
|
+
|
|
25
|
+
The UI kit's auth and team blocks therefore render against two adapter interfaces, `AuthAdapter` and `TeamAdapter`, instead of an SDK client:
|
|
26
|
+
|
|
27
|
+
- **Real implementations** wrap what you already run: OAuth/OIDC, Better Auth, Auth0, Clerk, Keycloak, or a plain session cookie. Implement `signIn`/`verifyTotp`/`signUp`/`session`/`signOut` (and the team CRUD) over your provider's SDK.
|
|
28
|
+
- **Session-expiry contract:** `session()` returns `null` once the session has expired for any reason. The shell treats null as signed-out and redirects to sign-in; no other expiry signal exists in the interface.
|
|
29
|
+
- **Mock adapters ship with the blocks** (`createMockAuthAdapter`, `createMockTeamAdapter`): zero credentials, deterministic 2FA code `000000`, an `expireSession()` driver, and invites that mint a display-only link – the mock never claims an email was sent.
|
|
30
|
+
- Password reset, SSO buttons, and passkeys are provider features: point users at your provider's flow rather than rebuilding it behind the adapter.
|
|
31
|
+
|
|
32
|
+
## Demo choreography (mock mode)
|
|
33
|
+
|
|
34
|
+
`useVenlyMock()` exposes the store controls. A credible end-to-end demo:
|
|
35
|
+
create party → `advanceVerification(id)` → create account → virtual bank account (note its `referenceCode`) → **fund it: `simulations.inbound.credit(vbaId, 500)`** (a new account holds nothing, as in production; an unfunded transfer is refused with `402 insufficient-funds`) → stage + confirm a transfer → `advanceTransfer(id)` → show the ledger, and `simulations.ledger.verify()` to prove it balances. Inject failures with `failNext("CONFLICT")` to show the stale-version approval path – error states are part of the product.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0 – 2026-08-19
|
|
4
|
+
|
|
5
|
+
The MCP now pushes a machine-checkable runtime contract instead of relying on
|
|
6
|
+
agents to discover composition guidance through optional pull surfaces.
|
|
7
|
+
|
|
8
|
+
- The initialize response carries server instructions that establish
|
|
9
|
+
`@venlyfinance/react` hooks and flow machines as the browser data plane,
|
|
10
|
+
`@venlyfinance/sdk` as the server data plane, and the shadcn registry as the
|
|
11
|
+
UI installation path.
|
|
12
|
+
- Every `get_journey_blueprint` response keeps its prose blueprint and adds the
|
|
13
|
+
same `runtime_contract` as fenced JSON and `structuredContent`: exact registry
|
|
14
|
+
dependencies, qualified hook imports, provider configuration, forbidden
|
|
15
|
+
hand-rolled patterns, install commands, and completion gates.
|
|
16
|
+
- New deterministic `verify` CLI and `verify_runtime_contract` MCP tool with
|
|
17
|
+
auto-detected `direct-sdk` and `backend-proxy` profiles. Exit codes match the
|
|
18
|
+
`review` CLI (0 clean, 1 on errors, 2 on usage/no-match), and
|
|
19
|
+
`venly-allow:<rule-id>` suppressions work on verifier findings.
|
|
20
|
+
- Money-route-without-SDK, in-memory-money-store, and direct-profile missing
|
|
21
|
+
React checks remain warning-only pending a false-positive-boundary ruling.
|
|
22
|
+
- Requires `@venlyfinance/sdk` ^0.5.0.
|
|
23
|
+
|
|
3
24
|
## 0.3.0 – 2026-08-04
|
|
4
25
|
|
|
5
|
-
Wording-is-the-safety-surface release. An outside integrator audit (
|
|
6
|
-
2026-08-04) found the server's words disagreeing with its behavior in three
|
|
26
|
+
Wording-is-the-safety-surface release. An outside integrator audit (2026-08-04) found the server's words disagreeing with its behavior in three
|
|
7
27
|
places; all fixed, plus the SDK under the mock now teaches the documented
|
|
8
28
|
lifecycle (see @venlyfinance/sdk 0.2.0).
|
|
9
29
|
|
|
@@ -104,3 +124,34 @@ Frontend toolset: the judgment layer for interface assembly.
|
|
|
104
124
|
## 0.4.1 – 2026-08-07
|
|
105
125
|
|
|
106
126
|
- `venly://frontend/agents` now carries the full cold-start recipe, learned from a fresh-agent build run: Tailwind + path-alias prerequisites before `shadcn init`, the non-interactive `-y -b radix -p nova` flags, that blocks land under `components/venly/` at the project root (relative imports, not the `@/` alias), that `shadcn add` auto-installs the npm dependencies, and the `.js`-extension bundler note.
|
|
127
|
+
|
|
128
|
+
## 0.5.0 – 2026-08-14 (backfill; published 2026-08-15)
|
|
129
|
+
|
|
130
|
+
Entry added retroactively in 0.6.0 – the publish predates it.
|
|
131
|
+
|
|
132
|
+
- Nine payout tools over the re-vendored finance contract (payouts, payout routes +
|
|
133
|
+
ownership proof, party payout bank accounts), fail-closed writes.
|
|
134
|
+
- Corrected capabilities text; react/ui alignment with the 0.4.0 SDK line.
|
|
135
|
+
|
|
136
|
+
## 0.6.0 – 2026-08-18
|
|
137
|
+
|
|
138
|
+
`review_screen` grows teeth: five new rule classes, a suppression hatch, and a CI-runnable command.
|
|
139
|
+
|
|
140
|
+
- New error rules: `invented-timing-claim` (copy promising durations, settlement windows
|
|
141
|
+
or custody behaviour no API in this stack returns), `intl-currency-crypto`
|
|
142
|
+
(`Intl.NumberFormat` + `style:"currency"` + a crypto code throws `RangeError` at render;
|
|
143
|
+
a variable-fed `currency:` in the same call is a warn), `required-rendered-optional`
|
|
144
|
+
(the payment reference labelled "(not required)"), `parity-fixture` (seeded 1:1
|
|
145
|
+
exchange rates).
|
|
146
|
+
- New warn rules: `blueprint-state-missing` (pass the new optional `journey` argument to
|
|
147
|
+
`review_screen` and the audit lists blueprint-named states not found in the source) and
|
|
148
|
+
`round-number-coincidence` (three or more `x.00` amounts seeded in one source).
|
|
149
|
+
- Suppression hatch on every rule, old and new: `venly-allow:<rule-id>` on the offending
|
|
150
|
+
line or the line above drops the finding silently.
|
|
151
|
+
- Copy-judging rules skip comment lines – a comment documenting a rule must not trip it.
|
|
152
|
+
- Findings now carry a 1-based `line`.
|
|
153
|
+
- New `review` CLI over the same audit: `npx @venlyfinance/settlement-mcp review "src/**/*.tsx"`
|
|
154
|
+
exits 1 on any error-severity finding (0 otherwise, 2 on usage errors/no matches), with
|
|
155
|
+
self-expanded globs and zero dependencies. Added a `settlement-mcp` bin alias so the
|
|
156
|
+
npx form resolves under npm's unscoped-name rule. This repo's CI now runs the command
|
|
157
|
+
over its own registry and example sources.
|
package/README.md
CHANGED
|
@@ -13,7 +13,9 @@ Start in explicit mock mode with no credentials or network. Move the same SDK
|
|
|
13
13
|
business logic to staging only after reviewing capabilities, compliance state and
|
|
14
14
|
the normalized write requests. Staging and production writes fail closed.
|
|
15
15
|
|
|
16
|
-
The Venly Finance builder surface documented here is the v0.
|
|
16
|
+
The Venly Finance builder surface documented here is the v0.7.0 release line.
|
|
17
|
+
|
|
18
|
+
Building with a coding agent? Start from [AGENTS.md](AGENTS.md) - the MCP also serves it as the `venly://frontend/agents` resource and pushes the core runtime doctrine in its initialize response.
|
|
17
19
|
|
|
18
20
|
## What it is
|
|
19
21
|
|
|
@@ -98,6 +100,15 @@ Operator writes: `approve_ramp_request`, `reject_ramp_request`. The legacy
|
|
|
98
100
|
Each is dry-run by default and returns the exact request it would send. See the
|
|
99
101
|
safety model below.
|
|
100
102
|
|
|
103
|
+
### Payout tools (since 0.5.0)
|
|
104
|
+
|
|
105
|
+
The payout surface of the finance contract, same tiering as above. Reads:
|
|
106
|
+
`list_payouts`, `get_payout`, `list_payout_routes`, `list_payout_bank_accounts`.
|
|
107
|
+
Writes (dry-run by default, fail-closed like every write):
|
|
108
|
+
`register_payout_bank_account`, `create_payout_route`,
|
|
109
|
+
`prepare_payout_ownership_proof`, `complete_payout_ownership_proof`,
|
|
110
|
+
`request_payout`.
|
|
111
|
+
|
|
101
112
|
### 3. x402 tool (position + stub)
|
|
102
113
|
|
|
103
114
|
`quote_x402_payment` returns an HTTP-402-shaped quote (price, asset, payTo,
|
|
@@ -113,16 +124,59 @@ Delivery of UI source rides the shadcn registry standard – add
|
|
|
113
124
|
to `components.json`, then `npx shadcn@latest add @venlyfinance/receive`. The MCP carries
|
|
114
125
|
what a registry cannot:
|
|
115
126
|
|
|
116
|
-
- `get_journey_blueprint` – screen inventory
|
|
117
|
-
|
|
127
|
+
- `get_journey_blueprint` – screen inventory and required states for eleven
|
|
128
|
+
money-product journeys, plus a machine-readable `runtime_contract` containing
|
|
129
|
+
exact package versions, qualified hooks, provider setup, forbidden patterns,
|
|
130
|
+
install commands and completion checks.
|
|
131
|
+
- `verify_runtime_contract` – deterministic runtime-contract checks over supplied
|
|
132
|
+
source and package metadata, using the same profiles and rules as the CLI.
|
|
118
133
|
- `review_screen` – deterministic design audit of a screen's source (raw colours,
|
|
119
|
-
hyphen-minus amounts, success styling on cancelled steps, masked review values,
|
|
120
|
-
|
|
134
|
+
hyphen-minus amounts, success styling on cancelled steps, masked review values,
|
|
135
|
+
invented timing/custody copy, crypto codes inside `Intl.NumberFormat` currency
|
|
136
|
+
formatting, required fields rendered optional, parity and round-number fixtures,
|
|
137
|
+
zebra striping, off-token shadows, colour-only state). Pass the optional `journey`
|
|
138
|
+
key to also check that every state the journey blueprint names appears in the
|
|
139
|
+
source. Findings, not a score.
|
|
121
140
|
- `venly://frontend/agents` – the composition rules an agent should read before building.
|
|
122
141
|
|
|
123
142
|
The `build_international_account` prompt assembles the interface from the registry and
|
|
124
143
|
gates every finished screen on `review_screen`. See [`ui/`](../ui/README.md) for the kit itself.
|
|
125
144
|
|
|
145
|
+
### Design-audit CLI
|
|
146
|
+
|
|
147
|
+
The same audit runs as a command, so a generated app can gate itself in CI:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npx @venlyfinance/settlement-mcp review "src/**/*.tsx"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Exit `1` on any error-severity finding, `0` otherwise (warnings print either way),
|
|
154
|
+
`2` on usage errors or a pattern that matches nothing. Suppress a deliberate,
|
|
155
|
+
justified exception with `venly-allow:<rule-id>` on the offending line or the line
|
|
156
|
+
above – the finding is dropped silently. This repo runs the same command over its
|
|
157
|
+
own registry sources in CI.
|
|
158
|
+
|
|
159
|
+
Scope the glob to component source, never to token or theme files: the
|
|
160
|
+
`raw-colour` rule fires on any hex/rgba literal by design, and a tokens/theme
|
|
161
|
+
css file is the one legitimate home of raw colour values (theming stays a
|
|
162
|
+
one-file edit). `"src/**/*.tsx"` is the right default.
|
|
163
|
+
|
|
164
|
+
### Runtime-contract CLI
|
|
165
|
+
|
|
166
|
+
Gate the data-plane composition alongside the screen audit:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npx @venlyfinance/settlement-mcp verify "src/**/*.{ts,tsx}"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The verifier auto-detects `direct-sdk` (browser provider + hooks) or
|
|
173
|
+
`backend-proxy` (browser proxy options + SDK-backed server routes). Override
|
|
174
|
+
with `--profile direct-sdk` or `--profile backend-proxy`. Exit codes are `0`
|
|
175
|
+
when no error findings exist, `1` when any error exists, and `2` for usage or
|
|
176
|
+
no-match failures; warnings print but do not fail. The same
|
|
177
|
+
`venly-allow:<rule-id>` token suppresses a deliberate finding on its line or
|
|
178
|
+
the line above.
|
|
179
|
+
|
|
126
180
|
## Safety model (fail closed)
|
|
127
181
|
|
|
128
182
|
Outside explicit mock mode, read-only/dry-run is the default posture. A staging
|
|
@@ -228,8 +282,9 @@ reads, and fail-closed write gate without mutating staging:
|
|
|
228
282
|
VENLY_CLIENT_ID=... VENLY_CLIENT_SECRET=... npm run smoke:staging
|
|
229
283
|
```
|
|
230
284
|
|
|
231
|
-
The command starts the MCP with `VENLY_ENV=staging`,
|
|
232
|
-
|
|
285
|
+
The command starts the MCP with `VENLY_ENV=staging`, verifies the discovery
|
|
286
|
+
surface against the exact tool/resource/prompt inventory pinned in the smoke
|
|
287
|
+
script itself, then reads parties, accounts, and reference data.
|
|
233
288
|
It deliberately removes `VENLY_MCP_LIVE` and `VENLY_MCP_PRODUCTION` from the child
|
|
234
289
|
process before submitting one confirmed `create_party` request. Passing requires that
|
|
235
290
|
request to return `mode: dry-run`, `environment: staging`, and an unarmed gate. Output
|
|
@@ -288,6 +343,7 @@ settlement-mcp/
|
|
|
288
343
|
resources.ts capability, safety and workflow resources
|
|
289
344
|
prompts.ts build_international_account prompt
|
|
290
345
|
results.ts text + structured output and error redaction
|
|
346
|
+
verify-cli.ts runtime-contract profiles, checks and CLI
|
|
291
347
|
staging-smoke.ts safe discovery/read/dry-run staging verification
|
|
292
348
|
client/
|
|
293
349
|
sdk-client.ts Adapter over @venlyfinance/sdk
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** Shared constants. The default environment is MOCK so an unconfigured run
|
|
2
2
|
* never touches real infrastructure; staging/production are explicit. */
|
|
3
3
|
export declare const SERVER_NAME = "venly-finance-mcp-server";
|
|
4
|
-
export declare const SERVER_VERSION = "0.
|
|
4
|
+
export declare const SERVER_VERSION = "0.7.0";
|
|
5
|
+
export declare const INSTRUCTIONS = "Venly Finance build advisor. This server is a build-time advisor, not your app's data plane. The data plane is the published packages: every read is a hook and every regulated lifecycle a flow machine from `@venlyfinance/react`, inside `<VenlyProvider environment=\"mock\">` \u2013 zero credentials, zero network; server-side code uses `@venlyfinance/sdk`. Hand-rolled fetch layers, in-memory money stores, or route handlers that re-implement transfers, balances, or approvals are off-contract and fail review. UI installs from the @venlyfinance shadcn registry: `npx shadcn@latest add @venlyfinance/balances @venlyfinance/send \u2026` (auto-installs the npm packages). Before scaffolding, read `venly://frontend/agents` \u2013 it is the composition doctrine (AGENTS.md). Consult `get_journey_blueprint` per screen; gate finished screens with `review_screen` and `npx @venlyfinance/settlement-mcp review \"src/**/*.tsx\"`.";
|
|
5
6
|
export declare const ENVIRONMENT_FLAG = "VENLY_ENV";
|
|
6
7
|
export type VenlyEnvironment = "mock" | "qa" | "staging" | "production";
|
|
7
8
|
export declare function resolveVenlyEnvironment(env: Record<string, string | undefined>): VenlyEnvironment;
|
package/dist/constants.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** Shared constants. The default environment is MOCK so an unconfigured run
|
|
2
2
|
* never touches real infrastructure; staging/production are explicit. */
|
|
3
3
|
export const SERVER_NAME = "venly-finance-mcp-server";
|
|
4
|
-
export const SERVER_VERSION = "0.
|
|
4
|
+
export const SERVER_VERSION = "0.7.0";
|
|
5
|
+
export const INSTRUCTIONS = `Venly Finance build advisor. This server is a build-time advisor, not your app's data plane. The data plane is the published packages: every read is a hook and every regulated lifecycle a flow machine from \`@venlyfinance/react\`, inside \`<VenlyProvider environment="mock">\` – zero credentials, zero network; server-side code uses \`@venlyfinance/sdk\`. Hand-rolled fetch layers, in-memory money stores, or route handlers that re-implement transfers, balances, or approvals are off-contract and fail review. UI installs from the @venlyfinance shadcn registry: \`npx shadcn@latest add @venlyfinance/balances @venlyfinance/send …\` (auto-installs the npm packages). Before scaffolding, read \`venly://frontend/agents\` – it is the composition doctrine (AGENTS.md). Consult \`get_journey_blueprint\` per screen; gate finished screens with \`review_screen\` and \`npx @venlyfinance/settlement-mcp review "src/**/*.tsx"\`.`;
|
|
5
6
|
export const ENVIRONMENT_FLAG = "VENLY_ENV";
|
|
6
7
|
export function resolveVenlyEnvironment(env) {
|
|
7
8
|
// Default is MOCK (since 0.3.0): the mock-first product must not point at
|
package/dist/frontend.d.ts
CHANGED
|
@@ -9,13 +9,33 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
11
|
export declare const REGISTRY_URL_TEMPLATE = "https://raw.githubusercontent.com/Venly/venly-settlement-sdk/main/ui/r/{name}.json";
|
|
12
|
+
declare const JOURNEYS: {
|
|
13
|
+
readonly auth: "# Auth (sign-in, 2FA, sign-up)\nShell: outside the app shell - a centred card column.\nRegistry items: venly-tokens; block: auth (SignInForm, TwoFactorForm, SignUpForm).\nBinding: an AuthAdapter YOU implement - the Venly APIs authenticate machines\n(client credentials), never people, so end-user auth is your identity layer\n(OAuth/OIDC, Better Auth, Auth0, Clerk, Keycloak). createMockAuthAdapter ships\nfor demos: deterministic 2FA code 000000, expireSession() driver.\nStates that must exist: signed out, bad credentials (ONE combined message -\nno user enumeration), 2FA challenge with wrong-code path, session expired\n(session() returns null - redirect, no other signal), duplicate sign-up email.\nRules that must hold: credential errors never confirm which half was wrong;\nthe code field is six slots with paste distribution and full keyboard support;\nthe mock never claims an email was sent.";
|
|
14
|
+
readonly team: "# Team\nShell: in-shell content column.\nRegistry items: venly-tokens, data-table, status-pill; block: team (TeamTable, InviteDialog).\nBinding: a TeamAdapter over your auth provider (createMockTeamAdapter for demos).\nStates that must exist: ACTIVE/INVITED/DISABLED members on first paint, invite\ncreated (display-only link in mock - never a fake sent-email claim), role\nchange persisting, self-actions blocked with the reason.\nRules that must hold: member status is word + glyph; role controls live in the\nrow; you cannot change your own role or disable yourself - the control is\ndisabled AND explains why.";
|
|
15
|
+
readonly "home-balances": "# Home / balances\nShell: left nav rail + thin top bar; full-width content.\nRegistry items: venly-tokens, balance-card, data-table, status-pill; block: balances (BalancesBlock, BalanceMiniature).\nHooks: useAccounts, useWallets; balances rendered per asset across the account's wallets.\nStates that must exist: loading, zero balances (first-run guidance), reserved buckets, entirely reserved (available 0 rendered honestly - the acct-escrow seed exercises it), balance load error degrading locally with a retry.\nRules that must hold: available is the emphasised figure and the only one above the rule; reserved is demoted by position and scale, never colour, and carries the still-yours qualifier; unspendable buckets carry the padlock; masking covers every figure including the chrome miniature; arithmetic mismatches are surfaced, never corrected; never assume stablecoin parity - render the quoted rate.";
|
|
16
|
+
readonly receive: "# Receive\nShell: content column - a warning callout, the field card, an advisory below.\nRegistry items: venly-tokens, field-list; block: receive.\nHooks: useVirtualBankAccounts (first active EUR account).\nStates that must exist: no virtual bank account yet (offer creation), details present, reference not yet assigned (\"Not assigned yet\" + Required pill - never \"(not required)\").\nRules that must hold: the payment reference is enforced as mandatory (amber Required pill, warning above the fields); per-field copy names the field it copied and only confirms on a successful write; rows never vanish - render the \"(not required)\" variant.";
|
|
17
|
+
readonly send: "# Send\nShell: full page; form clamped ~600px; review step replaces the form.\nRegistry items: venly-tokens, arithmetic-ladder, timeline; block: send.\nHooks: useStagedTransfer (the machine IS the flow), useFeeQuote when fees apply.\nStates that must exist: draft (validation issues listed), staged review, submitting, pending (polling), completed, failed (reason shown, terminal).\nRules that must hold: money movement is stage-then-confirm - the review renders the exact staged request as an arithmetic ladder (working before the answer, uncertainty attached to the number); the commit button restates the amount and never carries a countdown; values are never masked on review; execution is single-shot on an idempotency key pinned at staging.";
|
|
18
|
+
readonly activity: "# Activity\nShell: full-width table + side panel.\nRegistry items: venly-tokens, data-table, status-pill, side-panel, timeline; block: activity.\nHooks: useTransfers (and useRampRequests where ramps are in scope).\nStates that must exist: loading, empty ledger, rows with pending/failed pills, open detail panel that stays in sync with refetches.\nRules that must hold: a row click opens the panel, never navigates; no scrim - the source row stays tinted; settled rows stay quiet (colour is a budget; pills only where action or failure lives); the panel's hero is the amount; the failure reason rides the terminal timeline node.";
|
|
19
|
+
readonly "onboarding-status": "# Onboarding / verification status\nShell: full page, form clamped ~600px; a status home once submitted.\nRegistry items: venly-tokens, timeline, status-pill, field-list; block: onboarding (CompanyForm, VerificationStatusHome, RestrictedBanner).\nHooks: useCreateParty, useCreateAccount, useParty, useAccount; verification status from the party/account records verbatim.\nStates that must exist: collecting (review before submit), submitted/waiting (say who acts next, on which channel, what still works meanwhile), approved, declined (humane copy, review-request as the primary action), re-verification on a live account (banner naming what pauses and what keeps working).\nRules that must hold: never render a fake progress percentage - use real status; a waiting state answers how long / who acts / what still works, and where no review window is published the copy says so instead of inventing one; a decline explains and offers a next step, not a dead end; creating a party is NOT completed verification - show the honest state.";
|
|
20
|
+
readonly "withdraw-bank-accounts": "# Withdraw + bank accounts (off-ramp)\nShell: settings page for the whitelist; full page for the flow, form clamped ~600px.\nRegistry items: venly-tokens, data-table, status-pill, timeline, field-list, arithmetic-ladder; blocks: bank-accounts (BankAccountsBlock, AddBankAccountForm), withdraw (WithdrawFlow, WithdrawalsTable, ConnectedWithdrawDetail).\nHooks: useCompanyBankAccounts, useBankAccountConfig, useCreateCompanyBankAccount, useRampRequests, useRampRequest, useCreateRampRequest, useFeeQuote, useRampPairs, useReferenceData, useFourEyesApproval, useInitiateRamp, describeRampStatus.\nStates that must exist: empty whitelist (one CTA), account in review / verified / declined, no-verified-destination block, amount over balance (two-place signal), fee quote with its unit, awaiting approval (creator sees why they can't approve), stale decision (409 - refetch and re-decide, never auto-retry), awaiting funds (deposit instructions + mandatory reference + tx-hash report), processing, paid out, failed, rejected, cancelled, on hold.\nRules that must hold: destinations are the company's OWN verified accounts - unverified rows are disabled with the reason, never hidden; the pre-create review renders only known figures (no invented rate, no bank-receives placeholder - the created record carries the fiat arithmetic and the detail opens on it); a refusal never reads as a wait; the event timeline renders actor, role and absolute timestamps.";
|
|
21
|
+
readonly reconciliation: "# Reconciliation\nShell: split pane (roughly one-third list, two-thirds evidence) - not a drawer.\nRegistry items: venly-tokens, data-table, side-panel, status-pill, field-list.\nHooks: reconcile_by_reference_code (MCP composite) or useVirtualBankAccounts + useTransfers joined on referenceCode.\nStates that must exist: matched, unmatched with candidate expectations, partial/many-to-one with a live shortfall figure, resolved.\nRules that must hold: show per-signal match rationale (which fields agree), never a bare score; keep zero-counts visible - an empty exception queue is information; keyboard row-stepping for review throughput.";
|
|
22
|
+
readonly "proof-of-segregation": "# Proof of segregation\nShell: content column, single card.\nRegistry items: venly-tokens, field-list, balance-card.\nHooks: useWallets, useAccount; on-chain balance beside the ledger figure.\nStates that must exist: reconciled (figures agree, timestamped), reconciling, source unavailable (say so - never render a stale figure as current).\nRules that must hold: the wallet address renders monospace with copy; the on-chain figure and ledger figure sit side by side with their as-of times; discrepancies are stated, not smoothed.";
|
|
23
|
+
readonly approvals: "# Approvals\nShell: full-width queue + side panel tailored to the approver.\nRegistry items: venly-tokens, data-table, status-pill, side-panel, timeline.\nHooks: useRampRequests, useFourEyesApproval (capability decides what renders), useRampLifecycle.\nStates that must exist: queue with awaiting-approval items, detail with the decision context beside the figures, applied, stale-version (someone acted first - refetch and re-decide), creator-view (cannot approve own request - render the rule, not a disabled mystery button).\nRules that must hold: the optimistic-locking version travels with every decision; a 409 means re-decide against fresh state, never auto-retry; reject requires a reason; the creator sees why they cannot approve.";
|
|
24
|
+
readonly "console-review-queue": "# Console review queue (the operator worklist)\nShell: left nav rail + thin top bar, full-width content, and a page-edge\nenvironment banner naming mock mode. Not a consumer surface - density rules\napply.\nRegistry items: venly-tokens, data-table, status-pill, money, list-error. The\nregistry has no console block yet: compose these primitives.\nHooks: useAccounts, useParties. The queue's own state is DERIVED on every\nrender - never stored, never cached as a status.\nBinding: sections are ACTORS, not statuses - your move, waiting on the customer,\nwaiting on a provider, then a collapsed closed section, so the reviewer's own\nworklist is the top band by construction.\nStates that must exist: loading, your move, waiting on the customer, waiting on a provider, closed, empty queue, filtered to nothing, list error.\nRules that must hold: the whose-move value is a pure function of enum values on\nthe row - no clock reading, no threshold, no configuration and no default, and a\ncombination the mapping does not cover renders NO value plus an explicit\nnot-recognised line, which is a bug report rather than a guess; never a target\ntime, a breach colour or an overdue state, because the API publishes no targets\nand an invented one is the same defect as an invented fee; an age column is\nlabelled for what it actually measures - a created-at delta is \"Age\", and only a\nduration the API itself computes may be called time in state; empty sections are\nstill drawn as a zero header row, because nothing-to-do is information; loading\nis a skeleton that preserves column geometry exactly, never prose; a row click\nopens a side panel and never navigates; one status pill per row - the whose-move\nvalue is plain text, since two pills read as two states.";
|
|
25
|
+
readonly "console-decision-detail": "# Console decision detail (evidence, ceremony, audit trail)\nShell: side panel about 30% wide over the queue - no scrim, the table stays\nvisible clipped at the panel edge and the source row stays tinted. Escalate to a\n35/65 split only when the evidence outgrows the panel. Evidence goes on the\nLEFT: this is a judging task, not an authoring one.\nRegistry items: venly-tokens, side-panel, timeline, field-list, status-pill,\nmoney, data-table.\nHooks: useAccount, useParty, useWallets, useTransfers,\nuseVirtualBankAccounts, useVenlyMock (the trail reads the mock's event log).\nTwo timeline columns, not one feed: the decision chain (who decided what, when,\nin which seat) beside money movement on the same subject. Different actors,\ndifferent audiences; merging them is what makes an audit trail unreadable.\nStates that must exist: loading, evidence present, evidence unavailable, decision owed, decision applied, stale decision, terminal decision, frozen, empty trail.\nRules that must hold: every evidence row is either a real field path or a\nlabelled omission, and an omission is a FIRST-CLASS type in the component's\nprops, so a placeholder cannot be rendered where a gap belongs;\nomission copy states only what is verified and never implies a result, a\npending state, or a clean one - and never mentions the API contract, which is\ndeveloper diagnostics rather than operator language; a field the API cannot\ncarry is captured anyway when the work needs it, and rendered with a visible\nbadge saying it is a console note rather than API state; every decision carries the\noptimistic-locking version, and a conflict means refetch and re-decide against\nfresh state, never auto-retry; every transition the console causes leaves a\ntimeline node with actor, role and a timezone-qualified absolute timestamp, so a\nstatus change with no node is a bug; a store resync is a system line, not a\ndecision node; the panel footer carries row-stepping key chips so the reviewer\nmoves row to row without closing.";
|
|
26
|
+
readonly "console-pricing-config": "# Console pricing configuration\nShell: in-shell content column. A config screen, not a queue: no whose-move\nvalue and no aging.\nRegistry items: venly-tokens, data-table, arithmetic-ladder, field-list.\nHooks: useCompanyFees.\nBinding: the fee data the packages actually serve is a VOLUME-TIER model - tier\nname, ramp direction, minimum and maximum volume, percentage, version - and it\nis the same model the shipped withdrawal quote consumes, so this screen shows\nwhere a real quote comes from. A second, richer per-rail configuration model\nexists on an internal plane and is NOT served here; it renders as a labelled\nomission, never as an empty form.\nStates that must exist: loading, tiers present, no tiers, configuration unavailable, worked example, save failed.\nRules that must hold: a worked arithmetic ladder is mandatory on the tier\nsection - a sample amount times the tier percentage, with the operator glyphs in\na left gutter - because a pricing screen that shows only stored numbers teaches\nnothing; the ladder renders ONLY over data that exists, never over the omitted\nsection; the tier\na sample amount falls into is highlighted in the table so the row and the ladder\nare visibly the same fact; a single-member enum renders as a disabled\nsingle-value field that says so, not a select pretending at choice; a date\nwindow that has not opened reads scheduled, never active; forms are single\ncolumn with the field width capped, label above input, helper text between them\nand the error below.";
|
|
27
|
+
readonly "console-simulator": "# Sandbox simulator (play the counterparty)\nShell: its own chrome - a scrimmed right-hand drawer on a distinct surface with\na persistent sandbox label, reachable from ONE fixed affordance in the top bar.\nIt is the only scrimmed drawer in the console, so the surface change alone\nsignals the register change.\nRegistry items: venly-tokens, field-list, status-pill, money.\nHooks: useVenlyMock. Every control maps to exactly one call on the mock's\nsimulations namespace - no control without a call, and no call renamed.\nBinding: inbound credits, provider progression and screening verdicts are things\nOTHER parties do, so they live here rather than in the operator's workflow.\nStates that must exist: drawer closed, drawer open, sharing, not sharing, credit landed, verdict returned, payout advanced, books balanced, books do not balance, reset.\nRules that must hold: controls are phrased as events that happen to you, in the\nthird person, while operator controls elsewhere are imperative decisions - a\ncontrol phrased in the wrong voice is in the wrong surface; a simulated\ntransition emits the SAME event the real path emits, and the trail attributes it\nto the simulator plainly rather than to an operator; the drawer is reachable\nonly from the top bar, never from a queue row or a decision panel, because those\npaths make another party's action look like the operator's; the ledger check gets\na visible surface: it is the one control here that asserts something true, namely\nthat the simulated books balance; the channel footer states the adapter, session\nand peer count, and says IN WORDS when the surface is not actually sharing - the\ndefault channel shares nothing and cross-context sharing is same-origin only, so\nwithout that line a two-context demo can prove nothing while looking correct.";
|
|
28
|
+
};
|
|
29
|
+
type JourneyKey = keyof typeof JOURNEYS;
|
|
12
30
|
interface Finding {
|
|
13
31
|
rule: string;
|
|
14
32
|
severity: "error" | "warn";
|
|
15
33
|
evidence: string;
|
|
16
34
|
fix: string;
|
|
35
|
+
/** 1-based line the finding fired on - lets a CLI print path:line. */
|
|
36
|
+
line?: number;
|
|
17
37
|
}
|
|
18
38
|
/** Deterministic design audit. Text in, findings out - no model, no taste. */
|
|
19
|
-
export declare function reviewScreenSource(source: string): Finding[];
|
|
39
|
+
export declare function reviewScreenSource(source: string, journey?: JourneyKey): Finding[];
|
|
20
40
|
export declare function registerFrontendTools(server: McpServer): void;
|
|
21
41
|
export {};
|