@hyperscale0/adl 1.0.0-beta.1 → 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 +5 -0
- package/README.md +28 -86
- package/dist/conformance.d.ts.map +1 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/docs/assets/adl.svg +18 -0
- package/package.json +5 -4
- package/examples/meridian-bank/example.spec.ts +0 -162
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,11 @@ 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
|
+
|
|
9
14
|
## [1.0.0-beta.1] - 2026-08-29
|
|
10
15
|
|
|
11
16
|
- This is the first beta and has no package behavior changes from
|
package/README.md
CHANGED
|
@@ -1,41 +1,18 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
2
|
|
|
3
|
-
ADL
|
|
4
|
-
bank or provider behaves at the control-plane boundary. Its validator and
|
|
5
|
-
conformance suite refuse incomplete interface facts before a generic runtime
|
|
6
|
-
can poll, classify, reconcile, or deduplicate provider results.
|
|
3
|
+
# ADL
|
|
7
4
|
|
|
8
|
-
The three language boundaries stay separate
|
|
9
|
-
UDL defines the canonical product contract. ADL declares how an external
|
|
10
|
-
provider performs or confirms work behind that contract.
|
|
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.
|
|
11
6
|
|
|
12
|
-
**Status:
|
|
13
|
-
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.
|
|
14
8
|
|
|
15
9
|
## Adapter symmetry
|
|
16
10
|
|
|
17
|
-
Hyperscale
|
|
18
|
-
more. There is no private adapter API and no second interface for first-party
|
|
19
|
-
code. When one of our adapters needs a seam ADL lacks, the seam is added here,
|
|
20
|
-
in public, or it is not added.
|
|
21
|
-
|
|
22
|
-
So the guide below is not a simplified account of how we do it. It is how we
|
|
23
|
-
do it.
|
|
24
|
-
|
|
25
|
-
## What an adapter is
|
|
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.
|
|
26
12
|
|
|
27
|
-
|
|
28
|
-
which commands exist, how each response classifies, what the bank's status
|
|
29
|
-
tokens mean, when statements can be fetched, which reference comes back on a
|
|
30
|
-
debit. The runtime that executes it is generic and ships with the platform.
|
|
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.
|
|
31
14
|
|
|
32
|
-
|
|
33
|
-
what a payment means. It states facts, and the checks in this package prove
|
|
34
|
-
those facts are complete enough for a generic runtime to poll, classify,
|
|
35
|
-
reconcile, and dedupe without guessing.
|
|
36
|
-
|
|
37
|
-
Conformance checks a declaration. It does not contact a provider, verify a
|
|
38
|
-
credential, certify regulatory readiness, or make a Product live.
|
|
15
|
+
Conformance checks a declaration. It does not contact a bank, verify a credential, certify regulatory readiness, or make a Product live.
|
|
39
16
|
|
|
40
17
|
## Install
|
|
41
18
|
|
|
@@ -43,13 +20,11 @@ credential, certify regulatory readiness, or make a Product live.
|
|
|
43
20
|
npm install @hyperscale0/adl
|
|
44
21
|
```
|
|
45
22
|
|
|
46
|
-
|
|
47
|
-
a bare install gets it. Pin an exact version if you need one: until 1.0.0 a
|
|
48
|
-
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.
|
|
49
24
|
|
|
50
|
-
|
|
25
|
+
## Adapter declaration
|
|
51
26
|
|
|
52
|
-
|
|
27
|
+
A provider adapter declaration using verified package exports and the repository example profile:
|
|
53
28
|
|
|
54
29
|
```ts
|
|
55
30
|
import {
|
|
@@ -57,12 +32,12 @@ import {
|
|
|
57
32
|
createProviderAdapterRegistry,
|
|
58
33
|
type ProviderAdapterVocabulary,
|
|
59
34
|
} from "@hyperscale0/adl";
|
|
35
|
+
import { meridianProfile } from "./examples/meridian-bank/index.js";
|
|
60
36
|
|
|
61
37
|
interface MyVocabulary extends ProviderAdapterVocabulary {
|
|
62
38
|
capability: "payout_execution";
|
|
63
39
|
operation: "payout.submit";
|
|
64
40
|
resource: "payout";
|
|
65
|
-
// ...the rest of your closed unions
|
|
66
41
|
}
|
|
67
42
|
|
|
68
43
|
const defineAdapters = createProviderAdapterRegistry<MyVocabulary>();
|
|
@@ -91,30 +66,18 @@ export const adapters = defineAdapters([
|
|
|
91
66
|
config: {
|
|
92
67
|
credentialRef: { source: "environment", name: "MERIDIAN_CREDENTIALS" },
|
|
93
68
|
},
|
|
94
|
-
profile:
|
|
69
|
+
profile: meridianProfile,
|
|
95
70
|
},
|
|
96
71
|
]);
|
|
97
72
|
|
|
98
|
-
for (const adapter of adapters)
|
|
73
|
+
for (const adapter of adapters) {
|
|
74
|
+
certifyPartnerBankAdapter(adapter);
|
|
75
|
+
}
|
|
99
76
|
```
|
|
100
77
|
|
|
101
|
-
`createProviderAdapterRegistry` validates at import and preserves
|
|
102
|
-
types. `certifyPartnerBankAdapter` throws with every gap listed at once; an
|
|
103
|
-
empty finding list is the certification.
|
|
104
|
-
|
|
105
|
-
## 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.
|
|
106
79
|
|
|
107
|
-
|
|
108
|
-
by field, with the rule that enforces each one.
|
|
109
|
-
- **[examples/meridian-bank](examples/meridian-bank)**: a complete certified
|
|
110
|
-
adapter for a bank that does not exist, with fixtures and its own tests.
|
|
111
|
-
Copy it.
|
|
112
|
-
- **[conformance/cases.json](conformance/cases.json)**: every rule as data:
|
|
113
|
-
one broken fact per case, and what each gate says about it.
|
|
114
|
-
- **[spec/manifest.schema.json](spec/manifest.schema.json)**: JSON Schema
|
|
115
|
-
2020-12 for one declaration, for authors who are not writing TypeScript.
|
|
116
|
-
|
|
117
|
-
## Running conformance
|
|
80
|
+
Check an adapter declaration with conformance:
|
|
118
81
|
|
|
119
82
|
```bash
|
|
120
83
|
bun run conformance -- ./examples/meridian-bank
|
|
@@ -126,43 +89,22 @@ ok meridian_bank:bank_credit
|
|
|
126
89
|
2 adapter(s) checked, 0 finding(s) reported
|
|
127
90
|
```
|
|
128
91
|
|
|
129
|
-
|
|
92
|
+
The command exits 0 on success and 1 when findings are reported.
|
|
130
93
|
|
|
131
|
-
##
|
|
94
|
+
## Documentation
|
|
132
95
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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)
|
|
138
101
|
|
|
139
102
|
## Versioning
|
|
140
103
|
|
|
141
|
-
Semantic versioning from 1.0.0. Until then,
|
|
142
|
-
surface on a minor version. Two promises hold now:
|
|
143
|
-
|
|
144
|
-
- conformance codes are append-only: a released code is never renamed or
|
|
145
|
-
removed, so `switch` statements stay exhaustive;
|
|
146
|
-
- removing a value from a vocabulary in `src/vocabulary.ts` is a breaking
|
|
147
|
-
change and appears in [CHANGELOG.md](CHANGELOG.md).
|
|
148
|
-
|
|
149
|
-
## Contributing
|
|
150
|
-
|
|
151
|
-
Issues only. Hyperscale makes the changes to the format and the package; you
|
|
152
|
-
propose them in an issue carrying the provider that needs the seam and the
|
|
153
|
-
conformance case it would add. [CONTRIBUTING.md](CONTRIBUTING.md) has that
|
|
154
|
-
model in full, plus the loop: what earns a new field, how to regenerate the
|
|
155
|
-
schema, and the CLA behind a rare accepted pull request. Participation is under
|
|
156
|
-
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).
|
|
157
105
|
|
|
158
|
-
|
|
159
|
-
[SECURITY.md](SECURITY.md).
|
|
106
|
+
## License and security
|
|
160
107
|
|
|
161
|
-
|
|
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).
|
|
162
109
|
|
|
163
|
-
|
|
164
|
-
organisations that cannot accept the AGPL. See [LICENSE](LICENSE) for the text
|
|
165
|
-
and [LICENSING.md](LICENSING.md) for which one you want and how to ask for the
|
|
166
|
-
commercial one. Neither covers the Hyperscale marks;
|
|
167
|
-
[TRADEMARKS.md](TRADEMARKS.md) says what that means and carries the rule for
|
|
168
|
-
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":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,kBAAkB,QAA6B,CAAC;AAE7D,eAAO,MAAM,2BAA2B,YACtC,kBAAkB,EAClB,mBAAmB,CACX,CAAC;AAEX,eAAO,MAAM,wBAAwB,YACnC,QAAQ,EACR,OAAO,EACP,mBAAmB,EACnB,eAAe,CACP,CAAC;AAEX,eAAO,MAAM,mBAAmB,YAC9B,MAAM,EACN,MAAM,EACN,iBAAiB,EACjB,gBAAgB,CACR,CAAC;AAEX,qEAAqE;AACrE,eAAO,MAAM,iBAAiB,YAC5B,gBAAgB,EAChB,oBAAoB,CACZ,CAAC;AAEX;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,YACpC,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,CAChB,CAAC;AAEX;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,YAChC,2BAA2B,EAC3B,iBAAiB,EACjB,oBAAoB,EACpB,YAAY,EACZ,aAAa,CACL,CAAC;AAEX,eAAO,MAAM,8BAA8B,YAAI,MAAM,EAAE,SAAS,CAAU,CAAC;AAE3E,eAAO,MAAM,uBAAuB,YAClC,UAAU,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,WAAW,EACX,UAAU,EACV,UAAU,EACV,YAAY,CACJ,CAAC;AAEX,eAAO,MAAM,qBAAqB,YAChC,WAAW,EACX,cAAc,EACd,UAAU,CACF,CAAC;AAEX,eAAO,MAAM,mBAAmB,YAC9B,KAAK,EACL,oBAAoB,EACpB,aAAa,CACL,CAAC;AAEX,wEAAwE;AACxE,eAAO,MAAM,0BAA0B,YACrC,UAAU,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,UAAU,EACV,8BAA8B,EAC9B,6BAA6B,EAC7B,8BAA8B,CACtB,CAAC;AAEX,eAAO,MAAM,2BAA2B,YACtC,OAAO,EACP,OAAO,EACP,UAAU,EACV,UAAU,EACV,WAAW,CACH,CAAC;AAEX,uEAAuE;AACvE,eAAO,MAAM,qBAAqB,YAChC,QAAQ,EACR,eAAe,EACf,YAAY,EACZ,kBAAkB,CACV,CAAC;AAEX,4DAA4D;AAC5D,eAAO,MAAM,uBAAuB,YAAI,KAAK,EAAE,YAAY,CAAU,CAAC;AAEtE,iFAAiF;AACjF,eAAO,MAAM,wBAAwB,YACnC,gBAAgB,EAChB,oBAAoB,CACZ,CAAC;AAEX,eAAO,MAAM,cAAc,YAAI,UAAU,EAAE,gBAAgB,CAAU,CAAC;AAEtE,eAAO,MAAM,cAAc,YAAI,eAAe,EAAE,oBAAoB,CAAU,CAAC;AAE/E,eAAO,MAAM,eAAe,YAAI,mBAAmB,CAAU,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,iBAAiB,YAAI,cAAc,EAAE,MAAM,CAAU,CAAC;AAEnE,eAAO,MAAM,gBAAgB,YAAI,WAAW,CAAU,CAAC;AAEvD,eAAO,MAAM,eAAe,YAC1B,OAAO,EACP,iBAAiB,EACjB,kBAAkB,CACV,CAAC;AAEX,eAAO,MAAM,0BAA0B,YAAI,KAAK,EAAE,aAAa,CAAU,CAAC;AAE1E,eAAO,MAAM,uBAAuB,YAAI,MAAM,EAAE,aAAa,CAAU,CAAC;AAExE,eAAO,MAAM,sBAAsB,YAAI,MAAM,EAAE,mBAAmB,CAAU,CAAC;AAE7E,MAAM,MAAM,0BAA0B,GACpC,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAC/C,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAChF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACtE,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAClE,MAAM,MAAM,wBAAwB,GAClC,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC7C,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC1E,MAAM,MAAM,6BAA6B,GACvC,CAAC,OAAO,8BAA8B,CAAC,CAAC,MAAM,CAAC,CAAC;AAClD,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9E,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC1E,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACtE,MAAM,MAAM,yBAAyB,GACnC,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9C,MAAM,MAAM,0BAA0B,GACpC,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,CAAC,CAAC;AAC/C,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC1E,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC7E,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAChF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5D,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5D,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9D,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAClE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAChE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9D,MAAM,MAAM,yBAAyB,GACnC,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9C,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC/E,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5E,yEAAyE;AACzE,MAAM,MAAM,eAAe,GAAG,KAAK,MAAM,EAAE,CAAC"}
|
|
@@ -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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/adl",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
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
|
-
});
|