@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
package/dist/conformance.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Partner-bank conformance: the certification gate an adapter passes before it
|
|
3
|
-
* may serve the family live. Registry load validation
|
|
4
|
-
* (`createProviderAdapterRegistry`) proves a declaration is well-formed; this
|
|
5
|
-
* module proves it is OPERABLE against the Bank Reference Model -- the
|
|
6
|
-
* bank-agnostic contract distilled from observed bank behavior. Every rule
|
|
7
|
-
* names the operational failure it prevents, and every rule fails closed: a
|
|
8
|
-
* fact the adapter leaves unestablished is a finding, never a default.
|
|
9
|
-
*
|
|
10
|
-
* The checks are declaration-level on purpose. A partner-bank adapter is a
|
|
11
|
-
* declaration (bindings, operation map, webhook plans, profile facts); the
|
|
12
|
-
* runtime that executes it is generic. What certification must prove is that
|
|
13
|
-
* the declared facts are complete enough for that runtime to poll, classify,
|
|
14
|
-
* reconcile, and dedupe without guessing.
|
|
15
|
-
*/
|
|
16
|
-
import type { ProviderAdapter, ProviderAdapterVocabulary } from "./index.js";
|
|
17
|
-
/**
|
|
18
|
-
* Every rule this suite can report. Grows append-only: a released code is
|
|
19
|
-
* never renamed or removed, so consumers may switch on codes without a
|
|
20
|
-
* fallback arm going stale. `conformance/cases.json` must exercise all of
|
|
21
|
-
* them, and `test/conformance-cases.spec.ts` fails when one is unexercised.
|
|
22
|
-
*/
|
|
23
|
-
export declare const partnerBankConformanceCodes: readonly ["partner_bank_profile_missing", "operation_map_empty", "required_operation_missing", "response_envelope_unclassified", "response_envelope_unconfirmed", "status_enquiry_missing", "status_enquiry_without_rest_egress", "egress_direction_incoherent", "lifecycle_terminals_incomplete", "lifecycle_intake_missing", "lifecycle_spelling_blank", "lifecycle_spelling_conflict", "duplicate_verdicts_unmapped", "statement_debit_reference_unestablished", "statement_formats_empty", "statement_window_missing", "statement_window_incomplete", "charges_unestablished", "charge_narrative_unestablished", "vat_narrative_unestablished", "value_date_offset_invalid", "cutoff_window_regression", "cutoff_clock_invalid", "resource_binding_missing"];
|
|
24
|
-
export type PartnerBankConformanceCode = (typeof partnerBankConformanceCodes)[number];
|
|
25
|
-
export interface PartnerBankConformanceFinding {
|
|
26
|
-
readonly code: PartnerBankConformanceCode;
|
|
27
|
-
readonly message: string;
|
|
28
|
-
/** Dotted location inside the adapter declaration, for the fix. */
|
|
29
|
-
readonly path: string;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Every conformance finding for one adapter. The profile-independent checks
|
|
33
|
-
* (operations, egress coherence, bindings) run even when the profile is
|
|
34
|
-
* missing, so one run reports everything it can see; a missing profile leads
|
|
35
|
-
* the list. An empty result is the certification. Callers wanting a hard
|
|
36
|
-
* stop use `certifyPartnerBankAdapter`.
|
|
37
|
-
*/
|
|
38
|
-
export declare function partnerBankConformanceFindings<Vocabulary extends ProviderAdapterVocabulary>(adapter: ProviderAdapter<Vocabulary>): readonly PartnerBankConformanceFinding[];
|
|
39
|
-
/** Throw with every finding listed; return silently on a certified adapter. */
|
|
40
|
-
export declare function certifyPartnerBankAdapter<Vocabulary extends ProviderAdapterVocabulary>(adapter: ProviderAdapter<Vocabulary>): void;
|
|
41
|
-
//# sourceMappingURL=conformance.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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/conformance.js
DELETED
|
@@ -1,486 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Partner-bank conformance: the certification gate an adapter passes before it
|
|
3
|
-
* may serve the family live. Registry load validation
|
|
4
|
-
* (`createProviderAdapterRegistry`) proves a declaration is well-formed; this
|
|
5
|
-
* module proves it is OPERABLE against the Bank Reference Model -- the
|
|
6
|
-
* bank-agnostic contract distilled from observed bank behavior. Every rule
|
|
7
|
-
* names the operational failure it prevents, and every rule fails closed: a
|
|
8
|
-
* fact the adapter leaves unestablished is a finding, never a default.
|
|
9
|
-
*
|
|
10
|
-
* The checks are declaration-level on purpose. A partner-bank adapter is a
|
|
11
|
-
* declaration (bindings, operation map, webhook plans, profile facts); the
|
|
12
|
-
* runtime that executes it is generic. What certification must prove is that
|
|
13
|
-
* the declared facts are complete enough for that runtime to poll, classify,
|
|
14
|
-
* reconcile, and dedupe without guessing.
|
|
15
|
-
*/
|
|
16
|
-
import { statementAvailabilities } from "./vocabulary.js";
|
|
17
|
-
/**
|
|
18
|
-
* Every rule this suite can report. Grows append-only: a released code is
|
|
19
|
-
* never renamed or removed, so consumers may switch on codes without a
|
|
20
|
-
* fallback arm going stale. `conformance/cases.json` must exercise all of
|
|
21
|
-
* them, and `test/conformance-cases.spec.ts` fails when one is unexercised.
|
|
22
|
-
*/
|
|
23
|
-
export const partnerBankConformanceCodes = [
|
|
24
|
-
"partner_bank_profile_missing",
|
|
25
|
-
"operation_map_empty",
|
|
26
|
-
"required_operation_missing",
|
|
27
|
-
"response_envelope_unclassified",
|
|
28
|
-
"response_envelope_unconfirmed",
|
|
29
|
-
"status_enquiry_missing",
|
|
30
|
-
"status_enquiry_without_rest_egress",
|
|
31
|
-
"egress_direction_incoherent",
|
|
32
|
-
"lifecycle_terminals_incomplete",
|
|
33
|
-
"lifecycle_intake_missing",
|
|
34
|
-
"lifecycle_spelling_blank",
|
|
35
|
-
"lifecycle_spelling_conflict",
|
|
36
|
-
"duplicate_verdicts_unmapped",
|
|
37
|
-
"statement_debit_reference_unestablished",
|
|
38
|
-
"statement_formats_empty",
|
|
39
|
-
"statement_window_missing",
|
|
40
|
-
"statement_window_incomplete",
|
|
41
|
-
"charges_unestablished",
|
|
42
|
-
"charge_narrative_unestablished",
|
|
43
|
-
"vat_narrative_unestablished",
|
|
44
|
-
"value_date_offset_invalid",
|
|
45
|
-
"cutoff_window_regression",
|
|
46
|
-
"cutoff_clock_invalid",
|
|
47
|
-
"resource_binding_missing",
|
|
48
|
-
];
|
|
49
|
-
/**
|
|
50
|
-
* The Bank Reference Model's operation floor per bank capability: the
|
|
51
|
-
* commands an adapter must bind before the capability is served at all. An
|
|
52
|
-
* adapter cannot dodge a command-level rule by not declaring the command.
|
|
53
|
-
* Capabilities outside this table carry no floor beyond a non-empty map.
|
|
54
|
-
*/
|
|
55
|
-
const brmRequiredOperations = {
|
|
56
|
-
bank_credit: ["deposit.record"],
|
|
57
|
-
beneficiary_verification: ["beneficiary.verify"],
|
|
58
|
-
financial_address_provisioning: ["financial_address.activate"],
|
|
59
|
-
payout_execution: ["payout.submit"],
|
|
60
|
-
};
|
|
61
|
-
/**
|
|
62
|
-
* Every conformance finding for one adapter. The profile-independent checks
|
|
63
|
-
* (operations, egress coherence, bindings) run even when the profile is
|
|
64
|
-
* missing, so one run reports everything it can see; a missing profile leads
|
|
65
|
-
* the list. An empty result is the certification. Callers wanting a hard
|
|
66
|
-
* stop use `certifyPartnerBankAdapter`.
|
|
67
|
-
*/
|
|
68
|
-
export function partnerBankConformanceFindings(adapter) {
|
|
69
|
-
const declarationFindings = [
|
|
70
|
-
...operationFindings(adapter),
|
|
71
|
-
...bindingFindings(adapter),
|
|
72
|
-
];
|
|
73
|
-
const profile = adapter.profile;
|
|
74
|
-
if (!profile) {
|
|
75
|
-
return [
|
|
76
|
-
{
|
|
77
|
-
code: "partner_bank_profile_missing",
|
|
78
|
-
message: "no partner-bank profile is declared, so none of the bank facts " +
|
|
79
|
-
"(lifecycle vocabulary, statement identity, windows, charges) " +
|
|
80
|
-
"exist for the runtime to operate from",
|
|
81
|
-
path: "profile",
|
|
82
|
-
},
|
|
83
|
-
...declarationFindings,
|
|
84
|
-
];
|
|
85
|
-
}
|
|
86
|
-
return [
|
|
87
|
-
...declarationFindings,
|
|
88
|
-
...lifecycleFindings(profile),
|
|
89
|
-
...statementFindings(profile),
|
|
90
|
-
...chargeFindings(profile),
|
|
91
|
-
...windowFindings(profile),
|
|
92
|
-
];
|
|
93
|
-
}
|
|
94
|
-
/** Throw with every finding listed; return silently on a certified adapter. */
|
|
95
|
-
export function certifyPartnerBankAdapter(adapter) {
|
|
96
|
-
const findings = partnerBankConformanceFindings(adapter);
|
|
97
|
-
if (findings.length === 0)
|
|
98
|
-
return;
|
|
99
|
-
const lines = findings.map((finding) => ` - [${finding.code}] ${finding.path}: ${finding.message}`);
|
|
100
|
-
throw new Error(`${adapter.provider}:${adapter.capability} failed partner-bank ` +
|
|
101
|
-
`conformance (${findings.length} finding${findings.length === 1 ? "" : "s"}):\n` +
|
|
102
|
-
lines.join("\n"));
|
|
103
|
-
}
|
|
104
|
-
function operationFindings(adapter) {
|
|
105
|
-
const findings = [];
|
|
106
|
-
const bindings = Object.entries(adapter.operationMap).filter((entry) => entry[1] !== undefined);
|
|
107
|
-
// An adapter with no operations serves nothing, and a rule scoped to a
|
|
108
|
-
// command must not be dodgeable by deleting the command.
|
|
109
|
-
if (bindings.length === 0) {
|
|
110
|
-
findings.push({
|
|
111
|
-
code: "operation_map_empty",
|
|
112
|
-
message: "no operations are bound, so the capability cannot be served and " +
|
|
113
|
-
"every command-level rule is vacuously green",
|
|
114
|
-
path: "operationMap",
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
for (const required of brmRequiredOperations[adapter.capability] ?? []) {
|
|
118
|
-
if (bindings.some(([operation]) => operation === required))
|
|
119
|
-
continue;
|
|
120
|
-
findings.push({
|
|
121
|
-
code: "required_operation_missing",
|
|
122
|
-
message: `capability ${adapter.capability} must bind ${required}; without ` +
|
|
123
|
-
"it the capability's command surface does not exist",
|
|
124
|
-
path: `operationMap.${required}`,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
// A provider response exists to classify only where the runtime sends the
|
|
128
|
-
// provider a command and reads an answer: tenant-initiated bindings on an
|
|
129
|
-
// outbound egress mode. Observation-recorded settlements carry no response
|
|
130
|
-
// of their own, and an envelope declared there would be a borrowed fact.
|
|
131
|
-
const outboundEgress = adapter.egress === "rest" || adapter.egress === "sync_clearance";
|
|
132
|
-
for (const [operation, binding] of bindings) {
|
|
133
|
-
const path = `operationMap.${operation}`;
|
|
134
|
-
const commandCarriesResponse = outboundEgress && binding.direction === "tenant_initiated";
|
|
135
|
-
// An absent class on a real command means the runtime cannot tell
|
|
136
|
-
// acceptance from rejection, and `unconfirmed_until_live` is the honest
|
|
137
|
-
// authoring state for exactly that gap -- neither may serve live,
|
|
138
|
-
// because a rejection read as silence strands money in flight.
|
|
139
|
-
if (commandCarriesResponse && !binding.envelope) {
|
|
140
|
-
findings.push({
|
|
141
|
-
code: "response_envelope_unclassified",
|
|
142
|
-
message: "no response envelope class is declared, so the runtime cannot " +
|
|
143
|
-
"classify this command's outcome",
|
|
144
|
-
path: `${path}.envelope`,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
if (binding.envelope === "unconfirmed_until_live") {
|
|
148
|
-
findings.push({
|
|
149
|
-
code: "response_envelope_unconfirmed",
|
|
150
|
-
message: "the envelope class is declared unconfirmed; certification is the " +
|
|
151
|
-
"licence to serve live, so the class must be established first",
|
|
152
|
-
path: `${path}.envelope`,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
// Poll-first read-after-write: a REST command's response can be lost
|
|
156
|
-
// after the bank acted on it. Without an authoritative status enquiry
|
|
157
|
-
// the adapter's only options are guessing or double-submitting.
|
|
158
|
-
if (adapter.egress === "rest" &&
|
|
159
|
-
binding.direction === "tenant_initiated" &&
|
|
160
|
-
!binding.statusEnquiry) {
|
|
161
|
-
findings.push({
|
|
162
|
-
code: "status_enquiry_missing",
|
|
163
|
-
message: "a tenant-initiated REST command declares no status enquiry, so a " +
|
|
164
|
-
"lost response can only be resolved by guessing or resubmitting",
|
|
165
|
-
path: `${path}.statusEnquiry`,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
// The exemption is earned by the egress mode, never by authoring
|
|
169
|
-
// silence: an enquiry path on an adapter that performs no REST egress
|
|
170
|
-
// is a plan the runtime can never execute.
|
|
171
|
-
if (adapter.egress !== "rest" && binding.statusEnquiry) {
|
|
172
|
-
findings.push({
|
|
173
|
-
code: "status_enquiry_without_rest_egress",
|
|
174
|
-
message: `a status enquiry is declared but egress is ${adapter.egress}, ` +
|
|
175
|
-
"so the runtime has no transport to execute it",
|
|
176
|
-
path: `${path}.statusEnquiry`,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
// Sending nothing (`none`) or reconciling from files (`batch_reconcile`)
|
|
180
|
-
// cannot carry a tenant-initiated command; declaring one there promises
|
|
181
|
-
// an outbound call the egress mode cannot make.
|
|
182
|
-
if ((adapter.egress === "none" || adapter.egress === "batch_reconcile") &&
|
|
183
|
-
binding.direction === "tenant_initiated") {
|
|
184
|
-
findings.push({
|
|
185
|
-
code: "egress_direction_incoherent",
|
|
186
|
-
message: `a tenant-initiated command is bound but egress is ` +
|
|
187
|
-
`${adapter.egress}, which performs no per-command outbound call`,
|
|
188
|
-
path: `${path}.direction`,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return findings;
|
|
193
|
-
}
|
|
194
|
-
function lifecycleFindings(profile) {
|
|
195
|
-
const findings = [];
|
|
196
|
-
const path = "profile.lifecycleVocabulary";
|
|
197
|
-
const canonicalStates = new Set(Object.values(profile.lifecycleVocabulary));
|
|
198
|
-
// Tri-state settlement is the model's spine: a submitted payment ends
|
|
199
|
-
// processed, failed, or returned, and the poll lane owns the terminals. A
|
|
200
|
-
// vocabulary that cannot spell one of them leaves that ending unclassifiable
|
|
201
|
-
// and the payout polling forever.
|
|
202
|
-
const missingTerminals = ["processed", "failed", "returned"].filter((state) => !canonicalStates.has(state));
|
|
203
|
-
if (missingTerminals.length > 0) {
|
|
204
|
-
findings.push({
|
|
205
|
-
code: "lifecycle_terminals_incomplete",
|
|
206
|
-
message: `no bank spelling maps to ${missingTerminals.join(", ")}; a payment ` +
|
|
207
|
-
"ending there would be unclassifiable and poll forever",
|
|
208
|
-
path,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
// An ack proves the bank took the instruction, never that money moved.
|
|
212
|
-
// Without an intake state every ack would have to be graded terminal or
|
|
213
|
-
// dropped.
|
|
214
|
-
if (!canonicalStates.has("received") && !canonicalStates.has("accepted")) {
|
|
215
|
-
findings.push({
|
|
216
|
-
code: "lifecycle_intake_missing",
|
|
217
|
-
message: "no bank spelling maps to received or accepted, so an " +
|
|
218
|
-
"acknowledgement has no non-terminal state to land on",
|
|
219
|
-
path,
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
// Banks spell one verdict several ways (case drift, trailing spaces), so
|
|
223
|
-
// runtimes match spellings after trimming and upper-casing. A spelling that
|
|
224
|
-
// is empty after that normalization matches nothing; two spellings that
|
|
225
|
-
// collide under it while naming different canonical states make the
|
|
226
|
-
// classification order-dependent.
|
|
227
|
-
const normalized = new Map();
|
|
228
|
-
for (const [spelling, state] of Object.entries(profile.lifecycleVocabulary)) {
|
|
229
|
-
const token = spelling.trim().toUpperCase();
|
|
230
|
-
if (token.length === 0) {
|
|
231
|
-
findings.push({
|
|
232
|
-
code: "lifecycle_spelling_blank",
|
|
233
|
-
message: "a lifecycle spelling is empty after trimming, so it can never " +
|
|
234
|
-
"match a bank status token",
|
|
235
|
-
path,
|
|
236
|
-
});
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
const existing = normalized.get(token);
|
|
240
|
-
if (existing && existing.state !== state) {
|
|
241
|
-
findings.push({
|
|
242
|
-
code: "lifecycle_spelling_conflict",
|
|
243
|
-
message: `"${spelling}" and "${existing.spelling}" normalize to the same ` +
|
|
244
|
-
`token but map to ${state} and ${existing.state}`,
|
|
245
|
-
path: `${path}.${spelling}`,
|
|
246
|
-
});
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
normalized.set(token, { spelling, state });
|
|
250
|
-
}
|
|
251
|
-
// The dedupe spine is reference-based by load law (a non-empty dedupeKeys
|
|
252
|
-
// list), so the bank answers a crash-recovery resubmission with a duplicate
|
|
253
|
-
// verdict graded against the original's outcome. An adapter that cannot
|
|
254
|
-
// classify those verdicts reads its own recovery as a fresh failure.
|
|
255
|
-
if (!canonicalStates.has("duplicate_original_succeeded") ||
|
|
256
|
-
!canonicalStates.has("duplicate_original_rejected")) {
|
|
257
|
-
findings.push({
|
|
258
|
-
code: "duplicate_verdicts_unmapped",
|
|
259
|
-
message: "the vocabulary cannot classify duplicate verdicts, so a recovery " +
|
|
260
|
-
"resubmission over the dedupe spine reads as a fresh failure",
|
|
261
|
-
path,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
return findings;
|
|
265
|
-
}
|
|
266
|
-
function statementFindings(profile) {
|
|
267
|
-
const findings = [];
|
|
268
|
-
// Statements are bank truth; reconciliation has nothing to prove the ledger
|
|
269
|
-
// against without at least one fetchable format.
|
|
270
|
-
if (profile.statementFormats.length === 0) {
|
|
271
|
-
findings.push({
|
|
272
|
-
code: "statement_formats_empty",
|
|
273
|
-
message: "no statement format is declared, so reconciliation has no bank " +
|
|
274
|
-
"truth to prove the ledger against",
|
|
275
|
-
path: "profile.statementFormats",
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
// A statement debit line carries two references -- the bank's own entry
|
|
279
|
-
// reference and the client reference the instruction was sent with -- and
|
|
280
|
-
// banks disagree about which comes back. Left unestablished, every payout
|
|
281
|
-
// the platform instructed looks like an unexplained debit.
|
|
282
|
-
if (!profile.statementDebitReference) {
|
|
283
|
-
findings.push({
|
|
284
|
-
code: "statement_debit_reference_unestablished",
|
|
285
|
-
message: "which reference ties a statement debit to its instruction is not " +
|
|
286
|
-
"established, so every instructed payout reads as an unexplained " +
|
|
287
|
-
"debit",
|
|
288
|
-
path: "profile.statementDebitReference",
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
return findings;
|
|
292
|
-
}
|
|
293
|
-
function chargeFindings(profile) {
|
|
294
|
-
const findings = [];
|
|
295
|
-
const charges = profile.charges;
|
|
296
|
-
// The profile allows an absent charge model as honest authoring; a
|
|
297
|
-
// certified adapter cannot leave it open, because the bank's own charge
|
|
298
|
-
// debits would land as unexplained statement lines.
|
|
299
|
-
if (!charges) {
|
|
300
|
-
return [
|
|
301
|
-
{
|
|
302
|
-
code: "charges_unestablished",
|
|
303
|
-
message: "the bank's charge billing is unmodeled, so its own charge debits " +
|
|
304
|
-
"would land as unexplained statement lines",
|
|
305
|
-
path: "profile.charges",
|
|
306
|
-
},
|
|
307
|
-
];
|
|
308
|
-
}
|
|
309
|
-
// Post-hoc charges arrive as bank-initiated debits recognized only by their
|
|
310
|
-
// narrative wording; a separate VAT line is its own debit with its own
|
|
311
|
-
// wording. Each mode in play needs at least one declared prefix.
|
|
312
|
-
const chargeNarratives = charges.narratives?.charge ?? [];
|
|
313
|
-
if (charges.posting === "post_hoc" && chargeNarratives.length === 0) {
|
|
314
|
-
findings.push({
|
|
315
|
-
code: "charge_narrative_unestablished",
|
|
316
|
-
message: "charges post after the payment but no charge narrative prefix is " +
|
|
317
|
-
"declared, so the bank's charge debits are unrecognizable",
|
|
318
|
-
path: "profile.charges.narratives.charge",
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
const vatNarratives = charges.narratives?.vat ?? [];
|
|
322
|
-
if (charges.vat === "separate_line" && vatNarratives.length === 0) {
|
|
323
|
-
findings.push({
|
|
324
|
-
code: "vat_narrative_unestablished",
|
|
325
|
-
message: "VAT arrives as its own ledger line but no VAT narrative prefix is " +
|
|
326
|
-
"declared, so the tax debits are unrecognizable",
|
|
327
|
-
path: "profile.charges.narratives.vat",
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
return findings;
|
|
331
|
-
}
|
|
332
|
-
function windowFindings(profile) {
|
|
333
|
-
const findings = [];
|
|
334
|
-
const cutoff = profile.windows.paymentCutoff;
|
|
335
|
-
const beforeDays = valueDateOffsetDays(cutoff.beforeCutoffValueDate);
|
|
336
|
-
const afterDays = valueDateOffsetDays(cutoff.afterCutoffValueDate);
|
|
337
|
-
// The type says `D+<number>`, but a plain-JS consumer can send anything
|
|
338
|
-
// ("T+1", "D+1.5"), and a malformed offset must be a finding rather than a
|
|
339
|
-
// NaN that slides through every comparison below.
|
|
340
|
-
if (beforeDays === null) {
|
|
341
|
-
findings.push({
|
|
342
|
-
code: "value_date_offset_invalid",
|
|
343
|
-
message: `"${cutoff.beforeCutoffValueDate}" is not a D+<days> offset with a ` +
|
|
344
|
-
"non-negative whole number of days",
|
|
345
|
-
path: "profile.windows.paymentCutoff.beforeCutoffValueDate",
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
if (afterDays === null) {
|
|
349
|
-
findings.push({
|
|
350
|
-
code: "value_date_offset_invalid",
|
|
351
|
-
message: `"${cutoff.afterCutoffValueDate}" is not a D+<days> offset with a ` +
|
|
352
|
-
"non-negative whole number of days",
|
|
353
|
-
path: "profile.windows.paymentCutoff.afterCutoffValueDate",
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
// Registry load validates the cutoff clock too, but certification is the
|
|
357
|
-
// public gate a third-party adapter author runs, so it must not stay green
|
|
358
|
-
// on a clock the runtime would misread. Intl.DateTimeFormat reads an
|
|
359
|
-
// undefined timeZone as the runtime default, so presence comes before
|
|
360
|
-
// validity.
|
|
361
|
-
if (typeof cutoff.time !== "string" || !/^\d{2}:\d{2}$/.test(cutoff.time)) {
|
|
362
|
-
findings.push({
|
|
363
|
-
code: "cutoff_clock_invalid",
|
|
364
|
-
message: `paymentCutoff.time "${String(cutoff.time)}" is not an HH:MM ` +
|
|
365
|
-
"wall-clock time",
|
|
366
|
-
path: "profile.windows.paymentCutoff.time",
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
if (!isValidTimeZone(cutoff.timeZone)) {
|
|
370
|
-
findings.push({
|
|
371
|
-
code: "cutoff_clock_invalid",
|
|
372
|
-
message: `paymentCutoff.timeZone "${String(cutoff.timeZone)}" is not a valid ` +
|
|
373
|
-
"IANA time zone, so the cutoff cannot be read on the bank's clock",
|
|
374
|
-
path: "profile.windows.paymentCutoff.timeZone",
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
// Missing the cutoff can only push value later, and the bank must be able
|
|
378
|
-
// to honor its own after-cutoff value date within its forward window. Two
|
|
379
|
-
// independent facts, reported independently.
|
|
380
|
-
if (beforeDays !== null && afterDays !== null && afterDays < beforeDays) {
|
|
381
|
-
findings.push({
|
|
382
|
-
code: "cutoff_window_regression",
|
|
383
|
-
message: `after-cutoff value date D+${afterDays} is earlier than ` +
|
|
384
|
-
`before-cutoff D+${beforeDays}; missing a cutoff cannot move value ` +
|
|
385
|
-
"earlier",
|
|
386
|
-
path: "profile.windows.paymentCutoff",
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
if (afterDays !== null && profile.windows.valueDateMaxDaysAhead < afterDays) {
|
|
390
|
-
findings.push({
|
|
391
|
-
code: "cutoff_window_regression",
|
|
392
|
-
message: `the forward window ends ${profile.windows.valueDateMaxDaysAhead} ` +
|
|
393
|
-
`days out but the after-cutoff value date needs D+${afterDays}`,
|
|
394
|
-
path: "profile.windows.valueDateMaxDaysAhead",
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
// Every declared statement format needs its fetch window, and an
|
|
398
|
-
// end-of-day format pages over history, so its window needs the paging
|
|
399
|
-
// facts (how far back, how many days per request).
|
|
400
|
-
for (const format of profile.statementFormats) {
|
|
401
|
-
const window = profile.windows.statements[format];
|
|
402
|
-
if (!window) {
|
|
403
|
-
findings.push({
|
|
404
|
-
code: "statement_window_missing",
|
|
405
|
-
message: `statement format ${format} is declared but has no fetch window, ` +
|
|
406
|
-
"so the runtime cannot schedule its retrieval",
|
|
407
|
-
path: `profile.windows.statements.${format}`,
|
|
408
|
-
});
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
// TS requires availability, but a plain-JS consumer can send a window
|
|
412
|
-
// without one or with a spelling outside the enum, and every rule below
|
|
413
|
-
// branches on it -- an unrecognized value must not slide through green.
|
|
414
|
-
if (!statementAvailabilities.includes(window.availability)) {
|
|
415
|
-
findings.push({
|
|
416
|
-
code: "statement_window_incomplete",
|
|
417
|
-
message: `statement format ${format} declares availability ` +
|
|
418
|
-
`"${String(window.availability)}", which is not one of ` +
|
|
419
|
-
`${statementAvailabilities.join(", ")}, so the runtime cannot ` +
|
|
420
|
-
"schedule its retrieval",
|
|
421
|
-
path: `profile.windows.statements.${format}.availability`,
|
|
422
|
-
});
|
|
423
|
-
continue;
|
|
424
|
-
}
|
|
425
|
-
if (window.availability === "T-1" &&
|
|
426
|
-
(!isPositiveInteger(window.lookbackDays) ||
|
|
427
|
-
!isPositiveInteger(window.statementDaysPerRequest))) {
|
|
428
|
-
findings.push({
|
|
429
|
-
code: "statement_window_incomplete",
|
|
430
|
-
message: `end-of-day format ${format} pages over history but declares no ` +
|
|
431
|
-
"positive lookbackDays and statementDaysPerRequest",
|
|
432
|
-
path: `profile.windows.statements.${format}`,
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
return findings;
|
|
437
|
-
}
|
|
438
|
-
function bindingFindings(adapter) {
|
|
439
|
-
const findings = [];
|
|
440
|
-
const bindings = adapter.bindings;
|
|
441
|
-
const claimed = new Set();
|
|
442
|
-
const require = (kind, path) => {
|
|
443
|
-
if (bindings[kind] !== undefined || claimed.has(kind))
|
|
444
|
-
return;
|
|
445
|
-
claimed.add(kind);
|
|
446
|
-
// Provider facts commit onto platform resources through a declared match
|
|
447
|
-
// mode; a resource the adapter settles without one has no lawful way to
|
|
448
|
-
// bind the bank's answer.
|
|
449
|
-
findings.push({
|
|
450
|
-
code: "resource_binding_missing",
|
|
451
|
-
message: `resource kind ${kind} is settled by this adapter but declares no ` +
|
|
452
|
-
"binding mode, so provider facts have no lawful way to commit",
|
|
453
|
-
path,
|
|
454
|
-
});
|
|
455
|
-
};
|
|
456
|
-
for (const binding of Object.values(adapter.operationMap)) {
|
|
457
|
-
if (!binding)
|
|
458
|
-
continue;
|
|
459
|
-
require(binding.resourceKind, `bindings.${binding.resourceKind}`);
|
|
460
|
-
}
|
|
461
|
-
for (const plan of Object.values(adapter.webhookMap)) {
|
|
462
|
-
require(plan.resourceKind, `bindings.${plan.resourceKind}`);
|
|
463
|
-
}
|
|
464
|
-
return findings;
|
|
465
|
-
}
|
|
466
|
-
/** Parse `D+<days>`; null for anything that is not a whole-day offset. */
|
|
467
|
-
function valueDateOffsetDays(offset) {
|
|
468
|
-
const match = /^D\+(0|[1-9]\d*)$/.exec(offset);
|
|
469
|
-
return match ? Number(match[1]) : null;
|
|
470
|
-
}
|
|
471
|
-
function isPositiveInteger(value) {
|
|
472
|
-
return value !== undefined && Number.isInteger(value) && value > 0;
|
|
473
|
-
}
|
|
474
|
-
/** The type says string, but a plain-JS consumer can send anything. */
|
|
475
|
-
function isValidTimeZone(timeZone) {
|
|
476
|
-
if (typeof timeZone !== "string")
|
|
477
|
-
return false;
|
|
478
|
-
try {
|
|
479
|
-
new Intl.DateTimeFormat("en", { timeZone });
|
|
480
|
-
return true;
|
|
481
|
-
}
|
|
482
|
-
catch {
|
|
483
|
-
return false;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
//# sourceMappingURL=conformance.js.map
|
package/dist/conformance.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"conformance.js","sourceRoot":"","sources":["../src/conformance.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# Meridian Bank
|
|
2
|
-
|
|
3
|
-
A complete, certified ADL adapter for a bank that does not exist. Every endpoint,
|
|
4
|
-
status token, charge narrative, and account number here is invented, and the
|
|
5
|
-
domain `meridian-bank.example` is reserved by RFC 2606 so nothing in this
|
|
6
|
-
directory can point at a real institution.
|
|
7
|
-
|
|
8
|
-
It exists for two reasons: it is the thing to copy when you write your own,
|
|
9
|
-
and it is the proof that a third party can pass conformance with nothing but
|
|
10
|
-
the published package.
|
|
11
|
-
|
|
12
|
-
## What is here
|
|
13
|
-
|
|
14
|
-
| File | What it is |
|
|
15
|
-
| ----------------- | ------------------------------------------------------------------------------------------- |
|
|
16
|
-
| `index.ts` | The adapter: vocabulary, profile, config, two capabilities. Read it top to bottom. |
|
|
17
|
-
| `fixtures/*.json` | Meridian's own bytes: two command responses, two status enquiries, a statement, a callback. |
|
|
18
|
-
| `example.spec.ts` | Conformance, plus the checks that keep the declaration honest against the fixtures. |
|
|
19
|
-
|
|
20
|
-
## Meridian, briefly
|
|
21
|
-
|
|
22
|
-
An imaginary European bank with a REST API:
|
|
23
|
-
|
|
24
|
-
- **Errors ride the HTTP status.** A rejected payment is a 4xx with prose in
|
|
25
|
-
the body, which is `envelope: "http_status_error_body"`. A bank that answered
|
|
26
|
-
200 with a rejection token in the body would be `http_200_body_status`
|
|
27
|
-
instead, and the class is declared per operation because real providers are
|
|
28
|
-
inconsistent across their own endpoints.
|
|
29
|
-
- **A payment is re-read by the reference Meridian minted**, so
|
|
30
|
-
`statusEnquiry.keys` is `provider_reference` and the path template carries
|
|
31
|
-
`{providerReference}`. That is the read-after-write: a submit whose response
|
|
32
|
-
we never saw is re-read, never resubmitted.
|
|
33
|
-
- **Incoming credits are learned from statements**, so the `bank_credit`
|
|
34
|
-
capability declares `egress: "none"` and records what observation saw.
|
|
35
|
-
- **Fees and tax post separately** as `MERIDIAN FEE ...` and `MERIDIAN TAX ...`
|
|
36
|
-
debits, which is why both narrative prefixes are declared: unrecognized, they
|
|
37
|
-
would land as unexplained statement lines.
|
|
38
|
-
- **The statement echoes our reference**, so `statementDebitReference` is
|
|
39
|
-
`customer_reference`. Reading Meridian's own entry id instead would make
|
|
40
|
-
every payout we instructed look like an unexplained debit.
|
|
41
|
-
|
|
42
|
-
## Run it
|
|
43
|
-
|
|
44
|
-
From the package root:
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
bun run conformance -- ./examples/meridian-bank
|
|
48
|
-
bun test examples/meridian-bank
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Copying it
|
|
52
|
-
|
|
53
|
-
1. Copy the directory and rename `meridian` throughout. The provider key is
|
|
54
|
-
lowercase and machine-shaped (`^[a-z][a-z0-9_.-]{0,79}$`).
|
|
55
|
-
2. Replace the vocabulary with the operations and resources your product
|
|
56
|
-
actually uses.
|
|
57
|
-
3. Replace every profile fact with what your bank does. Do not carry a
|
|
58
|
-
Meridian value across because it looked plausible: a fact you have not
|
|
59
|
-
established is declared absent, and conformance will tell you which ones
|
|
60
|
-
are still open.
|
|
61
|
-
4. Replace the fixtures with real captures from your provider's sandbox, and
|
|
62
|
-
keep `example.spec.ts` pointed at them. A declaration that certifies but
|
|
63
|
-
cannot read its own sandbox capture is not finished.
|
|
64
|
-
|
|
65
|
-
`../../docs/authoring-guide.md` explains every field.
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"kind": "enquiry",
|
|
3
|
-
"operation": "payout.submit",
|
|
4
|
-
"request": {
|
|
5
|
-
"method": "GET",
|
|
6
|
-
"path": "/v2/payments/MER-9F3K2"
|
|
7
|
-
},
|
|
8
|
-
"response": {
|
|
9
|
-
"httpStatus": 200,
|
|
10
|
-
"body": {
|
|
11
|
-
"providerReference": "MER-9F3K2",
|
|
12
|
-
"paymentReference": "HS-PAY-0007",
|
|
13
|
-
"status": "REPLAY_SETTLED",
|
|
14
|
-
"originalProviderReference": "MER-9F3K2",
|
|
15
|
-
"valueDate": "2026-03-03",
|
|
16
|
-
"settledAt": "2026-03-03T09:04:41Z"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"kind": "enquiry",
|
|
3
|
-
"operation": "payout.submit",
|
|
4
|
-
"request": {
|
|
5
|
-
"method": "GET",
|
|
6
|
-
"path": "/v2/payments/MER-9F3K2"
|
|
7
|
-
},
|
|
8
|
-
"response": {
|
|
9
|
-
"httpStatus": 200,
|
|
10
|
-
"body": {
|
|
11
|
-
"providerReference": "MER-9F3K2",
|
|
12
|
-
"paymentReference": "HS-PAY-0007",
|
|
13
|
-
"status": "SETTLED",
|
|
14
|
-
"valueDate": "2026-03-03",
|
|
15
|
-
"settledAt": "2026-03-03T09:04:41Z",
|
|
16
|
-
"chargeAmount": "1.50"
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|