@hyperscale0/adl 1.0.0-beta.6 → 1.0.0-beta.8
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 +596 -439
- 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
package/docs/authoring-guide.md
CHANGED
|
@@ -1,432 +1,52 @@
|
|
|
1
1
|
# Writing a provider adapter in ADL
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
`ProviderAdapter` declares an adapter's identity, operation bindings and subject
|
|
4
|
+
requirements. `BoundaryAdapter` implements dispatch and observation. The runtime
|
|
5
|
+
owns instruction identity and money accounts.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Declare the binding
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
ships with the platform. Your adapter never opens a socket, never holds a
|
|
14
|
-
credential, and never decides what a payment means.
|
|
9
|
+
Use `createProviderAdapterRegistry` to validate declarations. Each operation map
|
|
10
|
+
key must equal its binding's `operation`. Its `resourceKind` must appear in
|
|
11
|
+
`bindings`. Subject requirements describe required authored fields or attachments;
|
|
12
|
+
they never supply an account or choose a party.
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
guessing. Every guess a runtime makes about money is a provider-boundary
|
|
20
|
-
defect.
|
|
14
|
+
The [generic fixture](../src/boundary-fixture.ts) declares one boundary
|
|
15
|
+
observation operation with no subject requirements. The
|
|
16
|
+
[subject fixture](../conformance/subject-adapter.ts) shows required subject fields.
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
`adapterConformanceFindings` checks the declaration. The
|
|
19
|
+
[manifest schema](../spec/manifest.schema.json) expresses the same public shape
|
|
20
|
+
for non-TypeScript authors. Neither check contacts a provider.
|
|
23
21
|
|
|
24
|
-
|
|
25
|
-
fields that may be omitted (`charges`, `statementDebitReference`, `envelope`)
|
|
26
|
-
exist so you can be honest during authoring. Conformance then refuses to
|
|
27
|
-
certify while they are open. Absent is a finding; a wrong value is an
|
|
28
|
-
outage.
|
|
29
|
-
- **Poll-first.** A provider callback is a hint that a poll is due, never a
|
|
30
|
-
fact to post from. `validatePartnerBankProfile` refuses a profile whose
|
|
31
|
-
`notificationMechanisms` omits `poll`, whatever else it lists.
|
|
22
|
+
## Dispatch an instruction
|
|
32
23
|
|
|
33
|
-
|
|
24
|
+
`BoundaryInstruction` binds an immutable ID to the environment, tenant, Product,
|
|
25
|
+
retained Build, target, source and destination accounts, positive minor-unit
|
|
26
|
+
amount, currency and adapter. The runtime reserves the amount before dispatch.
|
|
27
|
+
An adapter receives that instruction unchanged.
|
|
34
28
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
`BoundaryAdapter.dispatch` and `BoundaryAdapter.observe` return a
|
|
30
|
+
`BoundaryObservation` or `undefined`. The observation names the adapter,
|
|
31
|
+
instruction ID, stable external reference, explicit outcome and observation time.
|
|
32
|
+
Its outcome is `acknowledged`, `unknown`, `confirmed` or `rejected`.
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
An acknowledgement or missing result leaves the reservation held. HTTP status
|
|
35
|
+
never supplies a terminal outcome. Only a matching `confirmed` observation can
|
|
36
|
+
authorize posting; an explicit `rejected` observation can authorize voiding.
|
|
37
|
+
The runtime records immutable observations and consumes settlement evidence once.
|
|
38
|
+
Recovery completes the same ledger intent without resending uncertain work.
|
|
42
39
|
|
|
43
|
-
##
|
|
40
|
+
## Check the declaration
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
you will fix the wrong thing.
|
|
42
|
+
From this package:
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
| Registry load | `createProviderAdapterRegistry` | Cross-field structure at import time: key drift, blank locators, an unreadable clock, a duplicate identity. Throws. |
|
|
52
|
-
| Conformance | `partnerBankConformanceFindings`, `certifyPartnerBankAdapter` | Operational completeness. Returns findings, or throws with all of them listed. |
|
|
53
|
-
| Manifest schema | `spec/manifest.schema.json` | The same structure for authors who are not writing TypeScript. |
|
|
54
|
-
|
|
55
|
-
`conformance/cases.json` records, for every rule, which gates see it. Read it
|
|
56
|
-
when you want the exact division of labor.
|
|
57
|
-
|
|
58
|
-
## 1. Declare your vocabulary
|
|
59
|
-
|
|
60
|
-
Start by naming the closed unions your product uses. `ProviderAdapterVocabulary`
|
|
61
|
-
is nine string fields, and you narrow every one of them:
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
import type { ProviderAdapterVocabulary } from "@hyperscale0/adl";
|
|
65
|
-
|
|
66
|
-
interface MeridianVocabulary extends ProviderAdapterVocabulary {
|
|
67
|
-
readonly capability: "payout_execution" | "bank_credit";
|
|
68
|
-
readonly domain: "payouts" | "deposits";
|
|
69
|
-
readonly meter: "external_payout";
|
|
70
|
-
readonly obligation: "payout_submission" | "bank_credit";
|
|
71
|
-
readonly operation: "payout.submit" | "deposit.record";
|
|
72
|
-
readonly resource: "payout" | "deposit";
|
|
73
|
-
readonly webhookOperation: "payout.complete";
|
|
74
|
-
readonly webhookResource: "payout";
|
|
75
|
-
readonly webhookResourceId: "payoutId";
|
|
76
|
-
}
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
The names come from the published operation catalog for the product you are
|
|
80
|
-
integrating with, not from your provider's documentation. Your provider calls
|
|
81
|
-
it `POST /v2/payments`; the platform calls it `payout.submit`. Translating
|
|
82
|
-
between the two is the adapter's whole reason to exist.
|
|
83
|
-
|
|
84
|
-
Then bind the vocabulary once and keep the returned function:
|
|
85
|
-
|
|
86
|
-
```ts
|
|
87
|
-
const defineMeridianAdapters =
|
|
88
|
-
createProviderAdapterRegistry<MeridianVocabulary>();
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
`createProviderAdapterRegistry` returns a validator that preserves your exact
|
|
92
|
-
literal types (`const Adapters extends readonly ProviderAdapter<Vocabulary>[]`),
|
|
93
|
-
so downstream code sees the operations you actually declared, not `string`.
|
|
94
|
-
|
|
95
|
-
## 2. Declare the operations
|
|
96
|
-
|
|
97
|
-
`operationMap` is keyed by operation name, and the key must equal the binding's
|
|
98
|
-
`operation` field. Drift throws at load:
|
|
99
|
-
`operationMap key submit must match operation payout.submit`.
|
|
100
|
-
|
|
101
|
-
Each `ProviderOperationBinding` carries:
|
|
102
|
-
|
|
103
|
-
| Field | Contract |
|
|
104
|
-
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
105
|
-
| `operation` | The platform operation this binding serves. Equal to its map key. |
|
|
106
|
-
| `direction` | `tenant_initiated` when the runtime sends the provider a command and reads an answer. `system_settlement` when the operation records something observation already saw. This single field decides which rules apply below. |
|
|
107
|
-
| `obligationKind` | Which obligation the operation discharges. Your product's vocabulary. |
|
|
108
|
-
| `resourceKind` | The platform resource the provider's answer commits onto. Must appear in `bindings`. |
|
|
109
|
-
| `resourceIdPath` | Where the resource id is read from. Non-blank, checked at load. |
|
|
110
|
-
| `meter` | Optional. The usage meter this operation feeds. |
|
|
111
|
-
| `envelope` | How this operation's response carries its outcome. Required when the runtime sends a provider command, see below. |
|
|
112
|
-
| `statusEnquiry` | The authoritative read-after-write. Required on a REST command, see below. |
|
|
113
|
-
|
|
114
|
-
### The envelope class, per operation
|
|
115
|
-
|
|
116
|
-
`ProviderResponseEnvelope` has three inhabitants:
|
|
117
|
-
|
|
118
|
-
- `http_200_body_status`: every response is HTTP 200 and the outcome is a
|
|
119
|
-
status token in the body. A rejection arrives inside a success.
|
|
120
|
-
- `http_status_error_body`: the HTTP status carries the outcome and a failure
|
|
121
|
-
body is prose.
|
|
122
|
-
- `unconfirmed_until_live`: the provider publishes no error sample, so the
|
|
123
|
-
class is honestly unknown.
|
|
124
|
-
|
|
125
|
-
Declare it **per operation**, never per provider. Real providers answer in more
|
|
126
|
-
than one shape across their own endpoints, and inheriting a class from a
|
|
127
|
-
sibling endpoint is how a rejection gets read as a success.
|
|
128
|
-
|
|
129
|
-
Conformance requires the class on any binding where the runtime sends a command
|
|
130
|
-
and reads an answer, meaning `direction: "tenant_initiated"` on an `egress` of
|
|
131
|
-
`rest` or `sync_clearance`. Missing it is `response_envelope_unclassified`; a
|
|
132
|
-
declared `unconfirmed_until_live` is `response_envelope_unconfirmed`, because
|
|
133
|
-
conformance requires the response class to be established first. Passing that
|
|
134
|
-
check does not grant live readiness. A `system_settlement` binding carries no
|
|
135
|
-
response of its own, so it owes no envelope, and declaring one there would be a
|
|
136
|
-
borrowed fact.
|
|
137
|
-
|
|
138
|
-
### Status enquiry, the read-after-write
|
|
139
|
-
|
|
140
|
-
A REST command's response can be lost after the provider has already acted on
|
|
141
|
-
it. Without an authoritative way to re-read the outcome, the runtime's only
|
|
142
|
-
options are guessing and resubmitting, and resubmitting pays twice.
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
statusEnquiry: {
|
|
146
|
-
keys: "provider_reference",
|
|
147
|
-
pathTemplate: "/v2/payments/{providerReference}",
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
`keys` says which reference the enquiry is keyed by, and the template must
|
|
152
|
-
contain the matching placeholder, checked at load:
|
|
153
|
-
`{instructionId}` for `instruction_id`, `{providerReference}` for
|
|
154
|
-
`provider_reference`.
|
|
155
|
-
|
|
156
|
-
Presence is a readiness commitment, not a hint. Runtimes must never infer
|
|
157
|
-
enquiry support from a command URL, so:
|
|
158
|
-
|
|
159
|
-
- a `tenant_initiated` binding on `egress: "rest"` without one is
|
|
160
|
-
`status_enquiry_missing`;
|
|
161
|
-
- an enquiry declared on an adapter whose `egress` is not `rest` is
|
|
162
|
-
`status_enquiry_without_rest_egress`, because there is no transport to run it;
|
|
163
|
-
- `sync_clearance` is exempt: the answer arrives in the same exchange.
|
|
164
|
-
|
|
165
|
-
### Egress and direction have to agree
|
|
166
|
-
|
|
167
|
-
`ProviderEgressMode` is how this adapter reaches the provider at all:
|
|
168
|
-
|
|
169
|
-
- `none`: the capability is learned entirely by observation;
|
|
170
|
-
- `rest`: per-command HTTP;
|
|
171
|
-
- `batch_reconcile`: files, reconciled after the fact;
|
|
172
|
-
- `sync_clearance`: one synchronous exchange that carries its own truth.
|
|
173
|
-
|
|
174
|
-
A `tenant_initiated` binding on `none` or `batch_reconcile` promises an
|
|
175
|
-
outbound call the egress mode cannot make: `egress_direction_incoherent`.
|
|
176
|
-
|
|
177
|
-
## 3. Declare the webhook plans
|
|
178
|
-
|
|
179
|
-
`webhookMap` is keyed by event kind, and the key must equal `eventKind` (load
|
|
180
|
-
throws on drift, same as operations). A `ProviderWebhookTransitionPlan` says
|
|
181
|
-
what a callback must contain before the runtime will treat it as a reason to
|
|
182
|
-
poll: `requiredPayloadFields`, `optionalPayloadFields`,
|
|
183
|
-
`requiredFieldGroups` (each inner array is a set where at least one member is
|
|
184
|
-
required), `resourceIdField`, `resourceKind`, and `timestampField` from the
|
|
185
|
-
`ProviderTimestampField` vocabulary.
|
|
186
|
-
|
|
187
|
-
An adapter with no callbacks declares `webhookMap: {}`. That is complete, not
|
|
188
|
-
missing: polling is the fact.
|
|
189
|
-
|
|
190
|
-
## 4. Declare the bindings
|
|
191
|
-
|
|
192
|
-
`bindings` maps each resource kind this adapter settles to a
|
|
193
|
-
`ProviderResourceBinding`:
|
|
194
|
-
|
|
195
|
-
- `strict`: the provider's fact must match the platform's row exactly;
|
|
196
|
-
- `claim`: the provider's fact claims an existing row;
|
|
197
|
-
- `confirmation_only`: the provider only confirms what the platform already
|
|
198
|
-
decided;
|
|
199
|
-
- `evidence_only`: the fact is recorded as evidence, and settles nothing.
|
|
200
|
-
|
|
201
|
-
Every `resourceKind` named by an operation binding or a webhook plan needs an
|
|
202
|
-
entry, or conformance reports `resource_binding_missing`: a provider fact with
|
|
203
|
-
no declared match mode has no lawful way to commit.
|
|
204
|
-
|
|
205
|
-
## 5. Declare the config
|
|
206
|
-
|
|
207
|
-
```ts
|
|
208
|
-
config: {
|
|
209
|
-
baseUrl: "https://api.meridian-bank.example/v2",
|
|
210
|
-
credentialRef: { source: "environment", name: "MERIDIAN_BANK_CREDENTIALS" },
|
|
211
|
-
}
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
`ProviderCredentialReference` is a **locator**: an environment variable name,
|
|
215
|
-
or a keychain service and account. There is no field a secret can be written
|
|
216
|
-
into, and `validateConfig` refuses a blank locator or a relative `baseUrl`.
|
|
217
|
-
Never put a token, certificate, or password in an adapter. The declaration is
|
|
218
|
-
committed to a repository; the credential is not.
|
|
219
|
-
|
|
220
|
-
## 6. Declare the partner-bank profile
|
|
221
|
-
|
|
222
|
-
`PartnerBankAdapterProfile` is the bank as fact. It is the longest part of the
|
|
223
|
-
job and the part that repays care, because every field is something a runtime
|
|
224
|
-
would otherwise guess.
|
|
225
|
-
|
|
226
|
-
### Auth and wire
|
|
227
|
-
|
|
228
|
-
`wireCodec` (`rest_json`, `iso20022_xml`, `swift_mt`) and `auth`
|
|
229
|
-
(`ProviderAuthEnvelope` plus a signing scheme). Declaring a grant the bank will
|
|
230
|
-
not accept fails on the first provider call. Read the bank's token service and
|
|
231
|
-
do not assume client credentials.
|
|
232
|
-
|
|
233
|
-
### The idempotency spine
|
|
234
|
-
|
|
235
|
-
```ts
|
|
236
|
-
idempotencySpine: {
|
|
237
|
-
dedupeKeys: ["instructionId", "paymentReference"],
|
|
238
|
-
instructionId: { maxLength: 32 },
|
|
239
|
-
endToEndId: { maxLength: 32 },
|
|
240
|
-
paymentReference: { maxLength: 20 },
|
|
241
|
-
}
|
|
44
|
+
```sh
|
|
45
|
+
bun run conformance -- ./src/boundary-fixture.ts
|
|
46
|
+
bun run spec:emit
|
|
242
47
|
```
|
|
243
48
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
### The lifecycle vocabulary
|
|
250
|
-
|
|
251
|
-
The bank's own status spellings, mapped onto canonical states:
|
|
252
|
-
|
|
253
|
-
```ts
|
|
254
|
-
lifecycleVocabulary: {
|
|
255
|
-
QUEUED: "received",
|
|
256
|
-
AUTHORISED: "accepted",
|
|
257
|
-
SETTLED: "processed",
|
|
258
|
-
DECLINED: "failed",
|
|
259
|
-
RECALLED: "returned",
|
|
260
|
-
REPLAY_SETTLED: "duplicate_original_succeeded",
|
|
261
|
-
REPLAY_DECLINED: "duplicate_original_rejected",
|
|
262
|
-
}
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
Runtimes match after trimming and upper-casing, so `ok`, `OK ` and `OK` are one
|
|
266
|
-
entry. Four rules apply:
|
|
267
|
-
|
|
268
|
-
- the vocabulary must spell `processed`, `failed`, and `returned`, or a payment
|
|
269
|
-
ending there is unclassifiable and polls forever
|
|
270
|
-
(`lifecycle_terminals_incomplete`);
|
|
271
|
-
- it must spell `received` or `accepted`, because an acknowledgement proves the
|
|
272
|
-
bank took the instruction, never that money moved
|
|
273
|
-
(`lifecycle_intake_missing`);
|
|
274
|
-
- a spelling that trims to nothing can never match (`lifecycle_spelling_blank`);
|
|
275
|
-
- two spellings that normalize to the same token must agree on the canonical
|
|
276
|
-
state, or classification becomes order-dependent
|
|
277
|
-
(`lifecycle_spelling_conflict`).
|
|
278
|
-
|
|
279
|
-
And the one authors forget: both duplicate verdicts must be spellable
|
|
280
|
-
(`duplicate_verdicts_unmapped`). The dedupe spine means a crash-recovery
|
|
281
|
-
resubmission comes back graded against the original's outcome. Unclassified,
|
|
282
|
-
your own recovery reads as a fresh failure.
|
|
283
|
-
|
|
284
|
-
### Statements
|
|
285
|
-
|
|
286
|
-
`statementFormats` lists what the bank can hand you (`mt940`, `mt942`,
|
|
287
|
-
`camt_052`, `camt_053`, `obie_json`), and `windows.statements` gives each one a
|
|
288
|
-
fetch window:
|
|
289
|
-
|
|
290
|
-
- `availability: "T-1"` pages over history, so it needs positive `lookbackDays`
|
|
291
|
-
and `statementDaysPerRequest`;
|
|
292
|
-
- `availability: "today_only"` is intraday and owes no paging facts.
|
|
293
|
-
|
|
294
|
-
A declared format with no window is `statement_window_missing`; a T-1 window
|
|
295
|
-
without paging facts is `statement_window_incomplete`. No formats at all is
|
|
296
|
-
`statement_formats_empty`: statements are bank truth, and reconciliation has
|
|
297
|
-
nothing to prove the ledger against without one.
|
|
298
|
-
|
|
299
|
-
`statementDebitReference` says which of the two references on a debit line
|
|
300
|
-
names the instruction behind it: the bank's own entry reference
|
|
301
|
-
(`bank_reference`) or the reference you sent (`customer_reference`). Banks
|
|
302
|
-
disagree, and the wrong choice makes every payout you instructed look like an
|
|
303
|
-
unexplained debit. Leaving it unestablished is
|
|
304
|
-
`statement_debit_reference_unestablished`.
|
|
305
|
-
|
|
306
|
-
### Charges
|
|
307
|
-
|
|
308
|
-
```ts
|
|
309
|
-
charges: {
|
|
310
|
-
narratives: { charge: ["MERIDIAN FEE"], vat: ["MERIDIAN TAX"] },
|
|
311
|
-
posting: "post_hoc",
|
|
312
|
-
vat: "separate_line",
|
|
313
|
-
}
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
`posting` says whether the bank debits its fee separately after the payment or
|
|
317
|
-
nets it out of the instructed amount; `vat` says whether tax arrives as its own
|
|
318
|
-
line. Post-hoc charges and separate VAT lines are bank-initiated debits
|
|
319
|
-
recognizable only by their wording, so each mode in play needs at least one
|
|
320
|
-
narrative prefix (`charge_narrative_unestablished`,
|
|
321
|
-
`vat_narrative_unestablished`). Omitting `charges` entirely is honest during
|
|
322
|
-
authoring and refused at certification (`charges_unestablished`): the bank's own
|
|
323
|
-
debits would land as unexplained statement lines.
|
|
324
|
-
|
|
325
|
-
### Windows
|
|
326
|
-
|
|
327
|
-
```ts
|
|
328
|
-
paymentCutoff: {
|
|
329
|
-
time: "16:30",
|
|
330
|
-
timeZone: "Europe/Dublin",
|
|
331
|
-
beforeCutoffValueDate: "D+0",
|
|
332
|
-
afterCutoffValueDate: "D+1",
|
|
333
|
-
}
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
The cutoff is read on the **bank's** clock, so `timeZone` is an IANA name and
|
|
337
|
-
both gates resolve it through `Intl.DateTimeFormat`. Absence is not tolerated:
|
|
338
|
-
an undefined zone silently means the host's clock
|
|
339
|
-
(`cutoff_clock_invalid`).
|
|
340
|
-
|
|
341
|
-
Value dates are `D+<whole days>`. Missing a cutoff can only push value later,
|
|
342
|
-
so an after-cutoff date earlier than the before-cutoff one is a
|
|
343
|
-
`cutoff_window_regression`, and so is a `valueDateMaxDaysAhead` that cannot
|
|
344
|
-
reach the after-cutoff date. A malformed offset is
|
|
345
|
-
`value_date_offset_invalid` rather than a NaN that slides through every
|
|
346
|
-
comparison after it.
|
|
347
|
-
|
|
348
|
-
### The rest
|
|
349
|
-
|
|
350
|
-
`executedReadBack` (whether the executed read-back may name another rail, and
|
|
351
|
-
when an FX rate is final), `limitFacts` (what the bank exposes about limits),
|
|
352
|
-
`financialAddressProvisioning` (how virtual addresses are issued, priced, and
|
|
353
|
-
quota'd), and `notificationMechanisms` (which must include `poll`).
|
|
354
|
-
|
|
355
|
-
## 7. Run conformance
|
|
356
|
-
|
|
357
|
-
```bash
|
|
358
|
-
bun run conformance -- ./examples/meridian-bank
|
|
359
|
-
```
|
|
360
|
-
|
|
361
|
-
```
|
|
362
|
-
ok meridian_bank:payout_execution
|
|
363
|
-
ok meridian_bank:bank_credit
|
|
364
|
-
2 adapter(s) checked, 0 finding(s) reported
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
The runner exits 1 when anything reports, so it drops straight into CI. It
|
|
368
|
-
takes a module path or a directory, and certifies every adapter the module
|
|
369
|
-
exports.
|
|
370
|
-
|
|
371
|
-
In your own repository, call the function directly and skip the runner:
|
|
372
|
-
|
|
373
|
-
```ts
|
|
374
|
-
import { certifyPartnerBankAdapter } from "@hyperscale0/adl";
|
|
375
|
-
|
|
376
|
-
import { myAdapters } from "./src/index.js";
|
|
377
|
-
|
|
378
|
-
for (const adapter of myAdapters) certifyPartnerBankAdapter(adapter);
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
`partnerBankConformanceFindings` returns every finding as
|
|
382
|
-
`{ code, path, message }`, where `path` is the dotted location inside your
|
|
383
|
-
declaration. `certifyPartnerBankAdapter` throws with all of them listed at
|
|
384
|
-
once, so you fix a declaration in one pass rather than one throw at a time.
|
|
385
|
-
The codes are append-only: a released code is never renamed or removed, so you
|
|
386
|
-
can switch on `PartnerBankConformanceCode` without a stale fallback arm.
|
|
387
|
-
|
|
388
|
-
An empty result is the certification. Nothing else is.
|
|
389
|
-
|
|
390
|
-
## 8. Write the manifest without TypeScript
|
|
391
|
-
|
|
392
|
-
`spec/manifest.schema.json` is JSON Schema 2020-12 for one adapter
|
|
393
|
-
declaration, generated from `src/vocabulary.ts` by `scripts/emit-spec.ts` and
|
|
394
|
-
drift-checked by `bun run spec:check`. Generate types, validate a
|
|
395
|
-
hand-written manifest, or feed it to an editor.
|
|
396
|
-
|
|
397
|
-
The schema is structural. Rules that compare two fields, resolve a time zone,
|
|
398
|
-
or match a property key against a value inside it cannot be expressed in JSON
|
|
399
|
-
Schema and stay in the validator and the conformance suite.
|
|
400
|
-
`conformance/cases.json` says which gate owns each rule, in the `note` field of
|
|
401
|
-
every case where they differ.
|
|
402
|
-
|
|
403
|
-
## 9. Hand it over
|
|
404
|
-
|
|
405
|
-
An adapter is handed over as source, never as a running service:
|
|
406
|
-
|
|
407
|
-
1. `bun run conformance` reports zero findings.
|
|
408
|
-
2. Your own tests read captured provider bytes and prove the declaration
|
|
409
|
-
matches them. `examples/meridian-bank/example.spec.ts` is the pattern:
|
|
410
|
-
every status token in a fixture is spelled in the lifecycle vocabulary, the
|
|
411
|
-
envelope class is how the fixtures actually read, and the declared charge
|
|
412
|
-
narratives match the statement lines.
|
|
413
|
-
3. Open an issue on this repository with the certified declaration and the
|
|
414
|
-
conformance output. Credentials are never part of a submission; the
|
|
415
|
-
declaration names locators, and the operator fills them in.
|
|
416
|
-
|
|
417
|
-
## Stability
|
|
418
|
-
|
|
419
|
-
The package is pre-1.0 and the surface can still change on a minor version
|
|
420
|
-
until it is `1.0.0`. Two promises hold now:
|
|
421
|
-
|
|
422
|
-
- conformance codes are append-only;
|
|
423
|
-
- a value removed from a vocabulary in `src/vocabulary.ts` is a breaking change
|
|
424
|
-
and appears in `CHANGELOG.md`.
|
|
425
|
-
|
|
426
|
-
## What is not here
|
|
427
|
-
|
|
428
|
-
The declaration carries no timeouts, no retry policy, and no rate limits.
|
|
429
|
-
Those belong to the runtime that executes adapters, not to the facts about the
|
|
430
|
-
provider, and no field in `ProviderAdapter` accepts them. If your provider's
|
|
431
|
-
limits are a fact the runtime must know, open an issue: under Adapter Symmetry
|
|
432
|
-
the seam gets added here, publicly, or not at all.
|
|
49
|
+
The conformance command exits 1 when it reports a finding. The schema emitter
|
|
50
|
+
writes the declaration schema and the `BoundaryInstruction` and
|
|
51
|
+
`BoundaryObservation` definitions. Add a case in `conformance/cases.json` when a
|
|
52
|
+
new declaration rule prevents a concrete failure.
|
package/package.json
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/adl",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
4
|
-
"description": "The Adapter Declaration Language:
|
|
3
|
+
"version": "1.0.0-beta.8",
|
|
4
|
+
"description": "The Adapter Declaration Language: generic evidence, provider declarations and conformance.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adl",
|
|
7
7
|
"adapter",
|
|
8
|
-
"banking",
|
|
9
8
|
"conformance",
|
|
10
9
|
"fintech",
|
|
11
|
-
"iso20022",
|
|
12
10
|
"json-schema",
|
|
13
11
|
"payments",
|
|
14
12
|
"provider"
|
|
@@ -32,6 +30,10 @@
|
|
|
32
30
|
"types": "./dist/index.d.ts",
|
|
33
31
|
"default": "./dist/index.js"
|
|
34
32
|
},
|
|
33
|
+
"./boundary-fixture": {
|
|
34
|
+
"types": "./dist/boundary-fixture.d.ts",
|
|
35
|
+
"default": "./dist/boundary-fixture.js"
|
|
36
|
+
},
|
|
35
37
|
"./spec/manifest.schema.json": "./spec/manifest.schema.json",
|
|
36
38
|
"./package.json": "./package.json"
|
|
37
39
|
},
|
|
@@ -45,7 +47,6 @@
|
|
|
45
47
|
"conformance",
|
|
46
48
|
"dist",
|
|
47
49
|
"docs",
|
|
48
|
-
"examples",
|
|
49
50
|
"spec"
|
|
50
51
|
],
|
|
51
52
|
"engines": {
|
|
@@ -65,6 +66,10 @@
|
|
|
65
66
|
"test": "bun test",
|
|
66
67
|
"typecheck": "tsc -p tsconfig.json"
|
|
67
68
|
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"@hyperscale0/udl": "4.0.0",
|
|
71
|
+
"zod": "4.6.5"
|
|
72
|
+
},
|
|
68
73
|
"devDependencies": {
|
|
69
74
|
"bun-types": "1.4.2",
|
|
70
75
|
"typescript": "7.0.2"
|
|
@@ -75,5 +80,5 @@
|
|
|
75
80
|
"Amir Ayub",
|
|
76
81
|
"Sara AlBakaawi"
|
|
77
82
|
],
|
|
78
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "4f255b8c690520bcc406088064ae7898e16042ae"
|
|
79
84
|
}
|