@hyperscale0/adl 1.0.0-alpha.2 → 1.0.0-beta.10

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +46 -2
  2. package/LICENSE.md +356 -0
  3. package/NOTICE.md +8 -0
  4. package/README.md +29 -140
  5. package/conformance/cases.json +50 -764
  6. package/conformance/subject-adapter.ts +31 -0
  7. package/dist/boundary-fixture.d.ts +24 -0
  8. package/dist/boundary-fixture.d.ts.map +1 -0
  9. package/dist/boundary-fixture.js +34 -0
  10. package/dist/boundary-fixture.js.map +1 -0
  11. package/dist/index.d.ts +68 -156
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +100 -77
  14. package/dist/index.js.map +1 -1
  15. package/dist/vocabulary.d.ts +3 -57
  16. package/dist/vocabulary.d.ts.map +1 -1
  17. package/dist/vocabulary.js +12 -84
  18. package/dist/vocabulary.js.map +1 -1
  19. package/docs/assets/adl.svg +11 -0
  20. package/docs/assets/brand/adl-horizontal-white.svg +1 -0
  21. package/docs/assets/brand/adl-horizontal.svg +1 -0
  22. package/docs/assets/brand/adl-stacked-white.svg +1 -0
  23. package/docs/assets/brand/adl-stacked.svg +1 -0
  24. package/docs/assets/brand/manifest.json +30 -0
  25. package/docs/authoring-guide.md +37 -416
  26. package/package.json +17 -12
  27. package/spec/manifest.schema.json +646 -439
  28. package/LICENSE +0 -661
  29. package/LICENSING.md +0 -51
  30. package/TRADEMARKS.md +0 -30
  31. package/dist/conformance.d.ts +0 -41
  32. package/dist/conformance.d.ts.map +0 -1
  33. package/dist/conformance.js +0 -486
  34. package/dist/conformance.js.map +0 -1
  35. package/examples/meridian-bank/README.md +0 -65
  36. package/examples/meridian-bank/example.spec.ts +0 -162
  37. package/examples/meridian-bank/fixtures/payout-status-replay.json +0 -19
  38. package/examples/meridian-bank/fixtures/payout-status-settled.json +0 -19
  39. package/examples/meridian-bank/fixtures/payout-submit-accepted.json +0 -26
  40. package/examples/meridian-bank/fixtures/payout-submit-declined.json +0 -25
  41. package/examples/meridian-bank/fixtures/statement-camt053.json +0 -45
  42. package/examples/meridian-bank/fixtures/webhook-payout-settled.json +0 -11
  43. package/examples/meridian-bank/index.ts +0 -262
