@hyperscale0/adl 1.0.0-beta.5 → 1.0.0-beta.7
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/CHANGELOG.md +10 -1
- package/README.md +8 -79
- package/conformance/cases.json +50 -764
- package/conformance/subject-adapter.ts +31 -0
- package/dist/boundary-fixture.d.ts +24 -0
- package/dist/boundary-fixture.d.ts.map +1 -0
- package/dist/boundary-fixture.js +34 -0
- package/dist/boundary-fixture.js.map +1 -0
- package/dist/index.d.ts +68 -156
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +100 -77
- package/dist/index.js.map +1 -1
- package/dist/vocabulary.d.ts +3 -57
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +12 -84
- package/dist/vocabulary.js.map +1 -1
- package/docs/authoring-guide.md +37 -417
- package/package.json +11 -6
- package/spec/manifest.schema.json +511 -446
- package/dist/conformance.d.ts +0 -41
- package/dist/conformance.d.ts.map +0 -1
- package/dist/conformance.js +0 -486
- package/dist/conformance.js.map +0 -1
- package/examples/meridian-bank/README.md +0 -65
- package/examples/meridian-bank/fixtures/payout-status-replay.json +0 -19
- package/examples/meridian-bank/fixtures/payout-status-settled.json +0 -19
- package/examples/meridian-bank/fixtures/payout-submit-accepted.json +0 -26
- package/examples/meridian-bank/fixtures/payout-submit-declined.json +0 -25
- package/examples/meridian-bank/fixtures/statement-camt053.json +0 -45
- package/examples/meridian-bank/fixtures/webhook-payout-settled.json +0 -11
- package/examples/meridian-bank/index.ts +0 -262
|
@@ -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,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
|
-
]);
|