@hyperscale0/adl 1.0.0-alpha.2 → 1.0.0-beta.2
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 +12 -1
- package/README.md +28 -82
- package/dist/conformance.d.ts.map +1 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/docs/assets/adl.svg +18 -0
- package/docs/authoring-guide.md +24 -23
- package/package.json +5 -4
- package/examples/meridian-bank/example.spec.ts +0 -162
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,16 @@ follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html) from 1.0.0.
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.0.0-beta.2] - 2026-09-10
|
|
10
|
+
|
|
11
|
+
- Republished against UDL 2.0.0. No package behavior changes from
|
|
12
|
+
1.0.0-beta.1.
|
|
13
|
+
|
|
14
|
+
## [1.0.0-beta.1] - 2026-08-29
|
|
15
|
+
|
|
16
|
+
- This is the first beta and has no package behavior changes from
|
|
17
|
+
1.0.0-alpha.2.
|
|
18
|
+
|
|
9
19
|
## [1.0.0-alpha.2] - 2026-08-26
|
|
10
20
|
|
|
11
21
|
### Removed
|
|
@@ -54,6 +64,7 @@ write an adapter in; an adapter is still what you write.
|
|
|
54
64
|
the exported types derived from them, so the published schema cannot drift
|
|
55
65
|
from the type checker. No exported type changed shape.
|
|
56
66
|
|
|
57
|
-
[Unreleased]: https://github.com/hyperscale0/hyperscale-adl/compare/v1.0.0-
|
|
67
|
+
[Unreleased]: https://github.com/hyperscale0/hyperscale-adl/compare/v1.0.0-beta.1...HEAD
|
|
68
|
+
[1.0.0-beta.1]: https://github.com/hyperscale0/hyperscale-adl/compare/v1.0.0-alpha.2...v1.0.0-beta.1
|
|
58
69
|
[1.0.0-alpha.2]: https://github.com/hyperscale0/hyperscale-adl/compare/v1.0.0-alpha.1...v1.0.0-alpha.2
|
|
59
70
|
[1.0.0-alpha.1]: https://github.com/hyperscale0/hyperscale-adl/releases/tag/v1.0.0-alpha.1
|
package/README.md
CHANGED
|
@@ -1,37 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
2
|
|
|
3
|
-
ADL
|
|
4
|
-
provider to Hyperscale, the validator that refuses a declaration the runtime
|
|
5
|
-
could not operate, and the conformance suite that says when one is ready to
|
|
6
|
-
serve live money.
|
|
3
|
+
# ADL
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
movement, ADL declares the provider the money moves through.
|
|
5
|
+
ADL is the Adapter Declaration Language: the public format for declaring how a bank or provider behaves at the control-plane boundary. Its validator and conformance suite refuse incomplete interface facts before a generic runtime can poll, classify, reconcile, or deduplicate provider results. The three language boundaries stay separate: HSX authors settlement behavior, UDL defines the canonical product contract, and ADL declares how an external provider performs or confirms work behind that contract.
|
|
10
6
|
|
|
11
|
-
**Status:
|
|
12
|
-
version until 1.0.0.
|
|
7
|
+
**Status: beta.** Current release is 1.0.0-beta.1. Pre-1.0 releases can change on minor versions until 1.0.0.
|
|
13
8
|
|
|
14
9
|
## Adapter symmetry
|
|
15
10
|
|
|
16
|
-
Hyperscale
|
|
17
|
-
more. There is no private adapter API and no second interface for first-party
|
|
18
|
-
code. When one of our adapters needs a seam ADL lacks, the seam is added here,
|
|
19
|
-
in public, or it is not added.
|
|
20
|
-
|
|
21
|
-
So the guide below is not a simplified account of how we do it. It is how we
|
|
22
|
-
do it.
|
|
11
|
+
Hyperscale first-party bank adapters are written against this package and nothing more. There is no private adapter API and no second interface for first-party code. When an adapter needs a seam ADL lacks, the seam is added here, in public, or not at all.
|
|
23
12
|
|
|
24
|
-
|
|
13
|
+
An adapter is a declaration, not a client. It is a plain object stating what the provider does: which commands exist, how responses classify, what bank status tokens mean, when statements can be fetched, and which reference returns on a debit. The generic execution runtime ships with the platform. An adapter opens no sockets, holds no credentials, and decides nothing about what a payment means.
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
which commands exist, how each response classifies, what the bank's status
|
|
28
|
-
tokens mean, when statements can be fetched, which reference comes back on a
|
|
29
|
-
debit. The runtime that executes it is generic and ships with the platform.
|
|
30
|
-
|
|
31
|
-
Your adapter opens no sockets, holds no credentials, and decides nothing about
|
|
32
|
-
what a payment means. It states facts, and the checks in this package prove
|
|
33
|
-
those facts are complete enough for a generic runtime to poll, classify,
|
|
34
|
-
reconcile, and dedupe without guessing.
|
|
15
|
+
Conformance checks a declaration. It does not contact a bank, verify a credential, certify regulatory readiness, or make a Product live.
|
|
35
16
|
|
|
36
17
|
## Install
|
|
37
18
|
|
|
@@ -39,13 +20,11 @@ reconcile, and dedupe without guessing.
|
|
|
39
20
|
npm install @hyperscale0/adl
|
|
40
21
|
```
|
|
41
22
|
|
|
42
|
-
|
|
43
|
-
a bare install gets it. Pin an exact version if you need one: until 1.0.0 a
|
|
44
|
-
change to the surface ships as a minor bump, not a major.
|
|
23
|
+
Pre-1.0 releases are prerelease versions (alpha and beta). Pin an exact version if needed: until 1.0.0, a change to the surface ships as a minor bump, not a major. The package has no runtime dependencies.
|
|
45
24
|
|
|
46
|
-
|
|
25
|
+
## Adapter declaration
|
|
47
26
|
|
|
48
|
-
|
|
27
|
+
A provider adapter declaration using verified package exports and the repository example profile:
|
|
49
28
|
|
|
50
29
|
```ts
|
|
51
30
|
import {
|
|
@@ -53,12 +32,12 @@ import {
|
|
|
53
32
|
createProviderAdapterRegistry,
|
|
54
33
|
type ProviderAdapterVocabulary,
|
|
55
34
|
} from "@hyperscale0/adl";
|
|
35
|
+
import { meridianProfile } from "./examples/meridian-bank/index.js";
|
|
56
36
|
|
|
57
37
|
interface MyVocabulary extends ProviderAdapterVocabulary {
|
|
58
38
|
capability: "payout_execution";
|
|
59
39
|
operation: "payout.submit";
|
|
60
40
|
resource: "payout";
|
|
61
|
-
// ...the rest of your closed unions
|
|
62
41
|
}
|
|
63
42
|
|
|
64
43
|
const defineAdapters = createProviderAdapterRegistry<MyVocabulary>();
|
|
@@ -87,30 +66,18 @@ export const adapters = defineAdapters([
|
|
|
87
66
|
config: {
|
|
88
67
|
credentialRef: { source: "environment", name: "MERIDIAN_CREDENTIALS" },
|
|
89
68
|
},
|
|
90
|
-
profile:
|
|
69
|
+
profile: meridianProfile,
|
|
91
70
|
},
|
|
92
71
|
]);
|
|
93
72
|
|
|
94
|
-
for (const adapter of adapters)
|
|
73
|
+
for (const adapter of adapters) {
|
|
74
|
+
certifyPartnerBankAdapter(adapter);
|
|
75
|
+
}
|
|
95
76
|
```
|
|
96
77
|
|
|
97
|
-
`createProviderAdapterRegistry` validates at import and preserves
|
|
98
|
-
types. `certifyPartnerBankAdapter` throws with every gap listed at once; an
|
|
99
|
-
empty finding list is the certification.
|
|
100
|
-
|
|
101
|
-
## Where to go next
|
|
78
|
+
`createProviderAdapterRegistry` validates at import and preserves literal types. `certifyPartnerBankAdapter` throws with every gap listed at once; an empty finding list is the certification.
|
|
102
79
|
|
|
103
|
-
|
|
104
|
-
by field, with the rule that enforces each one.
|
|
105
|
-
- **[examples/meridian-bank](examples/meridian-bank)** -- a complete certified
|
|
106
|
-
adapter for a bank that does not exist, with fixtures and its own tests.
|
|
107
|
-
Copy it.
|
|
108
|
-
- **[conformance/cases.json](conformance/cases.json)** -- every rule as data:
|
|
109
|
-
one broken fact per case, and what each gate says about it.
|
|
110
|
-
- **[spec/manifest.schema.json](spec/manifest.schema.json)** -- JSON Schema
|
|
111
|
-
2020-12 for one declaration, for authors who are not writing TypeScript.
|
|
112
|
-
|
|
113
|
-
## Running conformance
|
|
80
|
+
Check an adapter declaration with conformance:
|
|
114
81
|
|
|
115
82
|
```bash
|
|
116
83
|
bun run conformance -- ./examples/meridian-bank
|
|
@@ -122,43 +89,22 @@ ok meridian_bank:bank_credit
|
|
|
122
89
|
2 adapter(s) checked, 0 finding(s) reported
|
|
123
90
|
```
|
|
124
91
|
|
|
125
|
-
|
|
92
|
+
The command exits 0 on success and 1 when findings are reported.
|
|
126
93
|
|
|
127
|
-
##
|
|
94
|
+
## Documentation
|
|
128
95
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
96
|
+
- [Authoring guide](docs/authoring-guide.md)
|
|
97
|
+
- [Meridian Bank example](examples/meridian-bank)
|
|
98
|
+
- [Conformance test cases](conformance/cases.json)
|
|
99
|
+
- [Manifest JSON schema](spec/manifest.schema.json)
|
|
100
|
+
- [Contributing](CONTRIBUTING.md)
|
|
134
101
|
|
|
135
102
|
## Versioning
|
|
136
103
|
|
|
137
|
-
Semantic versioning from 1.0.0. Until then,
|
|
138
|
-
surface on a minor version. Two promises hold now:
|
|
139
|
-
|
|
140
|
-
- conformance codes are append-only: a released code is never renamed or
|
|
141
|
-
removed, so `switch` statements stay exhaustive;
|
|
142
|
-
- removing a value from a vocabulary in `src/vocabulary.ts` is a breaking
|
|
143
|
-
change and appears in [CHANGELOG.md](CHANGELOG.md).
|
|
144
|
-
|
|
145
|
-
## Contributing
|
|
146
|
-
|
|
147
|
-
Issues only. Hyperscale makes the changes to the format and the package; you
|
|
148
|
-
propose them in an issue carrying the provider that needs the seam and the
|
|
149
|
-
conformance case it would add. [CONTRIBUTING.md](CONTRIBUTING.md) has that
|
|
150
|
-
model in full, plus the loop: what earns a new field, how to regenerate the
|
|
151
|
-
schema, and the CLA behind a rare accepted pull request. Participation is under
|
|
152
|
-
the [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
104
|
+
Semantic versioning from 1.0.0. Until then, prereleases may change the surface on a minor version. Conformance codes are append-only: a released code is never renamed or removed. Removing a value from a vocabulary in `src/vocabulary.ts` is a breaking change documented in [CHANGELOG.md](CHANGELOG.md).
|
|
153
105
|
|
|
154
|
-
|
|
155
|
-
[SECURITY.md](SECURITY.md).
|
|
106
|
+
## License and security
|
|
156
107
|
|
|
157
|
-
|
|
108
|
+
ADL is licensed under AGPL-3.0-only, with a commercial license available from Hyperscale LLC. See [LICENSE](LICENSE), [LICENSING.md](LICENSING.md), and [TRADEMARKS.md](TRADEMARKS.md).
|
|
158
109
|
|
|
159
|
-
|
|
160
|
-
organisations that cannot accept the AGPL. See [LICENSE](LICENSE) for the text
|
|
161
|
-
and [LICENSING.md](LICENSING.md) for which one you want and how to ask for the
|
|
162
|
-
commercial one. Neither covers the Hyperscale marks;
|
|
163
|
-
[TRADEMARKS.md](TRADEMARKS.md) says what that means and carries the rule for
|
|
164
|
-
claiming conformance.
|
|
110
|
+
Vulnerability reports go through private disclosure as described in [SECURITY.md](SECURITY.md).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAEV,eAAe,EACf,yBAAyB,EAG1B,MAAM,YAAY,CAAC;AAGpB;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,
|
|
1
|
+
{"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAEV,eAAe,EACf,yBAAyB,EAG1B,MAAM,YAAY,CAAC;AAGpB;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,YACtC,8BAA8B,EAC9B,qBAAqB,EACrB,4BAA4B,EAC5B,gCAAgC,EAChC,+BAA+B,EAC/B,wBAAwB,EACxB,oCAAoC,EACpC,6BAA6B,EAC7B,gCAAgC,EAChC,0BAA0B,EAC1B,0BAA0B,EAC1B,6BAA6B,EAC7B,6BAA6B,EAC7B,yCAAyC,EACzC,yBAAyB,EACzB,0BAA0B,EAC1B,6BAA6B,EAC7B,uBAAuB,EACvB,gCAAgC,EAChC,6BAA6B,EAC7B,2BAA2B,EAC3B,0BAA0B,EAC1B,sBAAsB,EACtB,0BAA0B,CAClB,CAAC;AAEX,MAAM,MAAM,0BAA0B,GACpC,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/C,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,IAAI,EAAE,0BAA0B,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAeD;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,UAAU,SAAS,yBAAyB,EAE5C,OAAO,EAAE,eAAe,CAAC,UAAU,CAAC,GACnC,SAAS,6BAA6B,EAAE,CA2B1C;AAED,+EAA+E;AAC/E,wBAAgB,yBAAyB,CACvC,UAAU,SAAS,yBAAyB,EAC5C,OAAO,EAAE,eAAe,CAAC,UAAU,CAAC,GAAG,IAAI,CAW5C"}
|
package/dist/vocabulary.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vocabulary.d.ts","sourceRoot":"","sources":["../src/vocabulary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,kBAAkB,QAA6B,CAAC;AAE7D,eAAO,MAAM,2BAA2B,
|
|
1
|
+
{"version":3,"file":"vocabulary.d.ts","sourceRoot":"","sources":["../src/vocabulary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="360" viewBox="0 0 1200 360" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">ADL</title>
|
|
3
|
+
<desc id="desc">Declare how a bank or provider behaves.</desc>
|
|
4
|
+
<rect width="1200" height="360" fill="#050505"/>
|
|
5
|
+
<path d="M48 48H1152M48 304H1152M944 48V304" fill="none" stroke="#303030"/>
|
|
6
|
+
<rect x="48" y="76" width="10" height="10" fill="#2f7dff"/>
|
|
7
|
+
<g font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">
|
|
8
|
+
<text x="72" y="86" fill="#a3a3a3" font-size="14" letter-spacing="2">HYPERSCALE / DOCUMENTATION</text>
|
|
9
|
+
<text x="44" y="194" fill="#ffffff" font-size="76" letter-spacing="-4">ADL</text>
|
|
10
|
+
<text x="48" y="243" fill="#a3a3a3" font-size="20">Declare how a bank or provider behaves.</text>
|
|
11
|
+
<text x="48" y="334" fill="#a3a3a3" font-size="12" letter-spacing="2">DECLARATION / VALIDATION / CONFORMANCE</text>
|
|
12
|
+
<text x="991" y="279" fill="#a3a3a3" font-size="12">SOURCE → CONTRACT</text>
|
|
13
|
+
</g>
|
|
14
|
+
<g fill="none" stroke="#2f7dff" stroke-width="2">
|
|
15
|
+
<path d="M990 110H1058V178H1126V246H1058V178H990Z"/>
|
|
16
|
+
<path d="M1058 110H1126V178H1058"/>
|
|
17
|
+
</g>
|
|
18
|
+
</svg>
|
package/docs/authoring-guide.md
CHANGED
|
@@ -15,9 +15,9 @@ credential, and never decides what a payment means.
|
|
|
15
15
|
|
|
16
16
|
That split is why the checks in ADL are declaration-level. Conformance
|
|
17
17
|
does not call your bank. It proves the facts you declared are complete enough
|
|
18
|
-
for a generic runtime to poll, classify, reconcile, and dedupe
|
|
19
|
-
guessing
|
|
20
|
-
|
|
18
|
+
for a generic runtime to poll, classify, reconcile, and dedupe without
|
|
19
|
+
guessing. Every guess a runtime makes about money is a provider-boundary
|
|
20
|
+
defect.
|
|
21
21
|
|
|
22
22
|
Two consequences worth internalizing before you write a line:
|
|
23
23
|
|
|
@@ -30,7 +30,7 @@ Two consequences worth internalizing before you write a line:
|
|
|
30
30
|
fact to post from. `validatePartnerBankProfile` refuses a profile whose
|
|
31
31
|
`notificationMechanisms` omits `poll`, whatever else it lists.
|
|
32
32
|
|
|
33
|
-
### Adapter
|
|
33
|
+
### Adapter symmetry
|
|
34
34
|
|
|
35
35
|
Hyperscale's own bank adapters are written against this package and nothing
|
|
36
36
|
more. There is no private adapter API, no internal hook, no second interface
|
|
@@ -108,18 +108,18 @@ Each `ProviderOperationBinding` carries:
|
|
|
108
108
|
| `resourceKind` | The platform resource the provider's answer commits onto. Must appear in `bindings`. |
|
|
109
109
|
| `resourceIdPath` | Where the resource id is read from. Non-blank, checked at load. |
|
|
110
110
|
| `meter` | Optional. The usage meter this operation feeds. |
|
|
111
|
-
| `envelope` | How
|
|
111
|
+
| `envelope` | How this operation's response carries its outcome. Required when the runtime sends a provider command, see below. |
|
|
112
112
|
| `statusEnquiry` | The authoritative read-after-write. Required on a REST command, see below. |
|
|
113
113
|
|
|
114
114
|
### The envelope class, per operation
|
|
115
115
|
|
|
116
116
|
`ProviderResponseEnvelope` has three inhabitants:
|
|
117
117
|
|
|
118
|
-
- `http_200_body_status
|
|
118
|
+
- `http_200_body_status`: every response is HTTP 200 and the outcome is a
|
|
119
119
|
status token in the body. A rejection arrives inside a success.
|
|
120
|
-
- `http_status_error_body
|
|
120
|
+
- `http_status_error_body`: the HTTP status carries the outcome and a failure
|
|
121
121
|
body is prose.
|
|
122
|
-
- `unconfirmed_until_live
|
|
122
|
+
- `unconfirmed_until_live`: the provider publishes no error sample, so the
|
|
123
123
|
class is honestly unknown.
|
|
124
124
|
|
|
125
125
|
Declare it **per operation**, never per provider. Real providers answer in more
|
|
@@ -130,9 +130,10 @@ Conformance requires the class on any binding where the runtime sends a command
|
|
|
130
130
|
and reads an answer, meaning `direction: "tenant_initiated"` on an `egress` of
|
|
131
131
|
`rest` or `sync_clearance`. Missing it is `response_envelope_unclassified`; a
|
|
132
132
|
declared `unconfirmed_until_live` is `response_envelope_unconfirmed`, because
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
no envelope, and declaring one there would be a
|
|
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.
|
|
136
137
|
|
|
137
138
|
### Status enquiry, the read-after-write
|
|
138
139
|
|
|
@@ -165,10 +166,10 @@ enquiry support from a command URL, so:
|
|
|
165
166
|
|
|
166
167
|
`ProviderEgressMode` is how this adapter reaches the provider at all:
|
|
167
168
|
|
|
168
|
-
- `none
|
|
169
|
-
- `rest
|
|
170
|
-
- `batch_reconcile
|
|
171
|
-
- `sync_clearance
|
|
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.
|
|
172
173
|
|
|
173
174
|
A `tenant_initiated` binding on `none` or `batch_reconcile` promises an
|
|
174
175
|
outbound call the egress mode cannot make: `egress_direction_incoherent`.
|
|
@@ -191,11 +192,11 @@ missing: polling is the fact.
|
|
|
191
192
|
`bindings` maps each resource kind this adapter settles to a
|
|
192
193
|
`ProviderResourceBinding`:
|
|
193
194
|
|
|
194
|
-
- `strict
|
|
195
|
-
- `claim
|
|
196
|
-
- `confirmation_only
|
|
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
|
|
197
198
|
decided;
|
|
198
|
-
- `evidence_only
|
|
199
|
+
- `evidence_only`: the fact is recorded as evidence, and settles nothing.
|
|
199
200
|
|
|
200
201
|
Every `resourceKind` named by an operation binding or a webhook plan needs an
|
|
201
202
|
entry, or conformance reports `resource_binding_missing`: a provider fact with
|
|
@@ -226,8 +227,8 @@ would otherwise guess.
|
|
|
226
227
|
|
|
227
228
|
`wireCodec` (`rest_json`, `iso20022_xml`, `swift_mt`) and `auth`
|
|
228
229
|
(`ProviderAuthEnvelope` plus a signing scheme). Declaring a grant the bank will
|
|
229
|
-
not accept fails on the first
|
|
230
|
-
not assume client credentials.
|
|
230
|
+
not accept fails on the first provider call. Read the bank's token service and
|
|
231
|
+
do not assume client credentials.
|
|
231
232
|
|
|
232
233
|
### The idempotency spine
|
|
233
234
|
|
|
@@ -415,8 +416,8 @@ An adapter is handed over as source, never as a running service:
|
|
|
415
416
|
|
|
416
417
|
## Stability
|
|
417
418
|
|
|
418
|
-
The package is
|
|
419
|
-
|
|
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:
|
|
420
421
|
|
|
421
422
|
- conformance codes are append-only;
|
|
422
423
|
- a value removed from a vocabulary in `src/vocabulary.ts` is a breaking change
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/adl",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
4
|
"description": "The Adapter Declaration Language: provider adapter interfaces, registry validation, and the partner-bank conformance suite.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adl",
|
|
@@ -67,13 +67,14 @@
|
|
|
67
67
|
"typecheck": "tsc -p tsconfig.json"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"
|
|
71
|
-
"typescript": "
|
|
70
|
+
"bun-types": "1.4.2",
|
|
71
|
+
"typescript": "7.0.2"
|
|
72
72
|
},
|
|
73
73
|
"author": "Hyperscale LLC",
|
|
74
74
|
"contributors": [
|
|
75
75
|
"Meshael AlBakaawi",
|
|
76
76
|
"Amir Ayub",
|
|
77
77
|
"Sara AlBakaawi"
|
|
78
|
-
]
|
|
78
|
+
],
|
|
79
|
+
"gitHead": "092924e9cf7c89ab2fbff740c5d19e355a74007c"
|
|
79
80
|
}
|
|
@@ -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
|
-
});
|