@hyperscale0/adl 1.0.0-beta.1 → 1.0.0-beta.11
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 +44 -1
- package/LICENSE.md +356 -0
- package/NOTICE.md +8 -0
- package/README.md +28 -144
- package/SECURITY.md +0 -4
- package/conformance/cases.json +50 -770
- package/conformance/subject-adapter.ts +26 -0
- package/dist/boundary-fixture.d.ts +21 -0
- package/dist/boundary-fixture.d.ts.map +1 -0
- package/dist/boundary-fixture.js +31 -0
- package/dist/boundary-fixture.js.map +1 -0
- package/dist/index.d.ts +66 -195
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +97 -105
- package/dist/index.js.map +1 -1
- package/dist/vocabulary.d.ts +5 -90
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +12 -135
- package/dist/vocabulary.js.map +1 -1
- package/docs/assets/adl.svg +11 -0
- package/docs/assets/brand/adl-horizontal-white.svg +1 -0
- package/docs/assets/brand/adl-horizontal.svg +1 -0
- package/docs/assets/brand/adl-stacked-white.svg +1 -0
- package/docs/assets/brand/adl-stacked.svg +1 -0
- package/docs/assets/brand/manifest.json +30 -0
- package/docs/authoring-guide.md +33 -417
- package/package.json +19 -18
- package/LICENSE +0 -661
- package/LICENSING.md +0 -51
- package/TRADEMARKS.md +0 -30
- 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/example.spec.ts +0 -162
- 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/spec/manifest.schema.json +0 -757
package/conformance/cases.json
CHANGED
|
@@ -1,835 +1,115 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "
|
|
3
|
-
"base": "
|
|
4
|
-
"mutations": "`set` writes a value at a path (array of keys, because operation names contain dots) and runs before `delete`, which removes a path.",
|
|
2
|
+
"description": "Generic declaration refusals. Boundary evidence shape checks are in conformance.spec.ts.",
|
|
3
|
+
"base": "../src/boundary-fixture.ts",
|
|
5
4
|
"cases": [
|
|
6
|
-
{
|
|
7
|
-
"id": "base-certifies",
|
|
8
|
-
"title": "the base declaration certifies with no findings",
|
|
9
|
-
"why": "The corpus is worthless if the base is not clean: every case below reads as one broken fact against a green baseline.",
|
|
10
|
-
"mutate": {},
|
|
11
|
-
"load": "accepts",
|
|
12
|
-
"conformance": [],
|
|
13
|
-
"schema": "accepts"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"id": "profile-missing",
|
|
17
|
-
"title": "no partner-bank profile at all",
|
|
18
|
-
"why": "Without the profile none of the bank facts exist, so the runtime has nothing to poll, classify, or reconcile from.",
|
|
19
|
-
"mutate": { "delete": [["profile"]] },
|
|
20
|
-
"load": "accepts",
|
|
21
|
-
"conformance": ["partner_bank_profile_missing"],
|
|
22
|
-
"schema": "accepts"
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"id": "profile-missing-still-checks-the-declaration",
|
|
26
|
-
"title": "a missing profile leads, and the declaration checks still run",
|
|
27
|
-
"why": "One run must report everything it can see; a missing profile is not a licence to stop reading the rest.",
|
|
28
|
-
"mutate": {
|
|
29
|
-
"set": [{ "path": ["bindings"], "value": {} }],
|
|
30
|
-
"delete": [["profile"]]
|
|
31
|
-
},
|
|
32
|
-
"load": "accepts",
|
|
33
|
-
"conformance": [
|
|
34
|
-
"partner_bank_profile_missing",
|
|
35
|
-
"resource_binding_missing"
|
|
36
|
-
],
|
|
37
|
-
"schema": "accepts"
|
|
38
|
-
},
|
|
39
5
|
{
|
|
40
6
|
"id": "operation-map-empty",
|
|
41
|
-
"title": "
|
|
42
|
-
"why": "
|
|
43
|
-
"mutate": { "set": [{ "path": ["operationMap"], "value": {} }] },
|
|
44
|
-
"load": "accepts",
|
|
45
|
-
"conformance": ["operation_map_empty", "required_operation_missing"],
|
|
46
|
-
"schema": "accepts"
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
"id": "envelope-unclassified",
|
|
50
|
-
"title": "a live command with no response envelope class",
|
|
51
|
-
"why": "Without a class the runtime cannot tell acceptance from rejection, and a rejection read as silence strands money in flight.",
|
|
52
|
-
"mutate": {
|
|
53
|
-
"delete": [["operationMap", "payout.submit", "envelope"]]
|
|
54
|
-
},
|
|
55
|
-
"load": "accepts",
|
|
56
|
-
"conformance": ["response_envelope_unclassified"],
|
|
57
|
-
"schema": "accepts"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"id": "envelope-unconfirmed",
|
|
61
|
-
"title": "the envelope class is declared unconfirmed",
|
|
62
|
-
"why": "`unconfirmed_until_live` is the honest authoring state for a provider that publishes no error sample, and certification is the licence to serve live.",
|
|
63
|
-
"mutate": {
|
|
64
|
-
"set": [
|
|
65
|
-
{
|
|
66
|
-
"path": ["operationMap", "payout.submit", "envelope"],
|
|
67
|
-
"value": "unconfirmed_until_live"
|
|
68
|
-
}
|
|
69
|
-
]
|
|
70
|
-
},
|
|
71
|
-
"load": "accepts",
|
|
72
|
-
"conformance": ["response_envelope_unconfirmed"],
|
|
73
|
-
"schema": "accepts"
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"id": "status-enquiry-missing",
|
|
77
|
-
"title": "a tenant-initiated REST command with no status enquiry",
|
|
78
|
-
"why": "A submit whose response is lost can then only be resolved by guessing or resubmitting, and resubmitting pays twice.",
|
|
79
|
-
"mutate": {
|
|
80
|
-
"delete": [["operationMap", "payout.submit", "statusEnquiry"]]
|
|
81
|
-
},
|
|
82
|
-
"load": "accepts",
|
|
83
|
-
"conformance": ["status_enquiry_missing"],
|
|
84
|
-
"schema": "accepts"
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
"id": "status-enquiry-exempt-under-sync-clearance",
|
|
88
|
-
"title": "sync clearance carries its truth in band",
|
|
89
|
-
"why": "The exemption is earned by the egress mode: a synchronous clearance answers in the same exchange, so there is no lost response to re-read.",
|
|
90
|
-
"mutate": {
|
|
91
|
-
"set": [{ "path": ["egress"], "value": "sync_clearance" }],
|
|
92
|
-
"delete": [["operationMap", "payout.submit", "statusEnquiry"]]
|
|
93
|
-
},
|
|
94
|
-
"load": "accepts",
|
|
95
|
-
"conformance": [],
|
|
96
|
-
"schema": "accepts"
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
"id": "egress-incoherent-batch-reconcile",
|
|
100
|
-
"title": "a tenant-initiated command on a file-reconciling adapter",
|
|
101
|
-
"why": "batch_reconcile performs no per-command outbound call, so the binding promises a call the runtime cannot make.",
|
|
102
|
-
"mutate": {
|
|
103
|
-
"set": [{ "path": ["egress"], "value": "batch_reconcile" }],
|
|
104
|
-
"delete": [["operationMap", "payout.submit", "statusEnquiry"]]
|
|
105
|
-
},
|
|
106
|
-
"load": "accepts",
|
|
107
|
-
"conformance": ["egress_direction_incoherent"],
|
|
108
|
-
"schema": "accepts"
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
"id": "egress-incoherent-none",
|
|
112
|
-
"title": "a tenant-initiated command on an observation-only adapter",
|
|
113
|
-
"why": "Same rule, the other silent egress mode: `none` sends the provider nothing at all.",
|
|
114
|
-
"mutate": {
|
|
115
|
-
"set": [{ "path": ["egress"], "value": "none" }],
|
|
116
|
-
"delete": [["operationMap", "payout.submit", "statusEnquiry"]]
|
|
117
|
-
},
|
|
118
|
-
"load": "accepts",
|
|
119
|
-
"conformance": ["egress_direction_incoherent"],
|
|
120
|
-
"schema": "accepts"
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
"id": "status-enquiry-without-rest-egress",
|
|
124
|
-
"title": "an enquiry plan an adapter has no transport to execute",
|
|
125
|
-
"why": "An enquiry path on an adapter that performs no REST egress is a plan the runtime can never run.",
|
|
126
|
-
"mutate": {
|
|
127
|
-
"set": [
|
|
128
|
-
{ "path": ["egress"], "value": "none" },
|
|
129
|
-
{
|
|
130
|
-
"path": ["operationMap", "payout.submit", "direction"],
|
|
131
|
-
"value": "system_settlement"
|
|
132
|
-
}
|
|
133
|
-
]
|
|
134
|
-
},
|
|
135
|
-
"load": "accepts",
|
|
136
|
-
"conformance": ["status_enquiry_without_rest_egress"],
|
|
137
|
-
"schema": "accepts"
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
"id": "lifecycle-terminals-incomplete",
|
|
141
|
-
"title": "no spelling maps to returned",
|
|
142
|
-
"why": "A payment ending in a return would be unclassifiable, so the payout polls forever.",
|
|
143
|
-
"mutate": {
|
|
144
|
-
"set": [
|
|
145
|
-
{
|
|
146
|
-
"path": ["profile", "lifecycleVocabulary"],
|
|
147
|
-
"value": {
|
|
148
|
-
"QUEUED": "received",
|
|
149
|
-
"AUTHORISED": "accepted",
|
|
150
|
-
"SETTLED": "processed",
|
|
151
|
-
"DECLINED": "failed",
|
|
152
|
-
"REPLAY_SETTLED": "duplicate_original_succeeded",
|
|
153
|
-
"REPLAY_DECLINED": "duplicate_original_rejected"
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
]
|
|
157
|
-
},
|
|
158
|
-
"load": "accepts",
|
|
159
|
-
"conformance": ["lifecycle_terminals_incomplete"],
|
|
160
|
-
"schema": "accepts"
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
"id": "lifecycle-intake-missing",
|
|
164
|
-
"title": "no spelling maps to received or accepted",
|
|
165
|
-
"why": "An acknowledgement proves the bank took the instruction, never that money moved; with no intake state every ack would have to be graded terminal or dropped.",
|
|
166
|
-
"mutate": {
|
|
167
|
-
"set": [
|
|
168
|
-
{
|
|
169
|
-
"path": ["profile", "lifecycleVocabulary"],
|
|
170
|
-
"value": {
|
|
171
|
-
"SETTLED": "processed",
|
|
172
|
-
"DECLINED": "failed",
|
|
173
|
-
"RECALLED": "returned",
|
|
174
|
-
"REPLAY_SETTLED": "duplicate_original_succeeded",
|
|
175
|
-
"REPLAY_DECLINED": "duplicate_original_rejected"
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
]
|
|
179
|
-
},
|
|
180
|
-
"load": "accepts",
|
|
181
|
-
"conformance": ["lifecycle_intake_missing"],
|
|
182
|
-
"schema": "accepts"
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
"id": "lifecycle-spelling-blank",
|
|
186
|
-
"title": "a spelling that trims to nothing",
|
|
187
|
-
"why": "Runtimes match after trimming and upper-casing, so an empty token can never match anything the bank sends.",
|
|
188
|
-
"mutate": {
|
|
189
|
-
"set": [
|
|
190
|
-
{
|
|
191
|
-
"path": ["profile", "lifecycleVocabulary"],
|
|
192
|
-
"value": {
|
|
193
|
-
"QUEUED": "received",
|
|
194
|
-
"IN_REVIEW": "received",
|
|
195
|
-
"AUTHORISED": "accepted",
|
|
196
|
-
"SETTLED": "processed",
|
|
197
|
-
"DECLINED": "failed",
|
|
198
|
-
"RECALLED": "returned",
|
|
199
|
-
"REPLAY_SETTLED": "duplicate_original_succeeded",
|
|
200
|
-
"REPLAY_DECLINED": "duplicate_original_rejected",
|
|
201
|
-
" ": "failed"
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
]
|
|
205
|
-
},
|
|
206
|
-
"load": "accepts",
|
|
207
|
-
"conformance": ["lifecycle_spelling_blank"],
|
|
208
|
-
"schema": "accepts"
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
"id": "lifecycle-spelling-conflict",
|
|
212
|
-
"title": "two spellings that normalize to one token and disagree",
|
|
213
|
-
"why": "Whichever the runtime matched first would decide the payment's fate, which makes classification order-dependent.",
|
|
214
|
-
"mutate": {
|
|
215
|
-
"set": [
|
|
216
|
-
{
|
|
217
|
-
"path": ["profile", "lifecycleVocabulary"],
|
|
218
|
-
"value": {
|
|
219
|
-
"QUEUED": "received",
|
|
220
|
-
"AUTHORISED": "accepted",
|
|
221
|
-
"SETTLED": "processed",
|
|
222
|
-
"DECLINED": "failed",
|
|
223
|
-
"RECALLED": "returned",
|
|
224
|
-
"REPLAY_SETTLED": "duplicate_original_succeeded",
|
|
225
|
-
"replay_settled ": "duplicate_original_rejected",
|
|
226
|
-
"REPLAY_DECLINED": "duplicate_original_rejected"
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
]
|
|
230
|
-
},
|
|
231
|
-
"load": "accepts",
|
|
232
|
-
"conformance": ["lifecycle_spelling_conflict"],
|
|
233
|
-
"schema": "accepts"
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
"id": "duplicate-verdicts-unmapped",
|
|
237
|
-
"title": "the replay verdicts have no spelling",
|
|
238
|
-
"why": "The dedupe spine is reference-based, so a crash-recovery resubmission comes back as a duplicate verdict; unclassified, it reads as a fresh failure.",
|
|
239
|
-
"mutate": {
|
|
240
|
-
"set": [
|
|
241
|
-
{
|
|
242
|
-
"path": ["profile", "lifecycleVocabulary"],
|
|
243
|
-
"value": {
|
|
244
|
-
"QUEUED": "received",
|
|
245
|
-
"AUTHORISED": "accepted",
|
|
246
|
-
"SETTLED": "processed",
|
|
247
|
-
"DECLINED": "failed",
|
|
248
|
-
"RECALLED": "returned"
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
]
|
|
252
|
-
},
|
|
253
|
-
"load": "accepts",
|
|
254
|
-
"conformance": ["duplicate_verdicts_unmapped"],
|
|
255
|
-
"schema": "accepts"
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
"id": "statement-formats-empty",
|
|
259
|
-
"title": "no statement format is declared",
|
|
260
|
-
"why": "Statements are bank truth; without one, reconciliation has nothing to prove the ledger against.",
|
|
261
|
-
"mutate": {
|
|
262
|
-
"set": [{ "path": ["profile", "statementFormats"], "value": [] }]
|
|
263
|
-
},
|
|
264
|
-
"load": "accepts",
|
|
265
|
-
"conformance": ["statement_formats_empty"],
|
|
266
|
-
"schema": "accepts"
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
"id": "statement-debit-reference-unestablished",
|
|
270
|
-
"title": "which reference comes back on a debit is unknown",
|
|
271
|
-
"why": "Pick the wrong one and every payout the platform instructed reads as an unexplained debit.",
|
|
272
|
-
"mutate": { "delete": [["profile", "statementDebitReference"]] },
|
|
273
|
-
"load": "accepts",
|
|
274
|
-
"conformance": ["statement_debit_reference_unestablished"],
|
|
275
|
-
"schema": "accepts"
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
"id": "statement-window-missing",
|
|
279
|
-
"title": "a declared format with no fetch window",
|
|
280
|
-
"why": "The runtime cannot schedule a retrieval it has no window for.",
|
|
281
|
-
"mutate": {
|
|
282
|
-
"set": [
|
|
283
|
-
{
|
|
284
|
-
"path": ["profile", "statementFormats"],
|
|
285
|
-
"value": ["camt_053", "camt_052", "mt940"]
|
|
286
|
-
}
|
|
287
|
-
]
|
|
288
|
-
},
|
|
289
|
-
"load": "accepts",
|
|
290
|
-
"conformance": ["statement_window_missing"],
|
|
291
|
-
"schema": "accepts"
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
"id": "statement-window-without-paging",
|
|
295
|
-
"title": "an end-of-day format with no paging facts",
|
|
296
|
-
"why": "T-1 pages over history, so how far back the archive goes and how many days one request covers are both required.",
|
|
297
|
-
"mutate": {
|
|
298
|
-
"set": [
|
|
299
|
-
{
|
|
300
|
-
"path": ["profile", "windows", "statements", "camt_053"],
|
|
301
|
-
"value": { "availability": "T-1" }
|
|
302
|
-
}
|
|
303
|
-
]
|
|
304
|
-
},
|
|
305
|
-
"load": "accepts",
|
|
306
|
-
"conformance": ["statement_window_incomplete"],
|
|
307
|
-
"schema": "accepts"
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
"id": "statement-window-availability-unrecognized",
|
|
311
|
-
"title": "an availability outside the vocabulary",
|
|
312
|
-
"why": "Every window rule branches on availability, so an unrecognized value must not slide through green.",
|
|
313
|
-
"mutate": {
|
|
314
|
-
"set": [
|
|
315
|
-
{
|
|
316
|
-
"path": ["profile", "windows", "statements", "camt_053"],
|
|
317
|
-
"value": { "availability": "whenever", "lookbackDays": 5 }
|
|
318
|
-
}
|
|
319
|
-
]
|
|
320
|
-
},
|
|
321
|
-
"load": {
|
|
322
|
-
"throws": "windows.statements.camt_053.availability must be one of T-1, today_only"
|
|
323
|
-
},
|
|
324
|
-
"conformance": ["statement_window_incomplete"],
|
|
325
|
-
"schema": "rejects"
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
"id": "statement-window-availability-missing",
|
|
329
|
-
"title": "a truthy window with no availability at all",
|
|
330
|
-
"why": "TypeScript requires the field; a plain-JSON manifest can omit it, and an availability-less window is unschedulable.",
|
|
331
|
-
"mutate": {
|
|
332
|
-
"set": [
|
|
333
|
-
{
|
|
334
|
-
"path": ["profile", "windows", "statements", "camt_053"],
|
|
335
|
-
"value": { "lookbackDays": 5 }
|
|
336
|
-
}
|
|
337
|
-
]
|
|
338
|
-
},
|
|
339
|
-
"load": {
|
|
340
|
-
"throws": "windows.statements.camt_053.availability must be one of T-1, today_only"
|
|
341
|
-
},
|
|
342
|
-
"conformance": ["statement_window_incomplete"],
|
|
343
|
-
"schema": "rejects"
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
"id": "statement-window-lookback-not-positive",
|
|
347
|
-
"title": "a lookback of zero days",
|
|
348
|
-
"why": "A window that pages back nothing cannot fetch the history it claims to.",
|
|
349
|
-
"mutate": {
|
|
350
|
-
"set": [
|
|
351
|
-
{
|
|
352
|
-
"path": ["profile", "windows", "statements", "camt_053"],
|
|
353
|
-
"value": {
|
|
354
|
-
"availability": "T-1",
|
|
355
|
-
"lookbackDays": 0,
|
|
356
|
-
"statementDaysPerRequest": 7
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
]
|
|
360
|
-
},
|
|
361
|
-
"load": {
|
|
362
|
-
"throws": "windows.statements.camt_053.lookbackDays must be a positive integer"
|
|
363
|
-
},
|
|
364
|
-
"conformance": ["statement_window_incomplete"],
|
|
365
|
-
"schema": "rejects"
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
"id": "charges-unestablished",
|
|
369
|
-
"title": "the bank's charge billing is unmodeled",
|
|
370
|
-
"why": "The bank's own charge debits would land as unexplained statement lines.",
|
|
371
|
-
"mutate": { "delete": [["profile", "charges"]] },
|
|
372
|
-
"load": "accepts",
|
|
373
|
-
"conformance": ["charges_unestablished"],
|
|
374
|
-
"schema": "accepts"
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
"id": "charge-narrative-unestablished",
|
|
378
|
-
"title": "post-hoc charges with no wording to recognize them by",
|
|
379
|
-
"why": "A post-hoc charge arrives as a bank-initiated debit recognizable only by its narrative.",
|
|
380
|
-
"mutate": {
|
|
381
|
-
"set": [
|
|
382
|
-
{
|
|
383
|
-
"path": ["profile", "charges"],
|
|
384
|
-
"value": {
|
|
385
|
-
"narratives": { "charge": [], "vat": ["MERIDIAN TAX"] },
|
|
386
|
-
"posting": "post_hoc",
|
|
387
|
-
"vat": "separate_line"
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
]
|
|
391
|
-
},
|
|
392
|
-
"load": "accepts",
|
|
393
|
-
"conformance": ["charge_narrative_unestablished"],
|
|
394
|
-
"schema": "accepts"
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
"id": "vat-narrative-unestablished",
|
|
398
|
-
"title": "a separate tax line with no wording to recognize it by",
|
|
399
|
-
"why": "A separate VAT line is its own debit with its own wording.",
|
|
400
|
-
"mutate": {
|
|
401
|
-
"set": [
|
|
402
|
-
{
|
|
403
|
-
"path": ["profile", "charges"],
|
|
404
|
-
"value": {
|
|
405
|
-
"narratives": { "charge": ["MERIDIAN FEE"], "vat": [] },
|
|
406
|
-
"posting": "post_hoc",
|
|
407
|
-
"vat": "separate_line"
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
]
|
|
411
|
-
},
|
|
412
|
-
"load": "accepts",
|
|
413
|
-
"conformance": ["vat_narrative_unestablished"],
|
|
414
|
-
"schema": "accepts"
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
"id": "charges-netted-need-no-narrative",
|
|
418
|
-
"title": "charges netted at instruction, tax folded in",
|
|
419
|
-
"why": "Nothing posts separately, so there is no debit to recognize and no narrative to declare.",
|
|
420
|
-
"mutate": {
|
|
421
|
-
"set": [
|
|
422
|
-
{
|
|
423
|
-
"path": ["profile", "charges"],
|
|
424
|
-
"value": {
|
|
425
|
-
"posting": "at_instruction",
|
|
426
|
-
"vat": "included_in_charge"
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
]
|
|
430
|
-
},
|
|
431
|
-
"load": "accepts",
|
|
432
|
-
"conformance": [],
|
|
433
|
-
"schema": "accepts"
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
"id": "value-date-offset-fractional",
|
|
437
|
-
"title": "a value date offset of a day and a half",
|
|
438
|
-
"why": "Value dates are whole banking days; a fraction would be NaN in every comparison that follows.",
|
|
439
|
-
"mutate": {
|
|
440
|
-
"set": [
|
|
441
|
-
{
|
|
442
|
-
"path": [
|
|
443
|
-
"profile",
|
|
444
|
-
"windows",
|
|
445
|
-
"paymentCutoff",
|
|
446
|
-
"afterCutoffValueDate"
|
|
447
|
-
],
|
|
448
|
-
"value": "D+1.5"
|
|
449
|
-
}
|
|
450
|
-
]
|
|
451
|
-
},
|
|
452
|
-
"load": "accepts",
|
|
453
|
-
"conformance": ["value_date_offset_invalid"],
|
|
454
|
-
"schema": "rejects",
|
|
455
|
-
"note": "The schema encodes the D+<days> shape as a pattern; registry load does not read the offsets at all, so conformance is the gate that catches this in TypeScript."
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
"id": "value-date-offset-wrong-prefix",
|
|
459
|
-
"title": "a T+1 offset where D+ is the vocabulary",
|
|
460
|
-
"why": "The template literal type stops this in TypeScript and nothing stops it in a JSON manifest.",
|
|
461
|
-
"mutate": {
|
|
462
|
-
"set": [
|
|
463
|
-
{
|
|
464
|
-
"path": [
|
|
465
|
-
"profile",
|
|
466
|
-
"windows",
|
|
467
|
-
"paymentCutoff",
|
|
468
|
-
"beforeCutoffValueDate"
|
|
469
|
-
],
|
|
470
|
-
"value": "T+1"
|
|
471
|
-
}
|
|
472
|
-
]
|
|
473
|
-
},
|
|
474
|
-
"load": "accepts",
|
|
475
|
-
"conformance": ["value_date_offset_invalid"],
|
|
476
|
-
"schema": "rejects",
|
|
477
|
-
"note": "Same split as value-date-offset-fractional: pattern in the schema, rule in conformance, silence at registry load."
|
|
478
|
-
},
|
|
479
|
-
{
|
|
480
|
-
"id": "cutoff-timezone-invalid",
|
|
481
|
-
"title": "a cutoff read on a zone that does not exist",
|
|
482
|
-
"why": "The cutoff is the bank's wall clock; an unreadable zone means every value date is computed somewhere else.",
|
|
483
|
-
"mutate": {
|
|
484
|
-
"set": [
|
|
485
|
-
{
|
|
486
|
-
"path": ["profile", "windows", "paymentCutoff", "timeZone"],
|
|
487
|
-
"value": "Mars/Phobos"
|
|
488
|
-
}
|
|
489
|
-
]
|
|
490
|
-
},
|
|
491
|
-
"load": {
|
|
492
|
-
"throws": "windows.paymentCutoff.timeZone must be a valid IANA time zone"
|
|
493
|
-
},
|
|
494
|
-
"conformance": ["cutoff_clock_invalid"],
|
|
495
|
-
"schema": "accepts",
|
|
496
|
-
"note": "JSON Schema cannot check an IANA zone name; the schema only requires a string. Both runtime gates resolve it through Intl.DateTimeFormat."
|
|
497
|
-
},
|
|
498
|
-
{
|
|
499
|
-
"id": "cutoff-timezone-missing",
|
|
500
|
-
"title": "no time zone at all",
|
|
501
|
-
"why": "Intl.DateTimeFormat reads an undefined zone as the host's, so absence would silently schedule the cutoff on the platform's clock.",
|
|
502
|
-
"mutate": {
|
|
503
|
-
"delete": [["profile", "windows", "paymentCutoff", "timeZone"]]
|
|
504
|
-
},
|
|
505
|
-
"load": {
|
|
506
|
-
"throws": "windows.paymentCutoff.timeZone must be a valid IANA time zone"
|
|
507
|
-
},
|
|
508
|
-
"conformance": ["cutoff_clock_invalid"],
|
|
509
|
-
"schema": "rejects"
|
|
510
|
-
},
|
|
511
|
-
{
|
|
512
|
-
"id": "cutoff-time-not-a-clock",
|
|
513
|
-
"title": "a cutoff time written in words",
|
|
514
|
-
"why": "The runtime compares HH:MM; prose compares as never.",
|
|
515
|
-
"mutate": {
|
|
516
|
-
"set": [
|
|
517
|
-
{
|
|
518
|
-
"path": ["profile", "windows", "paymentCutoff", "time"],
|
|
519
|
-
"value": "half past nine"
|
|
520
|
-
}
|
|
521
|
-
]
|
|
522
|
-
},
|
|
523
|
-
"load": { "throws": "windows.paymentCutoff.time must be HH:MM" },
|
|
524
|
-
"conformance": ["cutoff_clock_invalid"],
|
|
525
|
-
"schema": "rejects"
|
|
526
|
-
},
|
|
527
|
-
{
|
|
528
|
-
"id": "cutoff-window-regressions",
|
|
529
|
-
"title": "missing the cutoff moves value earlier, and past the forward window",
|
|
530
|
-
"why": "Two independent facts, reported independently on one run: a later cutoff can only push value later, and the bank must be able to honor its own after-cutoff date.",
|
|
7
|
+
"title": "operation-map-empty",
|
|
8
|
+
"why": "operation-map-empty",
|
|
531
9
|
"mutate": {
|
|
532
10
|
"set": [
|
|
533
11
|
{
|
|
534
|
-
"path": [
|
|
535
|
-
|
|
536
|
-
"windows",
|
|
537
|
-
"paymentCutoff",
|
|
538
|
-
"beforeCutoffValueDate"
|
|
539
|
-
],
|
|
540
|
-
"value": "D+5"
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
"path": [
|
|
544
|
-
"profile",
|
|
545
|
-
"windows",
|
|
546
|
-
"paymentCutoff",
|
|
547
|
-
"afterCutoffValueDate"
|
|
548
|
-
],
|
|
549
|
-
"value": "D+2"
|
|
550
|
-
},
|
|
551
|
-
{
|
|
552
|
-
"path": ["profile", "windows", "valueDateMaxDaysAhead"],
|
|
553
|
-
"value": 1
|
|
12
|
+
"path": ["operationMap"],
|
|
13
|
+
"value": {}
|
|
554
14
|
}
|
|
555
15
|
]
|
|
556
16
|
},
|
|
557
17
|
"load": "accepts",
|
|
558
|
-
"conformance": ["
|
|
559
|
-
"schema": "accepts"
|
|
560
|
-
},
|
|
561
|
-
{
|
|
562
|
-
"id": "value-date-window-not-positive",
|
|
563
|
-
"title": "a forward window of zero days",
|
|
564
|
-
"why": "The bank must be able to honor the after-cutoff value date it declares.",
|
|
565
|
-
"mutate": {
|
|
566
|
-
"set": [
|
|
567
|
-
{
|
|
568
|
-
"path": ["profile", "windows", "valueDateMaxDaysAhead"],
|
|
569
|
-
"value": 0
|
|
570
|
-
}
|
|
571
|
-
]
|
|
572
|
-
},
|
|
573
|
-
"load": {
|
|
574
|
-
"throws": "windows.valueDateMaxDaysAhead must be a positive integer"
|
|
575
|
-
},
|
|
576
|
-
"conformance": ["cutoff_window_regression"],
|
|
577
|
-
"schema": "rejects"
|
|
18
|
+
"conformance": ["operation_map_empty"]
|
|
578
19
|
},
|
|
579
20
|
{
|
|
580
21
|
"id": "resource-binding-missing",
|
|
581
|
-
"title": "
|
|
582
|
-
"why": "
|
|
583
|
-
"mutate": { "set": [{ "path": ["bindings"], "value": {} }] },
|
|
584
|
-
"load": "accepts",
|
|
585
|
-
"conformance": ["resource_binding_missing"],
|
|
586
|
-
"schema": "accepts"
|
|
587
|
-
},
|
|
588
|
-
{
|
|
589
|
-
"id": "webhook-resource-binding-missing",
|
|
590
|
-
"title": "a webhook plan for a resource kind the adapter does not bind",
|
|
591
|
-
"why": "The webhook arm of the same rule: a plan names a resource, so that resource needs a binding mode too.",
|
|
22
|
+
"title": "resource-binding-missing",
|
|
23
|
+
"why": "resource-binding-missing",
|
|
592
24
|
"mutate": {
|
|
593
25
|
"set": [
|
|
594
26
|
{
|
|
595
|
-
"path": ["
|
|
596
|
-
"value": {
|
|
597
|
-
"deposit.received": {
|
|
598
|
-
"eventKind": "deposit.received",
|
|
599
|
-
"operationName": "deposit.record",
|
|
600
|
-
"requiredPayloadFields": ["amount"],
|
|
601
|
-
"optionalPayloadFields": [],
|
|
602
|
-
"requiredFieldGroups": [],
|
|
603
|
-
"resourceIdField": "depositId",
|
|
604
|
-
"resourceKind": "deposit",
|
|
605
|
-
"timestampField": "receivedAt"
|
|
606
|
-
}
|
|
607
|
-
}
|
|
27
|
+
"path": ["bindings"],
|
|
28
|
+
"value": {}
|
|
608
29
|
}
|
|
609
30
|
]
|
|
610
31
|
},
|
|
611
32
|
"load": "accepts",
|
|
612
|
-
"conformance": ["resource_binding_missing"]
|
|
613
|
-
"schema": "accepts"
|
|
614
|
-
},
|
|
615
|
-
{
|
|
616
|
-
"id": "lifecycle-vocabulary-empty",
|
|
617
|
-
"title": "a profile with no lifecycle vocabulary",
|
|
618
|
-
"why": "Nothing the bank says could be classified at all.",
|
|
619
|
-
"mutate": {
|
|
620
|
-
"set": [{ "path": ["profile", "lifecycleVocabulary"], "value": {} }]
|
|
621
|
-
},
|
|
622
|
-
"load": { "throws": "partner-bank profile has no lifecycle vocabulary" },
|
|
623
|
-
"conformance": [
|
|
624
|
-
"lifecycle_terminals_incomplete",
|
|
625
|
-
"lifecycle_intake_missing",
|
|
626
|
-
"duplicate_verdicts_unmapped"
|
|
627
|
-
],
|
|
628
|
-
"schema": "rejects"
|
|
33
|
+
"conformance": ["resource_binding_missing"]
|
|
629
34
|
},
|
|
630
35
|
{
|
|
631
|
-
"id": "
|
|
632
|
-
"title": "
|
|
633
|
-
"why": "
|
|
634
|
-
"mutate": {
|
|
635
|
-
"set": [
|
|
636
|
-
{ "path": ["profile", "idempotencySpine", "dedupeKeys"], "value": [] }
|
|
637
|
-
]
|
|
638
|
-
},
|
|
639
|
-
"load": { "throws": "idempotencySpine.dedupeKeys is empty" },
|
|
640
|
-
"conformance": [],
|
|
641
|
-
"schema": "rejects"
|
|
642
|
-
},
|
|
643
|
-
{
|
|
644
|
-
"id": "dedupe-keys-duplicated",
|
|
645
|
-
"title": "the same dedupe key listed twice",
|
|
646
|
-
"why": "The list widened from a fixed triple, so it now carries the guarantees the tuple used to give for free.",
|
|
36
|
+
"id": "operation-key-mismatch",
|
|
37
|
+
"title": "operation-key-mismatch",
|
|
38
|
+
"why": "operation-key-mismatch",
|
|
647
39
|
"mutate": {
|
|
648
40
|
"set": [
|
|
649
41
|
{
|
|
650
|
-
"path": ["
|
|
651
|
-
"value":
|
|
652
|
-
}
|
|
653
|
-
]
|
|
654
|
-
},
|
|
655
|
-
"load": { "throws": "idempotencySpine.dedupeKeys contains duplicates" },
|
|
656
|
-
"conformance": [],
|
|
657
|
-
"schema": "rejects"
|
|
658
|
-
},
|
|
659
|
-
{
|
|
660
|
-
"id": "spine-max-length-not-positive",
|
|
661
|
-
"title": "a reference length of zero",
|
|
662
|
-
"why": "A reference that cannot hold a character cannot carry the spine.",
|
|
663
|
-
"mutate": {
|
|
664
|
-
"set": [
|
|
665
|
-
{
|
|
666
|
-
"path": ["profile", "idempotencySpine", "instructionId"],
|
|
667
|
-
"value": { "maxLength": 0 }
|
|
42
|
+
"path": ["operationMap", "boundary.observe", "operation"],
|
|
43
|
+
"value": "wrong"
|
|
668
44
|
}
|
|
669
45
|
]
|
|
670
46
|
},
|
|
671
47
|
"load": {
|
|
672
|
-
"throws": "
|
|
673
|
-
},
|
|
674
|
-
"conformance": [],
|
|
675
|
-
"schema": "rejects"
|
|
676
|
-
},
|
|
677
|
-
{
|
|
678
|
-
"id": "notification-mechanisms-without-poll",
|
|
679
|
-
"title": "a bank we would only hear from by callback",
|
|
680
|
-
"why": "Poll-first is the portable floor: a callback is at best a hint that a poll is due, never a fact to post from.",
|
|
681
|
-
"mutate": {
|
|
682
|
-
"set": [
|
|
683
|
-
{
|
|
684
|
-
"path": ["profile", "notificationMechanisms"],
|
|
685
|
-
"value": ["webhook"]
|
|
686
|
-
}
|
|
687
|
-
]
|
|
48
|
+
"throws": "must match operation"
|
|
688
49
|
},
|
|
689
|
-
"
|
|
690
|
-
"conformance": [],
|
|
691
|
-
"schema": "rejects"
|
|
50
|
+
"conformance": ["declaration_invalid"]
|
|
692
51
|
},
|
|
693
52
|
{
|
|
694
|
-
"id": "
|
|
695
|
-
"title": "
|
|
696
|
-
"why": "
|
|
53
|
+
"id": "invalid-provider",
|
|
54
|
+
"title": "invalid-provider",
|
|
55
|
+
"why": "invalid-provider",
|
|
697
56
|
"mutate": {
|
|
698
57
|
"set": [
|
|
699
58
|
{
|
|
700
|
-
"path": ["
|
|
701
|
-
"value":
|
|
702
|
-
}
|
|
703
|
-
]
|
|
704
|
-
},
|
|
705
|
-
"load": { "throws": "config.credentialRef.name must not be blank" },
|
|
706
|
-
"conformance": [],
|
|
707
|
-
"schema": "rejects"
|
|
708
|
-
},
|
|
709
|
-
{
|
|
710
|
-
"id": "keychain-locator-blank",
|
|
711
|
-
"title": "a keychain locator missing its account",
|
|
712
|
-
"why": "Same rule on the other locator shape.",
|
|
713
|
-
"mutate": {
|
|
714
|
-
"set": [
|
|
715
|
-
{
|
|
716
|
-
"path": ["config", "credentialRef"],
|
|
717
|
-
"value": {
|
|
718
|
-
"source": "keychain",
|
|
719
|
-
"account": " ",
|
|
720
|
-
"service": "meridian"
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
]
|
|
724
|
-
},
|
|
725
|
-
"load": { "throws": "config.credentialRef.account must not be blank" },
|
|
726
|
-
"conformance": [],
|
|
727
|
-
"schema": "rejects"
|
|
728
|
-
},
|
|
729
|
-
{
|
|
730
|
-
"id": "base-url-not-absolute",
|
|
731
|
-
"title": "a base URL that is only a path",
|
|
732
|
-
"why": "The runtime joins operation paths onto this; a relative base resolves against nothing.",
|
|
733
|
-
"mutate": {
|
|
734
|
-
"set": [{ "path": ["config", "baseUrl"], "value": "/v2" }]
|
|
735
|
-
},
|
|
736
|
-
"load": { "throws": "config.baseUrl must be an absolute URL" },
|
|
737
|
-
"conformance": [],
|
|
738
|
-
"schema": "rejects"
|
|
739
|
-
},
|
|
740
|
-
{
|
|
741
|
-
"id": "provider-key-invalid",
|
|
742
|
-
"title": "a provider key with spaces and capitals",
|
|
743
|
-
"why": "The key is an identity used in logs, metrics, and the registry; it is lowercase and machine-shaped.",
|
|
744
|
-
"mutate": {
|
|
745
|
-
"set": [{ "path": ["provider"], "value": "Meridian Bank" }]
|
|
746
|
-
},
|
|
747
|
-
"load": { "throws": "invalid provider key Meridian Bank" },
|
|
748
|
-
"conformance": [],
|
|
749
|
-
"schema": "rejects"
|
|
750
|
-
},
|
|
751
|
-
{
|
|
752
|
-
"id": "operation-map-key-drift",
|
|
753
|
-
"title": "an operation map key that does not match its binding",
|
|
754
|
-
"why": "The key is how the runtime looks the binding up; drift means the runtime finds nothing under the name it holds.",
|
|
755
|
-
"mutate": {
|
|
756
|
-
"set": [
|
|
757
|
-
{
|
|
758
|
-
"path": ["operationMap"],
|
|
759
|
-
"value": {
|
|
760
|
-
"submit": {
|
|
761
|
-
"operation": "payout.submit",
|
|
762
|
-
"direction": "tenant_initiated",
|
|
763
|
-
"obligationKind": "payout_submission",
|
|
764
|
-
"resourceIdPath": "payoutId",
|
|
765
|
-
"resourceKind": "payout",
|
|
766
|
-
"envelope": "http_status_error_body",
|
|
767
|
-
"statusEnquiry": {
|
|
768
|
-
"keys": "provider_reference",
|
|
769
|
-
"pathTemplate": "/v2/payments/{providerReference}"
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
}
|
|
59
|
+
"path": ["provider"],
|
|
60
|
+
"value": "Invalid Adapter"
|
|
773
61
|
}
|
|
774
62
|
]
|
|
775
63
|
},
|
|
776
64
|
"load": {
|
|
777
|
-
"throws": "
|
|
65
|
+
"throws": "invalid provider key"
|
|
778
66
|
},
|
|
779
|
-
"conformance": ["
|
|
780
|
-
"schema": "accepts",
|
|
781
|
-
"note": "JSON Schema has no way to require a property's key to equal a value inside it, so this one is registry-load only."
|
|
67
|
+
"conformance": ["declaration_invalid"]
|
|
782
68
|
},
|
|
783
69
|
{
|
|
784
|
-
"id": "
|
|
785
|
-
"title": "
|
|
786
|
-
"why": "
|
|
70
|
+
"id": "optional-subject-requirement",
|
|
71
|
+
"title": "optional-subject-requirement",
|
|
72
|
+
"why": "optional-subject-requirement",
|
|
787
73
|
"mutate": {
|
|
788
74
|
"set": [
|
|
789
75
|
{
|
|
790
|
-
"path": ["
|
|
791
|
-
"value":
|
|
792
|
-
|
|
793
|
-
"
|
|
794
|
-
"
|
|
795
|
-
"
|
|
796
|
-
"optionalPayloadFields": [],
|
|
797
|
-
"requiredFieldGroups": [],
|
|
798
|
-
"resourceIdField": "payoutId",
|
|
799
|
-
"resourceKind": "payout",
|
|
800
|
-
"timestampField": "completedAt"
|
|
76
|
+
"path": ["operationMap", "boundary.observe", "subjectRequirements"],
|
|
77
|
+
"value": [
|
|
78
|
+
{
|
|
79
|
+
"name": "reference",
|
|
80
|
+
"type": "text",
|
|
81
|
+
"optional": true
|
|
801
82
|
}
|
|
802
|
-
|
|
83
|
+
]
|
|
803
84
|
}
|
|
804
85
|
]
|
|
805
86
|
},
|
|
806
87
|
"load": {
|
|
807
|
-
"throws": "
|
|
88
|
+
"throws": "action requirements cannot be optional"
|
|
808
89
|
},
|
|
809
|
-
"conformance": []
|
|
810
|
-
"schema": "accepts",
|
|
811
|
-
"note": "Key-equals-value again: expressible in the validator, not in the schema."
|
|
90
|
+
"conformance": ["declaration_invalid"]
|
|
812
91
|
},
|
|
813
92
|
{
|
|
814
|
-
"id": "
|
|
815
|
-
"title": "
|
|
816
|
-
"why": "
|
|
93
|
+
"id": "invalid-subject-field",
|
|
94
|
+
"title": "invalid-subject-field",
|
|
95
|
+
"why": "invalid-subject-field",
|
|
817
96
|
"mutate": {
|
|
818
97
|
"set": [
|
|
819
98
|
{
|
|
820
|
-
"path": ["operationMap", "
|
|
821
|
-
"value":
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
99
|
+
"path": ["operationMap", "boundary.observe", "subjectRequirements"],
|
|
100
|
+
"value": [
|
|
101
|
+
{
|
|
102
|
+
"name": "reference",
|
|
103
|
+
"type": "unknown"
|
|
104
|
+
}
|
|
105
|
+
]
|
|
825
106
|
}
|
|
826
107
|
]
|
|
827
108
|
},
|
|
828
109
|
"load": {
|
|
829
|
-
"throws": "
|
|
110
|
+
"throws": "does not match UDL"
|
|
830
111
|
},
|
|
831
|
-
"conformance": []
|
|
832
|
-
"schema": "rejects"
|
|
112
|
+
"conformance": ["declaration_invalid"]
|
|
833
113
|
}
|
|
834
114
|
]
|
|
835
115
|
}
|