@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
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** Synthetic conformance metadata, not a real provider requirement. */
|
|
2
|
+
import type { ProviderAdapter } from "../src/index.js";
|
|
3
|
+
|
|
4
|
+
export const subjectAdapter: ProviderAdapter = {
|
|
5
|
+
provider: "conformance_subject",
|
|
6
|
+
capability: "subject",
|
|
7
|
+
bindings: {
|
|
8
|
+
subject: "strict",
|
|
9
|
+
},
|
|
10
|
+
operationMap: {
|
|
11
|
+
"subject.check": {
|
|
12
|
+
operation: "subject.check",
|
|
13
|
+
direction: "tenant_initiated",
|
|
14
|
+
obligationKind: "subject_check",
|
|
15
|
+
resourceKind: "subject",
|
|
16
|
+
resourceIdPath: "subjectId",
|
|
17
|
+
subjectRequirements: [
|
|
18
|
+
{
|
|
19
|
+
name: "referenceCode",
|
|
20
|
+
type: "text",
|
|
21
|
+
minLength: 1,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type BoundaryInstruction, type BoundaryObservation, type BoundaryOutcome } from "./index.js";
|
|
2
|
+
/** The sandbox boundary adapter the platform registers. No transport, clock, accounts or inferred outcome. */
|
|
3
|
+
export declare const genericAdapter: {
|
|
4
|
+
readonly provider: "conformance_boundary";
|
|
5
|
+
readonly capability: "boundary";
|
|
6
|
+
readonly bindings: {
|
|
7
|
+
readonly instruction: "strict";
|
|
8
|
+
};
|
|
9
|
+
readonly operationMap: {
|
|
10
|
+
readonly "boundary.observe": {
|
|
11
|
+
readonly operation: "boundary.observe";
|
|
12
|
+
readonly direction: "system_settlement";
|
|
13
|
+
readonly obligationKind: "boundary_observation";
|
|
14
|
+
readonly resourceKind: "instruction";
|
|
15
|
+
readonly resourceIdPath: "instructionId";
|
|
16
|
+
readonly subjectRequirements: readonly [];
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export declare function observeBoundary(instruction: BoundaryInstruction, outcome: BoundaryOutcome, externalReference: string, observedAt: string): BoundaryObservation;
|
|
21
|
+
//# sourceMappingURL=boundary-fixture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundary-fixture.d.ts","sourceRoot":"","sources":["../src/boundary-fixture.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAEpB,8GAA8G;AAC9G,eAAO,MAAM,cAAc;uBAEb,sBAAsB;yBACpB,UAAU;;iBACV,WAAW,EAAE,QAAQ;;;iBAE/B,kBAAkB;qBAChB,SAAS,EAAE,kBAAkB;qBAC7B,SAAS,EAAE,mBAAmB;qBAC9B,cAAc,EAAE,sBAAsB;qBACtC,YAAY,EAAE,aAAa;qBAC3B,cAAc,EAAE,eAAe;qBAC/B,mBAAmB;;;CAIb,CAAC;AAEf,wBAAgB,eAAe,CAC7B,WAAW,EAAE,mBAAmB,EAChC,OAAO,EAAE,eAAe,EACxB,iBAAiB,EAAE,MAAM,EACzB,UAAU,EAAE,MAAM,GACjB,mBAAmB,CAUrB"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createProviderAdapterRegistry, boundaryObservationSchema, } from "./index.js";
|
|
2
|
+
/** The sandbox boundary adapter the platform registers. No transport, clock, accounts or inferred outcome. */
|
|
3
|
+
export const genericAdapter = createProviderAdapterRegistry()([
|
|
4
|
+
{
|
|
5
|
+
provider: "conformance_boundary",
|
|
6
|
+
capability: "boundary",
|
|
7
|
+
bindings: { instruction: "strict" },
|
|
8
|
+
operationMap: {
|
|
9
|
+
"boundary.observe": {
|
|
10
|
+
operation: "boundary.observe",
|
|
11
|
+
direction: "system_settlement",
|
|
12
|
+
obligationKind: "boundary_observation",
|
|
13
|
+
resourceKind: "instruction",
|
|
14
|
+
resourceIdPath: "instructionId",
|
|
15
|
+
subjectRequirements: [],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
])[0];
|
|
20
|
+
export function observeBoundary(instruction, outcome, externalReference, observedAt) {
|
|
21
|
+
if (instruction.adapter !== genericAdapter.provider)
|
|
22
|
+
throw new Error("instruction adapter mismatch");
|
|
23
|
+
return boundaryObservationSchema.parse({
|
|
24
|
+
adapter: genericAdapter.provider,
|
|
25
|
+
instructionId: instruction.id,
|
|
26
|
+
outcome,
|
|
27
|
+
externalReference,
|
|
28
|
+
observedAt,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=boundary-fixture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundary-fixture.js","sourceRoot":"","sources":["../src/boundary-fixture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,yBAAyB,GAI1B,MAAM,YAAY,CAAC;AAEpB,8GAA8G;AAC9G,MAAM,CAAC,MAAM,cAAc,GAAG,6BAA6B,EAAE,CAAC;IAC5D;QACE,QAAQ,EAAE,sBAAsB;QAChC,UAAU,EAAE,UAAU;QACtB,QAAQ,EAAE,EAAE,WAAW,EAAE,QAAQ,EAAE;QACnC,YAAY,EAAE;YACZ,kBAAkB,EAAE;gBAClB,SAAS,EAAE,kBAAkB;gBAC7B,SAAS,EAAE,mBAAmB;gBAC9B,cAAc,EAAE,sBAAsB;gBACtC,YAAY,EAAE,aAAa;gBAC3B,cAAc,EAAE,eAAe;gBAC/B,mBAAmB,EAAE,EAAE;aACxB;SACF;KACF;CACO,CAAC,CAAC,CAAC,CAAC,CAAC;AAEf,MAAM,UAAU,eAAe,CAC7B,WAAgC,EAChC,OAAwB,EACxB,iBAAyB,EACzB,UAAkB;IAElB,IAAI,WAAW,CAAC,OAAO,KAAK,cAAc,CAAC,QAAQ;QACjD,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAClD,OAAO,yBAAyB,CAAC,KAAK,CAAC;QACrC,OAAO,EAAE,cAAc,CAAC,QAAQ;QAChC,aAAa,EAAE,WAAW,CAAC,EAAE;QAC7B,OAAO;QACP,iBAAiB;QACjB,UAAU;KACX,CAAC,CAAC;AACL,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,219 +1,90 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { type UdlObjectField } from "@hyperscale0/udl";
|
|
3
|
+
import { subjectFieldTypes, type ProviderOperationDirection, type ProviderResourceBinding, type SubjectFieldType } from "./vocabulary.js";
|
|
4
|
+
export { subjectFieldTypes };
|
|
5
|
+
export type { ProviderOperationDirection, ProviderResourceBinding, SubjectFieldType, };
|
|
6
|
+
export declare const boundaryOutcomeSchema: z.ZodEnum<{
|
|
7
|
+
acknowledged: "acknowledged";
|
|
8
|
+
confirmed: "confirmed";
|
|
9
|
+
rejected: "rejected";
|
|
10
|
+
unknown: "unknown";
|
|
11
|
+
}>;
|
|
12
|
+
export declare const boundaryInstructionSchema: z.ZodReadonly<z.ZodObject<{
|
|
13
|
+
id: z.ZodString;
|
|
14
|
+
environment: z.ZodEnum<{
|
|
15
|
+
live: "live";
|
|
16
|
+
sandbox: "sandbox";
|
|
17
|
+
}>;
|
|
18
|
+
tenantId: z.ZodString;
|
|
19
|
+
productId: z.ZodString;
|
|
20
|
+
productBuildId: z.ZodString;
|
|
21
|
+
buildDigest: z.ZodString;
|
|
22
|
+
target: z.ZodReadonly<z.ZodObject<{
|
|
23
|
+
kind: z.ZodString;
|
|
24
|
+
attachment: z.ZodString;
|
|
25
|
+
instanceId: z.ZodString;
|
|
26
|
+
}, z.core.$strict>>;
|
|
27
|
+
sourceAccountId: z.ZodString;
|
|
28
|
+
destinationAccountId: z.ZodString;
|
|
29
|
+
amount: z.ZodString;
|
|
30
|
+
currency: z.ZodString;
|
|
31
|
+
adapter: z.ZodString;
|
|
32
|
+
}, z.core.$strict>>;
|
|
33
|
+
export declare const boundaryObservationSchema: z.ZodReadonly<z.ZodObject<{
|
|
34
|
+
adapter: z.ZodString;
|
|
35
|
+
externalReference: z.ZodString;
|
|
36
|
+
instructionId: z.ZodString;
|
|
37
|
+
outcome: z.ZodEnum<{
|
|
38
|
+
acknowledged: "acknowledged";
|
|
39
|
+
confirmed: "confirmed";
|
|
40
|
+
rejected: "rejected";
|
|
41
|
+
unknown: "unknown";
|
|
42
|
+
}>;
|
|
43
|
+
observedAt: z.ZodISODateTime;
|
|
44
|
+
}, z.core.$strict>>;
|
|
45
|
+
export type BoundaryInstruction = z.infer<typeof boundaryInstructionSchema>;
|
|
46
|
+
export type BoundaryObservation = z.infer<typeof boundaryObservationSchema>;
|
|
47
|
+
export type BoundaryOutcome = z.infer<typeof boundaryOutcomeSchema>;
|
|
48
|
+
/** Adapter implementation. Dispatch and enquiry never choose money bindings. */
|
|
49
|
+
export interface BoundaryAdapter {
|
|
50
|
+
readonly id: string;
|
|
51
|
+
dispatch(instruction: BoundaryInstruction): Promise<BoundaryObservation | undefined>;
|
|
52
|
+
observe(instruction: BoundaryInstruction): Promise<BoundaryObservation | undefined>;
|
|
53
|
+
}
|
|
5
54
|
export interface ProviderAdapterVocabulary {
|
|
6
55
|
readonly capability: string;
|
|
7
|
-
readonly domain: string;
|
|
8
|
-
readonly meter: string;
|
|
9
56
|
readonly obligation: string;
|
|
10
57
|
readonly operation: string;
|
|
11
58
|
readonly resource: string;
|
|
12
|
-
readonly webhookOperation: string;
|
|
13
|
-
readonly webhookResource: string;
|
|
14
|
-
readonly webhookResourceId: string;
|
|
15
59
|
}
|
|
16
60
|
export interface ProviderAdapter<Vocabulary extends ProviderAdapterVocabulary = ProviderAdapterVocabulary> {
|
|
17
61
|
readonly bindings: Partial<Record<Vocabulary["resource"], ProviderResourceBinding>>;
|
|
18
62
|
readonly capability: Vocabulary["capability"];
|
|
19
|
-
readonly config: ProviderConfig;
|
|
20
|
-
readonly domain?: Vocabulary["domain"];
|
|
21
|
-
readonly egress: ProviderEgressMode;
|
|
22
63
|
readonly operationMap: Partial<Record<Vocabulary["operation"], ProviderOperationBinding<Vocabulary>>>;
|
|
23
|
-
readonly profile?: PartnerBankAdapterProfile;
|
|
24
64
|
readonly provider: string;
|
|
25
|
-
readonly webhookMap: Readonly<Record<string, ProviderWebhookTransitionPlan<Vocabulary>>>;
|
|
26
65
|
}
|
|
27
66
|
export interface ProviderOperationBinding<Vocabulary extends ProviderAdapterVocabulary = ProviderAdapterVocabulary> {
|
|
28
|
-
/**
|
|
29
|
-
* Authoritative read-after-write status enquiry for a command whose response
|
|
30
|
-
* may have been lost. Presence is an explicit provider-readiness commitment;
|
|
31
|
-
* runtimes must never infer enquiry support from the command URL.
|
|
32
|
-
*/
|
|
33
|
-
readonly statusEnquiry?: {
|
|
34
|
-
readonly keys: StatusEnquiryKey;
|
|
35
|
-
readonly pathTemplate: string;
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* How THIS operation's response carries its outcome. Per operation, never
|
|
39
|
-
* per provider and never inherited from another provider's class: one real
|
|
40
|
-
* provider answers two ways, wrapping six of seven endpoints in
|
|
41
|
-
* `{status, message, data}` while the seventh returns a bare object. Absent
|
|
42
|
-
* means the class is not established yet -- an unclassified operation, not
|
|
43
|
-
* a silent `http_200_body_status`.
|
|
44
|
-
*/
|
|
45
|
-
readonly envelope?: ProviderResponseEnvelope;
|
|
46
67
|
readonly operation: Vocabulary["operation"];
|
|
47
68
|
readonly direction: ProviderOperationDirection;
|
|
48
|
-
readonly meter?: Vocabulary["meter"];
|
|
49
69
|
readonly obligationKind: Vocabulary["obligation"];
|
|
50
70
|
readonly resourceIdPath: string;
|
|
51
71
|
readonly resourceKind: Vocabulary["resource"];
|
|
52
|
-
}
|
|
53
|
-
export interface ProviderWebhookTransitionPlan<Vocabulary extends ProviderAdapterVocabulary = ProviderAdapterVocabulary> {
|
|
54
|
-
readonly eventKind: string;
|
|
55
|
-
readonly operationName: Vocabulary["webhookOperation"];
|
|
56
|
-
readonly optionalPayloadFields: readonly string[];
|
|
57
|
-
readonly requiredFieldGroups: readonly (readonly string[])[];
|
|
58
|
-
readonly requiredPayloadFields: readonly string[];
|
|
59
|
-
readonly resourceIdField: Vocabulary["webhookResourceId"];
|
|
60
|
-
readonly resourceKind: Vocabulary["webhookResource"];
|
|
61
|
-
readonly timestampField: ProviderTimestampField;
|
|
62
|
-
}
|
|
63
|
-
export type ProviderCredentialReference = {
|
|
64
|
-
readonly name: string;
|
|
65
|
-
readonly source: "environment";
|
|
66
|
-
} | {
|
|
67
|
-
readonly account: string;
|
|
68
|
-
readonly service: string;
|
|
69
|
-
readonly source: "keychain";
|
|
70
|
-
};
|
|
71
|
-
export interface ProviderConfig {
|
|
72
|
-
readonly baseUrl?: string;
|
|
73
|
-
readonly credentialRef?: ProviderCredentialReference;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* The fetch window facts for one declared statement format. The paging fields
|
|
77
|
-
* are required for an end-of-day format (`availability: "T-1"` pages over
|
|
78
|
-
* history) and absent for an intraday one; conformance enforces that split.
|
|
79
|
-
*/
|
|
80
|
-
export interface PartnerBankStatementWindow {
|
|
81
|
-
readonly availability: StatementAvailability;
|
|
82
|
-
readonly lookbackDays?: number;
|
|
83
|
-
readonly statementDaysPerRequest?: number;
|
|
84
|
-
}
|
|
85
|
-
export interface PartnerBankAdapterProfile {
|
|
86
|
-
readonly auth: {
|
|
87
|
-
readonly envelope: ProviderAuthEnvelope;
|
|
88
|
-
readonly signing: PartnerBankSigning;
|
|
89
|
-
};
|
|
90
72
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
73
|
+
* Stored object metadata fields required by this operation. An array of UDL
|
|
74
|
+
* object fields validated against the exported UDL schema and the closed
|
|
75
|
+
* subject field type vocabulary. Absence means requirements are undeclared;
|
|
76
|
+
* an explicit empty array declares no requirements. Neither implies
|
|
77
|
+
* provider readiness.
|
|
96
78
|
*/
|
|
97
|
-
readonly
|
|
98
|
-
/**
|
|
99
|
-
* The text this bank prints on its own charge lines, as prefixes: the
|
|
100
|
-
* trailing part names the charged thing. Recognizing a charge is Executor
|
|
101
|
-
* knowledge, so it is declared per bank and never inferred downstream, and
|
|
102
|
-
* a bank whose charge wording is not established leaves this absent rather
|
|
103
|
-
* than borrowing another bank's words.
|
|
104
|
-
*/
|
|
105
|
-
readonly narratives?: {
|
|
106
|
-
readonly charge: readonly string[];
|
|
107
|
-
readonly vat: readonly string[];
|
|
108
|
-
};
|
|
109
|
-
readonly posting: ChargePosting;
|
|
110
|
-
readonly vat: ChargeVatMode;
|
|
111
|
-
};
|
|
112
|
-
readonly executedReadBack: {
|
|
113
|
-
readonly fxRate: FxRateReadBack;
|
|
114
|
-
readonly railSubstitution: RailSubstitution;
|
|
115
|
-
};
|
|
116
|
-
readonly idempotencySpine: {
|
|
117
|
-
readonly dedupeKeys: readonly PartnerBankDedupeKey[];
|
|
118
|
-
readonly endToEndId: {
|
|
119
|
-
readonly maxLength: number;
|
|
120
|
-
};
|
|
121
|
-
readonly instructionId: {
|
|
122
|
-
readonly maxLength: number;
|
|
123
|
-
};
|
|
124
|
-
readonly paymentReference: {
|
|
125
|
-
readonly maxLength: number;
|
|
126
|
-
};
|
|
127
|
-
};
|
|
128
|
-
readonly kind: "partner_bank";
|
|
129
|
-
readonly lifecycleVocabulary: Readonly<Record<string, PartnerBankLifecycleState>>;
|
|
130
|
-
readonly limitFacts: {
|
|
131
|
-
readonly access: LimitAccessMode;
|
|
132
|
-
readonly dimensions: readonly LimitDimension[];
|
|
133
|
-
};
|
|
134
|
-
readonly notificationMechanisms: readonly ProviderNotificationMechanism[];
|
|
135
|
-
/**
|
|
136
|
-
* Which reference on a statement debit line names the instruction behind it.
|
|
137
|
-
* A statement entry carries two: the bank's own entry reference and the
|
|
138
|
-
* client reference the instruction was sent with. Banks disagree about which
|
|
139
|
-
* one comes back on the statement, and picking the wrong one makes every
|
|
140
|
-
* payout the platform instructed look like an unexplained debit. Absent when
|
|
141
|
-
* a bank's statement reference identity is not established.
|
|
142
|
-
*/
|
|
143
|
-
readonly statementDebitReference?: StatementDebitReference;
|
|
144
|
-
readonly statementFormats: readonly PartnerBankStatementFormat[];
|
|
145
|
-
readonly financialAddressProvisioning: {
|
|
146
|
-
readonly mechanism: FinancialAddressMechanism;
|
|
147
|
-
readonly pricing: FinancialAddressPricing;
|
|
148
|
-
readonly quota: FinancialAddressQuota;
|
|
149
|
-
};
|
|
150
|
-
readonly windows: {
|
|
151
|
-
readonly paymentCutoff: {
|
|
152
|
-
readonly afterCutoffValueDate: ValueDateOffset;
|
|
153
|
-
readonly beforeCutoffValueDate: ValueDateOffset;
|
|
154
|
-
/** Bank-local wall-clock time, HH:MM. */
|
|
155
|
-
readonly time: string;
|
|
156
|
-
/** The IANA time zone the cutoff time is read in (e.g. Europe/Dublin). */
|
|
157
|
-
readonly timeZone: string;
|
|
158
|
-
};
|
|
159
|
-
/** One window per declared statement format, keyed by that format. */
|
|
160
|
-
readonly statements: Partial<Readonly<Record<PartnerBankStatementFormat, PartnerBankStatementWindow>>>;
|
|
161
|
-
readonly valueDateMaxDaysAhead: number;
|
|
162
|
-
};
|
|
163
|
-
readonly wireCodec: PartnerBankWireCodec;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* The insurance-distribution marketplace class, the partner bank's opposite
|
|
167
|
-
* number: an aggregator API whose whole truth arrives in the command
|
|
168
|
-
* response. Every field below is a recorded observation of one such surface,
|
|
169
|
-
* not a reading of a standard.
|
|
170
|
-
*
|
|
171
|
-
* What makes the class distinct from `partner_bank`, and why each field exists:
|
|
172
|
-
*
|
|
173
|
-
* - **Nothing to poll.** No status enquiry, no list, no read-back, no
|
|
174
|
-
* webhook, on any line. `notificationMechanisms` is empty by evidence, and
|
|
175
|
-
* the issuance response is the ONLY carrier of the policy identity -- so
|
|
176
|
-
* the transport must capture it durably BEFORE acknowledging the caller.
|
|
177
|
-
* - **No idempotency header exists anywhere in the corpus.** Dedupe runs on
|
|
178
|
-
* caller-minted business reference numbers replayed verbatim at each step,
|
|
179
|
-
* which is why the spine is a reference chain rather than a key.
|
|
180
|
-
* - **Auth is a bespoke credential exchange**, not OAuth2 client
|
|
181
|
-
* credentials: username/password against the provider's own login route
|
|
182
|
-
* yields a bearer with no documented lifetime, refresh, or scope.
|
|
183
|
-
* - **Errors are prose.** No machine error code appears anywhere, so an
|
|
184
|
-
* adapter cannot build a decline taxonomy from the docs alone.
|
|
185
|
-
*/
|
|
186
|
-
export interface InsuranceDistributorAdapterProfile {
|
|
187
|
-
readonly kind: "insurance_distributor";
|
|
188
|
-
readonly wireCodec: "rest_json";
|
|
189
|
-
readonly auth: {
|
|
190
|
-
readonly envelope: ProviderAuthEnvelope;
|
|
191
|
-
/** How the bearer is obtained, when the provider documents it at all. */
|
|
192
|
-
readonly acquisition: "password_login" | "out_of_band";
|
|
193
|
-
readonly signing: "none";
|
|
194
|
-
};
|
|
195
|
-
/**
|
|
196
|
-
* Envelope class per DOCUMENTED operation name, because one provider answers
|
|
197
|
-
* in more than one shape. Keys are the provider's own operation names, not
|
|
198
|
-
* platform operations -- this profile describes a surface no adapter has
|
|
199
|
-
* integrated yet.
|
|
200
|
-
*/
|
|
201
|
-
readonly envelopes: Readonly<Record<string, ProviderResponseEnvelope>>;
|
|
202
|
-
/**
|
|
203
|
-
* The correlation chain that stands in for an idempotency key: which
|
|
204
|
-
* references the CALLER mints and which the provider mints, in the order the
|
|
205
|
-
* exchange replays them.
|
|
206
|
-
*/
|
|
207
|
-
readonly referenceChain: {
|
|
208
|
-
readonly clientMinted: readonly string[];
|
|
209
|
-
readonly providerMinted: readonly string[];
|
|
210
|
-
};
|
|
211
|
-
readonly notificationMechanisms: readonly ProviderNotificationMechanism[];
|
|
212
|
-
/** Whether the provider publishes machine-readable failure codes. */
|
|
213
|
-
readonly errorModel: "prose_only" | "coded";
|
|
214
|
-
/** True when the command response is the only carrier of the created fact. */
|
|
215
|
-
readonly captureBeforeAcknowledge: boolean;
|
|
79
|
+
readonly subjectRequirements?: readonly UdlObjectField[];
|
|
216
80
|
}
|
|
217
81
|
/** Bind a product vocabulary once, then retain literal adapter registry types. */
|
|
218
82
|
export declare function createProviderAdapterRegistry<Vocabulary extends ProviderAdapterVocabulary>(): <const Adapters extends readonly ProviderAdapter<Vocabulary>[]>(adapters: Adapters) => Adapters;
|
|
83
|
+
export type AdapterConformanceCode = "operation_map_empty" | "resource_binding_missing" | "declaration_invalid";
|
|
84
|
+
export interface AdapterConformanceFinding {
|
|
85
|
+
readonly code: AdapterConformanceCode;
|
|
86
|
+
readonly message: string;
|
|
87
|
+
readonly path: string;
|
|
88
|
+
}
|
|
89
|
+
export declare function adapterConformanceFindings<Vocabulary extends ProviderAdapterVocabulary>(adapter: ProviderAdapter<Vocabulary>): readonly AdapterConformanceFinding[];
|
|
219
90
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,45 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
import { udlObjectFieldSchema } from "@hyperscale0/udl";
|
|
3
|
+
import { providerKeyPattern, subjectFieldTypes, } from "./vocabulary.js";
|
|
4
|
+
export { subjectFieldTypes };
|
|
5
|
+
const boundaryId = z.string().min(1).max(240);
|
|
6
|
+
export const boundaryOutcomeSchema = z.enum([
|
|
7
|
+
"acknowledged",
|
|
8
|
+
"unknown",
|
|
9
|
+
"confirmed",
|
|
10
|
+
"rejected",
|
|
11
|
+
]);
|
|
12
|
+
export const boundaryInstructionSchema = z
|
|
13
|
+
.strictObject({
|
|
14
|
+
id: boundaryId,
|
|
15
|
+
environment: z.enum(["sandbox", "live"]),
|
|
16
|
+
tenantId: boundaryId,
|
|
17
|
+
productId: boundaryId,
|
|
18
|
+
productBuildId: boundaryId,
|
|
19
|
+
buildDigest: boundaryId,
|
|
20
|
+
target: z
|
|
21
|
+
.strictObject({
|
|
22
|
+
kind: boundaryId,
|
|
23
|
+
attachment: boundaryId,
|
|
24
|
+
instanceId: boundaryId,
|
|
25
|
+
})
|
|
26
|
+
.readonly(),
|
|
27
|
+
sourceAccountId: boundaryId,
|
|
28
|
+
destinationAccountId: boundaryId,
|
|
29
|
+
amount: z.string().regex(/^[1-9][0-9]{0,17}$/),
|
|
30
|
+
currency: z.string().regex(/^[A-Z]{3}$/),
|
|
31
|
+
adapter: z.string().regex(providerKeyPattern),
|
|
32
|
+
})
|
|
33
|
+
.readonly();
|
|
34
|
+
export const boundaryObservationSchema = z
|
|
35
|
+
.strictObject({
|
|
36
|
+
adapter: z.string().regex(providerKeyPattern),
|
|
37
|
+
externalReference: boundaryId,
|
|
38
|
+
instructionId: boundaryId,
|
|
39
|
+
outcome: boundaryOutcomeSchema,
|
|
40
|
+
observedAt: z.iso.datetime(),
|
|
41
|
+
})
|
|
42
|
+
.readonly();
|
|
4
43
|
/** Validate an adapter registry once at load, then preserve its exact type. */
|
|
5
44
|
function defineProviderAdapters(adapters) {
|
|
6
45
|
const identities = new Set();
|
|
@@ -13,12 +52,7 @@ function defineProviderAdapters(adapters) {
|
|
|
13
52
|
throw new Error(`duplicate provider adapter ${identity}`);
|
|
14
53
|
}
|
|
15
54
|
identities.add(identity);
|
|
16
|
-
validateConfig(adapter.provider, adapter.config);
|
|
17
55
|
validateOperations(adapter);
|
|
18
|
-
validateWebhooks(adapter);
|
|
19
|
-
if (adapter.profile) {
|
|
20
|
-
validatePartnerBankProfile(adapter.provider, adapter.profile);
|
|
21
|
-
}
|
|
22
56
|
}
|
|
23
57
|
return adapters;
|
|
24
58
|
}
|
|
@@ -26,119 +60,77 @@ function defineProviderAdapters(adapters) {
|
|
|
26
60
|
export function createProviderAdapterRegistry() {
|
|
27
61
|
return (adapters) => defineProviderAdapters(adapters);
|
|
28
62
|
}
|
|
29
|
-
function validateConfig(provider, config) {
|
|
30
|
-
if (config.baseUrl) {
|
|
31
|
-
try {
|
|
32
|
-
new URL(config.baseUrl);
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
throw new Error(`${provider} config.baseUrl must be an absolute URL`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const credential = config.credentialRef;
|
|
39
|
-
if (!credential)
|
|
40
|
-
return;
|
|
41
|
-
if (credential.source === "environment") {
|
|
42
|
-
assertNonblank(provider, "config.credentialRef.name", credential.name);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
assertNonblank(provider, "config.credentialRef.account", credential.account);
|
|
46
|
-
assertNonblank(provider, "config.credentialRef.service", credential.service);
|
|
47
|
-
}
|
|
48
63
|
function validateOperations(adapter) {
|
|
49
64
|
for (const [operation, binding] of Object.entries(adapter.operationMap)) {
|
|
50
65
|
if (operation !== binding.operation) {
|
|
51
66
|
throw new Error(`${adapter.provider} operationMap key ${operation} must match operation ${binding.operation}`);
|
|
52
67
|
}
|
|
53
68
|
assertNonblank(adapter.provider, `${operation}.resourceIdPath`, binding.resourceIdPath);
|
|
54
|
-
if (binding.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
if (binding.subjectRequirements !== undefined) {
|
|
70
|
+
if (!Array.isArray(binding.subjectRequirements) ||
|
|
71
|
+
binding.subjectRequirements.length > 128) {
|
|
72
|
+
throw new Error(`${adapter.provider} ${operation}.subjectRequirements must be an array of at most 128 fields`);
|
|
73
|
+
}
|
|
74
|
+
for (const [index, requirement,] of binding.subjectRequirements.entries()) {
|
|
75
|
+
const result = udlObjectFieldSchema.safeParse(requirement);
|
|
76
|
+
if (!result.success || result.data.optional) {
|
|
77
|
+
throw new Error(`${adapter.provider} ${operation}.subjectRequirements[${index}] does not match UDL object field schema: ${result.success ? "action requirements cannot be optional" : result.error.message}`);
|
|
78
|
+
}
|
|
79
|
+
if (!subjectFieldTypes.includes(requirement.type)) {
|
|
80
|
+
throw new Error(`${adapter.provider} ${operation}.subjectRequirements[${index}] unknown subject field type ${requirement.type}`);
|
|
81
|
+
}
|
|
61
82
|
}
|
|
62
83
|
}
|
|
63
84
|
}
|
|
64
85
|
}
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
throw new Error(`${adapter.provider} webhookMap key ${event} must match eventKind ${plan.eventKind}`);
|
|
69
|
-
}
|
|
86
|
+
function assertNonblank(provider, field, value) {
|
|
87
|
+
if (value.trim().length === 0) {
|
|
88
|
+
throw new Error(`${provider} ${field} must not be blank`);
|
|
70
89
|
}
|
|
71
90
|
}
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
assertUnique(provider, "notificationMechanisms", profile.notificationMechanisms);
|
|
81
|
-
assertUnique(provider, "limitFacts.dimensions", profile.limitFacts.dimensions);
|
|
82
|
-
const spine = profile.idempotencySpine;
|
|
83
|
-
// The dedupe keys widened from a fixed triple to a list, so the list now
|
|
84
|
-
// carries the guarantees the tuple used to give for free.
|
|
85
|
-
if (spine.dedupeKeys.length === 0) {
|
|
86
|
-
throw new Error(`${provider} idempotencySpine.dedupeKeys is empty`);
|
|
87
|
-
}
|
|
88
|
-
assertUnique(provider, "idempotencySpine.dedupeKeys", spine.dedupeKeys);
|
|
89
|
-
const spineLimits = {
|
|
90
|
-
endToEndId: spine.endToEndId,
|
|
91
|
-
instructionId: spine.instructionId,
|
|
92
|
-
paymentReference: spine.paymentReference,
|
|
93
|
-
};
|
|
94
|
-
for (const [field, limit] of Object.entries(spineLimits)) {
|
|
95
|
-
assertPositiveInteger(provider, `idempotencySpine.${field}.maxLength`, limit.maxLength);
|
|
96
|
-
}
|
|
97
|
-
const windows = profile.windows;
|
|
98
|
-
if (!/^\d{2}:\d{2}$/.test(windows.paymentCutoff.time)) {
|
|
99
|
-
throw new Error(`${provider} windows.paymentCutoff.time must be HH:MM`);
|
|
100
|
-
}
|
|
101
|
-
// Intl.DateTimeFormat treats an undefined timeZone as the runtime default,
|
|
102
|
-
// so presence must be checked before validity.
|
|
103
|
-
const timeZone = windows.paymentCutoff.timeZone;
|
|
104
|
-
if (typeof timeZone !== "string") {
|
|
105
|
-
throw new Error(`${provider} windows.paymentCutoff.timeZone must be a valid IANA time zone`);
|
|
106
|
-
}
|
|
91
|
+
export function adapterConformanceFindings(adapter) {
|
|
92
|
+
const findings = bindingFindings(adapter);
|
|
93
|
+
if (Object.keys(adapter.operationMap).length === 0)
|
|
94
|
+
findings.unshift({
|
|
95
|
+
code: "operation_map_empty",
|
|
96
|
+
path: "operationMap",
|
|
97
|
+
message: "An adapter must declare an operation.",
|
|
98
|
+
});
|
|
107
99
|
try {
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
throw new Error(`${provider} windows.paymentCutoff.timeZone must be a valid IANA time zone`);
|
|
100
|
+
createProviderAdapterRegistry()([adapter]);
|
|
112
101
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
`of ${statementAvailabilities.join(", ")}`);
|
|
120
|
-
}
|
|
121
|
-
if (window.lookbackDays !== undefined) {
|
|
122
|
-
assertPositiveInteger(provider, `windows.statements.${format}.lookbackDays`, window.lookbackDays);
|
|
123
|
-
}
|
|
124
|
-
if (window.statementDaysPerRequest !== undefined) {
|
|
125
|
-
assertPositiveInteger(provider, `windows.statements.${format}.statementDaysPerRequest`, window.statementDaysPerRequest);
|
|
126
|
-
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
findings.push({
|
|
104
|
+
code: "declaration_invalid",
|
|
105
|
+
path: "",
|
|
106
|
+
message: error instanceof Error ? error.message : String(error),
|
|
107
|
+
});
|
|
127
108
|
}
|
|
109
|
+
return findings;
|
|
128
110
|
}
|
|
129
|
-
function
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
111
|
+
function bindingFindings(adapter) {
|
|
112
|
+
const findings = [];
|
|
113
|
+
const bindings = adapter.bindings;
|
|
114
|
+
const claimed = new Set();
|
|
115
|
+
const require = (kind, path) => {
|
|
116
|
+
if (bindings[kind] !== undefined || claimed.has(kind))
|
|
117
|
+
return;
|
|
118
|
+
claimed.add(kind);
|
|
119
|
+
// Provider facts commit onto platform resources through a declared match
|
|
120
|
+
// mode; a resource the adapter settles without one has no lawful way to
|
|
121
|
+
// bind the observation.
|
|
122
|
+
findings.push({
|
|
123
|
+
code: "resource_binding_missing",
|
|
124
|
+
message: `resource kind ${kind} is settled by this adapter but declares no ` +
|
|
125
|
+
"binding mode, so provider facts have no lawful way to commit",
|
|
126
|
+
path,
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
for (const binding of Object.values(adapter.operationMap)) {
|
|
130
|
+
if (!binding)
|
|
131
|
+
continue;
|
|
132
|
+
require(binding.resourceKind, `bindings.${binding.resourceKind}`);
|
|
142
133
|
}
|
|
134
|
+
return findings;
|
|
143
135
|
}
|
|
144
136
|
//# sourceMappingURL=index.js.map
|