@@ -1,65 +0,0 @@
1
- # Meridian Bank
2
-
3
- A complete, certified ADL adapter for a bank that does not exist. Every endpoint,
4
- status token, charge narrative, and account number here is invented, and the
5
- domain `meridian-bank.example` is reserved by RFC 2606 so nothing in this
6
- directory can point at a real institution.
7
-
8
- It exists for two reasons: it is the thing to copy when you write your own,
9
- and it is the proof that a third party can pass conformance with nothing but
10
- the published package.
11
-
12
- ## What is here
13
-
14
- | File | What it is |
15
- | ----------------- | ------------------------------------------------------------------------------------------- |
16
- | `index.ts` | The adapter: vocabulary, profile, config, two capabilities. Read it top to bottom. |
17
- | `fixtures/*.json` | Meridian's own bytes: two command responses, two status enquiries, a statement, a callback. |
18
- | `example.spec.ts` | Conformance, plus the checks that keep the declaration honest against the fixtures. |
19
-
20
- ## Meridian, briefly
21
-
22
- An imaginary European bank with a REST API:
23
-
24
- - **Errors ride the HTTP status.** A rejected payment is a 4xx with prose in
25
- the body, which is `envelope: "http_status_error_body"`. A bank that answered
26
- 200 with a rejection token in the body would be `http_200_body_status`
27
- instead, and the class is declared per operation because real providers are
28
- inconsistent across their own endpoints.
29
- - **A payment is re-read by the reference Meridian minted**, so
30
- `statusEnquiry.keys` is `provider_reference` and the path template carries
31
- `{providerReference}`. That is the read-after-write: a submit whose response
32
- we never saw is re-read, never resubmitted.
33
- - **Incoming credits are learned from statements**, so the `bank_credit`
34
- capability declares `egress: "none"` and records what observation saw.
35
- - **Fees and tax post separately** as `MERIDIAN FEE ...` and `MERIDIAN TAX ...`
36
- debits, which is why both narrative prefixes are declared: unrecognized, they
37
- would land as unexplained statement lines.
38
- - **The statement echoes our reference**, so `statementDebitReference` is
39
- `customer_reference`. Reading Meridian's own entry id instead would make
40
- every payout we instructed look like an unexplained debit.
41
-
42
- ## Run it
43
-
44
- From the package root:
45
-
46
- ```bash
47
- bun run conformance -- ./examples/meridian-bank
48
- bun test examples/meridian-bank
49
- ```
50
-
51
- ## Copying it
52
-
53
- 1. Copy the directory and rename `meridian` throughout. The provider key is
54
- lowercase and machine-shaped (`^[a-z][a-z0-9_.-]{0,79}$`).
55
- 2. Replace the vocabulary with the operations and resources your product
56
- actually uses.
57
- 3. Replace every profile fact with what your bank does. Do not carry a
58
- Meridian value across because it looked plausible: a fact you have not
59
- established is declared absent, and conformance will tell you which ones
60
- are still open.
61
- 4. Replace the fixtures with real captures from your provider's sandbox, and
62
- keep `example.spec.ts` pointed at them. A declaration that certifies but
63
- cannot read its own sandbox capture is not finished.
64
-
65
- `../../docs/authoring-guide.md` explains every field.
@@ -1,162 +0,0 @@
1
- /**
2
- * The example's own test, and the shape yours should take: conformance is
3
- * the gate, and the fixtures keep the declaration honest against the bytes
4
- * the bank actually sends. A declaration that certifies but cannot read its
5
- * own sandbox capture is not finished.
6
- */
7
-
8
- import { readFileSync } from "node:fs";
9
-
10
- import { partnerBankConformanceFindings } from "@hyperscale0/adl";
11
- import { describe, expect, test } from "bun:test";
12
-
13
- import { meridianAdapters, meridianProfile } from "./index.js";
14
-
15
- interface ExchangeFixture {
16
- readonly kind: "command" | "enquiry";
17
- readonly operation: string;
18
- readonly response: {
19
- readonly httpStatus: number;
20
- readonly body: Record<string, unknown>;
21
- };
22
- }
23
-
24
- interface StatementFixture {
25
- readonly format: string;
26
- readonly entries: readonly {
27
- readonly amount: string;
28
- readonly customerReference: string | null;
29
- readonly narrative: string;
30
- }[];
31
- }
32
-
33
- interface NotificationFixture {
34
- readonly eventKind: string;
35
- readonly payload: Record<string, unknown>;
36
- }
37
-
38
- function fixture<T>(name: string): T {
39
- return JSON.parse(
40
- readFileSync(new URL(`./fixtures/${name}.json`, import.meta.url), "utf8"),
41
- ) as T;
42
- }
43
-
44
- const exchanges = [
45
- fixture<ExchangeFixture>("payout-submit-accepted"),
46
- fixture<ExchangeFixture>("payout-submit-declined"),
47
- fixture<ExchangeFixture>("payout-status-settled"),
48
- fixture<ExchangeFixture>("payout-status-replay"),
49
- ];
50
- const statement = fixture<StatementFixture>("statement-camt053");
51
- const notification = fixture<NotificationFixture>("webhook-payout-settled");
52
-
53
- const [payoutAdapter] = meridianAdapters;
54
- if (!payoutAdapter) throw new Error("payout adapter missing");
55
-
56
- /** The same normalization a runtime uses to match a status token. */
57
- function normalize(token: string): string {
58
- return token.trim().toUpperCase();
59
- }
60
-
61
- const canonicalStateFor = new Map(
62
- Object.entries(meridianProfile.lifecycleVocabulary).map(
63
- ([spelling, state]) => [normalize(spelling), state as string],
64
- ),
65
- );
66
-
67
- describe("meridian bank adapter", () => {
68
- test("every capability certifies with zero findings", () => {
69
- for (const adapter of meridianAdapters) {
70
- expect({
71
- capability: adapter.capability,
72
- findings: partnerBankConformanceFindings(adapter),
73
- }).toEqual({ capability: adapter.capability, findings: [] });
74
- }
75
- });
76
-
77
- test("the lifecycle vocabulary spells every token the bank sends", () => {
78
- for (const exchange of exchanges) {
79
- const token = exchange.response.body["status"];
80
- expect(typeof token).toBe("string");
81
- expect(canonicalStateFor.get(normalize(token as string))).toBeDefined();
82
- }
83
- });
84
-
85
- test("the declared envelope class is how the fixtures actually read", () => {
86
- // http_status_error_body: the HTTP status carries the outcome and a
87
- // failure body is prose. Reading the body of a 2xx for a rejection, or
88
- // treating a 4xx as a lost response, would strand the payment.
89
- expect(payoutAdapter.operationMap["payout.submit"]?.envelope).toBe(
90
- "http_status_error_body",
91
- );
92
- for (const exchange of exchanges) {
93
- if (exchange.kind !== "command") continue;
94
- const state = canonicalStateFor.get(
95
- normalize(exchange.response.body["status"] as string),
96
- );
97
- if (state === "failed") {
98
- expect(exchange.response.httpStatus).toBeGreaterThanOrEqual(400);
99
- expect(typeof exchange.response.body["message"]).toBe("string");
100
- continue;
101
- }
102
- expect(exchange.response.httpStatus).toBeLessThan(400);
103
- }
104
- });
105
-
106
- test("the statement format is declared and has a fetch window", () => {
107
- const formats: readonly string[] = meridianProfile.statementFormats;
108
- expect(formats).toContain(statement.format);
109
- const windows: Record<string, unknown> = meridianProfile.windows.statements;
110
- expect(windows[statement.format]).toBeDefined();
111
- });
112
-
113
- test("the charge narratives recognize the bank's own debits", () => {
114
- const startsWithAny = (narrative: string, prefixes: readonly string[]) =>
115
- prefixes.some((prefix) => normalize(narrative).startsWith(prefix));
116
-
117
- const { charge, vat } = meridianProfile.charges.narratives;
118
- const chargeLines = statement.entries.filter((entry) =>
119
- startsWithAny(entry.narrative, charge),
120
- );
121
- const vatLines = statement.entries.filter((entry) =>
122
- startsWithAny(entry.narrative, vat),
123
- );
124
- expect(chargeLines).toHaveLength(1);
125
- expect(vatLines).toHaveLength(1);
126
- });
127
-
128
- test("an instructed payout is attributable on the statement", () => {
129
- // statementDebitReference says which reference comes back. Every debit
130
- // that is not one of the bank's own charge lines must carry it, or the
131
- // ledger cannot prove the debit was ours.
132
- expect(meridianProfile.statementDebitReference).toBe("customer_reference");
133
- const chargePrefixes = [
134
- ...meridianProfile.charges.narratives.charge,
135
- ...meridianProfile.charges.narratives.vat,
136
- ];
137
- const instructedDebits = statement.entries.filter(
138
- (entry) =>
139
- entry.amount.startsWith("-") &&
140
- !chargePrefixes.some((prefix) =>
141
- normalize(entry.narrative).startsWith(prefix),
142
- ),
143
- );
144
- expect(instructedDebits).not.toHaveLength(0);
145
- for (const debit of instructedDebits) {
146
- expect(typeof debit.customerReference).toBe("string");
147
- }
148
- });
149
-
150
- test("the webhook plan describes the callback the bank sends", () => {
151
- const plan = Object.entries(payoutAdapter.webhookMap).find(
152
- ([eventKind]) => eventKind === notification.eventKind,
153
- )?.[1];
154
- expect(plan).toBeDefined();
155
- if (!plan) return;
156
- for (const field of plan.requiredPayloadFields) {
157
- expect(notification.payload[field]).toBeDefined();
158
- }
159
- expect(notification.payload[plan.resourceIdField]).toBeDefined();
160
- expect(notification.payload[plan.timestampField]).toBeDefined();
161
- });
162
- });
@@ -1,19 +0,0 @@
1
- {
2
- "kind": "enquiry",
3
- "operation": "payout.submit",
4
- "request": {
5
- "method": "GET",
6
- "path": "/v2/payments/MER-9F3K2"
7
- },
8
- "response": {
9
- "httpStatus": 200,
10
- "body": {
11
- "providerReference": "MER-9F3K2",
12
- "paymentReference": "HS-PAY-0007",
13
- "status": "REPLAY_SETTLED",
14
- "originalProviderReference": "MER-9F3K2",
15
- "valueDate": "2026-03-03",
16
- "settledAt": "2026-03-03T09:04:41Z"
17
- }
18
- }
19
- }
@@ -1,19 +0,0 @@
1
- {
2
- "kind": "enquiry",
3
- "operation": "payout.submit",
4
- "request": {
5
- "method": "GET",
6
- "path": "/v2/payments/MER-9F3K2"
7
- },
8
- "response": {
9
- "httpStatus": 200,
10
- "body": {
11
- "providerReference": "MER-9F3K2",
12
- "paymentReference": "HS-PAY-0007",
13
- "status": "SETTLED",
14
- "valueDate": "2026-03-03",
15
- "settledAt": "2026-03-03T09:04:41Z",
16
- "chargeAmount": "1.50"
17
- }
18
- }
19
- }
@@ -1,26 +0,0 @@
1
- {
2
- "kind": "command",
3
- "operation": "payout.submit",
4
- "request": {
5
- "method": "POST",
6
- "path": "/v2/payments",
7
- "body": {
8
- "instructionId": "HS-INS-0007",
9
- "endToEndId": "HS-E2E-0007",
10
- "paymentReference": "HS-PAY-0007",
11
- "amount": "2500.00",
12
- "currency": "EUR",
13
- "creditorName": "Acme Ltd",
14
- "creditorAccount": "IE29MERI93115212345678",
15
- "requestedValueDate": "2026-03-03"
16
- }
17
- },
18
- "response": {
19
- "httpStatus": 202,
20
- "body": {
21
- "providerReference": "MER-9F3K2",
22
- "status": "QUEUED",
23
- "receivedAt": "2026-03-02T13:20:05Z"
24
- }
25
- }
26
- }
@@ -1,25 +0,0 @@
1
- {
2
- "kind": "command",
3
- "operation": "payout.submit",
4
- "request": {
5
- "method": "POST",
6
- "path": "/v2/payments",
7
- "body": {
8
- "instructionId": "HS-INS-0008",
9
- "endToEndId": "HS-E2E-0008",
10
- "paymentReference": "HS-PAY-0008",
11
- "amount": "40.00",
12
- "currency": "EUR",
13
- "creditorName": "Closed Account Ltd",
14
- "creditorAccount": "IE29MERI93115299999999",
15
- "requestedValueDate": "2026-03-03"
16
- }
17
- },
18
- "response": {
19
- "httpStatus": 422,
20
- "body": {
21
- "status": "DECLINED",
22
- "message": "creditor account is closed"
23
- }
24
- }
25
- }
@@ -1,45 +0,0 @@
1
- {
2
- "kind": "statement",
3
- "format": "camt_053",
4
- "account": "IE29MERI93115200000001",
5
- "statementDate": "2026-03-03",
6
- "entries": [
7
- {
8
- "entryReference": "MER-ENT-88213",
9
- "customerReference": "HS-PAY-0007",
10
- "amount": "-2500.00",
11
- "currency": "EUR",
12
- "narrative": "PAYMENT TO ACME LTD",
13
- "bookingDate": "2026-03-03",
14
- "valueDate": "2026-03-03"
15
- },
16
- {
17
- "entryReference": "MER-ENT-88214",
18
- "customerReference": null,
19
- "amount": "-1.50",
20
- "currency": "EUR",
21
- "narrative": "MERIDIAN FEE HS-PAY-0007",
22
- "bookingDate": "2026-03-03",
23
- "valueDate": "2026-03-03"
24
- },
25
- {
26
- "entryReference": "MER-ENT-88215",
27
- "customerReference": null,
28
- "amount": "-0.35",
29
- "currency": "EUR",
30
- "narrative": "MERIDIAN TAX HS-PAY-0007",
31
- "bookingDate": "2026-03-03",
32
- "valueDate": "2026-03-03"
33
- },
34
- {
35
- "entryReference": "MER-ENT-88301",
36
- "customerReference": null,
37
- "amount": "1200.00",
38
- "currency": "EUR",
39
- "narrative": "INBOUND TRANSFER FROM BUYER 4471",
40
- "financialAddressId": "fa_2299",
41
- "bookingDate": "2026-03-03",
42
- "valueDate": "2026-03-03"
43
- }
44
- ]
45
- }
@@ -1,11 +0,0 @@
1
- {
2
- "kind": "notification",
3
- "eventKind": "payout.settled",
4
- "payload": {
5
- "payoutId": "pyt_01HQ8",
6
- "providerReference": "MER-9F3K2",
7
- "valueDate": "2026-03-03",
8
- "chargeAmount": "1.50",
9
- "completedAt": "2026-03-03T09:04:41Z"
10
- }
11
- }
@@ -1,262 +0,0 @@
1
- /**
2
- * Meridian Bank: a complete, certified adapter for a bank that does not
3
- * exist. Every endpoint, status token, and charge narrative below is
4
- * invented. It is here to be read top to bottom and copied.
5
- *
6
- * The order of the file is the order you write one:
7
- *
8
- * 1. the vocabulary -- the closed operation, resource, and webhook unions
9
- * your product uses;
10
- * 2. the profile -- what the bank does, as facts;
11
- * 3. the config -- where the credentials live, never what they are;
12
- * 4. the adapters -- one per capability, validated at import.
13
- *
14
- * `docs/authoring-guide.md` explains every field. `fixtures/` holds the
15
- * bank's own bytes, and `example.spec.ts` proves the declaration matches
16
- * them.
17
- */
18
-
19
- import {
20
- createProviderAdapterRegistry,
21
- type PartnerBankAdapterProfile,
22
- type ProviderAdapterVocabulary,
23
- type ProviderConfig,
24
- } from "@hyperscale0/adl";
25
-
26
- /**
27
- * The slice of the platform vocabulary this bank serves, spelled as closed
28
- * literal unions. Nothing here comes from a private import: the operation and
29
- * resource names are the published ones.
30
- */
31
- export interface MeridianVocabulary extends ProviderAdapterVocabulary {
32
- readonly capability: "payout_execution" | "bank_credit";
33
- readonly domain: "payouts" | "deposits";
34
- readonly meter: "external_payout";
35
- readonly obligation:
36
- | "payout_submission"
37
- | "payout_execution"
38
- | "payout_return"
39
- | "bank_credit";
40
- readonly operation:
41
- | "payout.submit"
42
- | "payout.complete"
43
- | "payout.fail"
44
- | "payout.return"
45
- | "deposit.record";
46
- readonly resource: "payout" | "deposit";
47
- readonly webhookOperation: "payout.complete" | "deposit.record";
48
- readonly webhookResource: "payout" | "deposit";
49
- readonly webhookResourceId: "payoutId" | "depositId";
50
- }
51
-
52
- const defineMeridianAdapters =
53
- createProviderAdapterRegistry<MeridianVocabulary>();
54
-
55
- /**
56
- * Meridian as its documentation and its sandbox describe it. Each fact is
57
- * something a runtime would otherwise have to guess, and a guess here is a
58
- * stranded payment.
59
- */
60
- export const meridianProfile = {
61
- kind: "partner_bank",
62
- wireCodec: "rest_json",
63
- auth: {
64
- // Machine-to-machine client credentials, plus a detached signature over
65
- // the request body that Meridian verifies with the channel certificate.
66
- envelope: "oauth2_client_credentials",
67
- signing: "detached_signature",
68
- },
69
- idempotencySpine: {
70
- // Meridian grades a replay on the instruction id we mint, and refuses a
71
- // second payment carrying a payment reference it has already settled.
72
- dedupeKeys: ["instructionId", "paymentReference"],
73
- instructionId: { maxLength: 32 },
74
- endToEndId: { maxLength: 32 },
75
- paymentReference: { maxLength: 20 },
76
- },
77
- /**
78
- * Meridian's own status tokens on the left, the canonical states on the
79
- * right. Runtimes match after trimming and upper-casing, so a token that
80
- * only differs by case or padding needs no second entry.
81
- *
82
- * Both replay verdicts are spelled: without them a crash-recovery
83
- * resubmission reads as a fresh failure and the payout is instructed twice.
84
- */
85
- lifecycleVocabulary: {
86
- QUEUED: "received",
87
- IN_REVIEW: "received",
88
- AUTHORISED: "accepted",
89
- SETTLED: "processed",
90
- DECLINED: "failed",
91
- RECALLED: "returned",
92
- REPLAY_SETTLED: "duplicate_original_succeeded",
93
- REPLAY_DECLINED: "duplicate_original_rejected",
94
- },
95
- executedReadBack: {
96
- fxRate: "final_status_only",
97
- // Meridian never moves a payment to another rail behind our back.
98
- railSubstitution: "none",
99
- },
100
- charges: {
101
- // The bank raises its fee and its tax as two separate debits, each
102
- // recognizable only by the wording it prints. Prefixes, because the
103
- // trailing part names the payment being charged for.
104
- narratives: {
105
- charge: ["MERIDIAN FEE"],
106
- vat: ["MERIDIAN TAX"],
107
- },
108
- posting: "post_hoc",
109
- vat: "separate_line",
110
- },
111
- windows: {
112
- paymentCutoff: {
113
- time: "16:30",
114
- // The bank's wall clock, not the platform's.
115
- timeZone: "Europe/Dublin",
116
- beforeCutoffValueDate: "D+0",
117
- afterCutoffValueDate: "D+1",
118
- },
119
- valueDateMaxDaysAhead: 30,
120
- statements: {
121
- // End of day pages over history, so it declares how far back the
122
- // archive goes and how many days one request may cover.
123
- camt_053: {
124
- availability: "T-1",
125
- lookbackDays: 90,
126
- statementDaysPerRequest: 7,
127
- },
128
- // Intraday is today or nothing, so it owes no paging facts.
129
- camt_052: { availability: "today_only" },
130
- },
131
- },
132
- // Meridian echoes the reference we sent, not its own entry id. Reading the
133
- // entry id instead would leave every payout we instructed looking like an
134
- // unexplained debit.
135
- statementDebitReference: "customer_reference",
136
- statementFormats: ["camt_053", "camt_052"],
137
- // The callback is a hint that a poll is due. Polling is the fact.
138
- notificationMechanisms: ["poll", "webhook"],
139
- financialAddressProvisioning: {
140
- mechanism: "api",
141
- quota: "none",
142
- pricing: "none",
143
- },
144
- limitFacts: {
145
- access: "read_only",
146
- dimensions: ["daily", "per_transaction"],
147
- },
148
- } as const satisfies PartnerBankAdapterProfile;
149
-
150
- /**
151
- * A locator, never a value. The registry validator refuses a blank locator
152
- * and has no field a secret could be written into.
153
- */
154
- const meridianConfig = {
155
- baseUrl: "https://api.meridian-bank.example/v2",
156
- credentialRef: {
157
- source: "environment",
158
- name: "MERIDIAN_BANK_CREDENTIALS",
159
- },
160
- } as const satisfies ProviderConfig;
161
-
162
- /**
163
- * One adapter per capability, both carrying the same bank profile. The
164
- * registry validates shape at import; `example.spec.ts` runs conformance.
165
- */
166
- export const meridianAdapters = defineMeridianAdapters([
167
- {
168
- provider: "meridian_bank",
169
- capability: "payout_execution",
170
- domain: "payouts",
171
- egress: "rest",
172
- operationMap: {
173
- "payout.submit": {
174
- // Read-after-write: a submit whose response we never saw is re-read
175
- // here by the reference Meridian minted, never resubmitted blind.
176
- statusEnquiry: {
177
- keys: "provider_reference",
178
- pathTemplate: "/v2/payments/{providerReference}",
179
- },
180
- // Meridian answers on the HTTP status and puts prose in the body of a
181
- // failure, so a 4xx is the rejection and the body is for humans.
182
- envelope: "http_status_error_body",
183
- operation: "payout.submit",
184
- obligationKind: "payout_submission",
185
- resourceKind: "payout",
186
- resourceIdPath: "payoutId",
187
- direction: "tenant_initiated",
188
- meter: "external_payout",
189
- },
190
- // The three endings. Nothing is sent for them: they record what the
191
- // poll lane observed, so they carry no envelope of their own.
192
- "payout.complete": {
193
- operation: "payout.complete",
194
- obligationKind: "payout_execution",
195
- resourceKind: "payout",
196
- resourceIdPath: "payoutId",
197
- direction: "system_settlement",
198
- },
199
- "payout.fail": {
200
- operation: "payout.fail",
201
- obligationKind: "payout_execution",
202
- resourceKind: "payout",
203
- resourceIdPath: "payoutId",
204
- direction: "system_settlement",
205
- },
206
- "payout.return": {
207
- operation: "payout.return",
208
- obligationKind: "payout_return",
209
- resourceKind: "payout",
210
- resourceIdPath: "payoutId",
211
- direction: "system_settlement",
212
- },
213
- },
214
- webhookMap: {
215
- "payout.settled": {
216
- eventKind: "payout.settled",
217
- operationName: "payout.complete",
218
- requiredPayloadFields: ["providerReference", "valueDate"],
219
- optionalPayloadFields: ["chargeAmount"],
220
- requiredFieldGroups: [],
221
- resourceIdField: "payoutId",
222
- resourceKind: "payout",
223
- timestampField: "completedAt",
224
- },
225
- },
226
- bindings: { payout: "claim" },
227
- config: meridianConfig,
228
- profile: meridianProfile,
229
- },
230
- {
231
- provider: "meridian_bank",
232
- capability: "bank_credit",
233
- domain: "deposits",
234
- // Incoming credits are learned by reading statements, so this capability
235
- // sends Meridian nothing.
236
- egress: "none",
237
- operationMap: {
238
- "deposit.record": {
239
- operation: "deposit.record",
240
- obligationKind: "bank_credit",
241
- resourceKind: "deposit",
242
- resourceIdPath: "depositId",
243
- direction: "system_settlement",
244
- },
245
- },
246
- webhookMap: {
247
- "deposit.received": {
248
- eventKind: "deposit.received",
249
- operationName: "deposit.record",
250
- requiredPayloadFields: ["financialAddressId", "amount", "currency"],
251
- optionalPayloadFields: ["remitterName"],
252
- requiredFieldGroups: [],
253
- resourceIdField: "depositId",
254
- resourceKind: "deposit",
255
- timestampField: "receivedAt",
256
- },
257
- },
258
- bindings: { deposit: "strict" },
259
- config: meridianConfig,
260
- profile: meridianProfile,
261
- },
262
- ]);