@fabricorg/platform-host 2.0.0 → 3.0.0
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 +35 -8
- package/MIGRATION-2-IDEMPOTENCY.md +32 -0
- package/README.md +36 -7
- package/dist/index.cjs +295 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +94 -8
- package/dist/index.d.ts +94 -8
- package/dist/index.js +292 -32
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,9 +1,66 @@
|
|
|
1
1
|
import { AdapterRegistry, resolveAction, createFabricId, assertGovernanceRuntimeEvidence, FABRIC_GOVERNANCE_CONTRACT_VERSION, assertMutationGovernanceContext, evaluatePolicyDefinitions, aggregatePolicyOutcomes, validateTransition, executeWithAdapterRetry, resolveStateMachine } from '@fabricorg/platform';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
2
3
|
|
|
3
4
|
// src/host.ts
|
|
4
5
|
|
|
5
6
|
// src/types.ts
|
|
6
7
|
var PLATFORM_HOST_CONTRACT_VERSION = 2;
|
|
8
|
+
var IdempotencyConflictError = class extends Error {
|
|
9
|
+
constructor(conflict) {
|
|
10
|
+
super(`Idempotency key "${conflict.idempotencyKey}" conflicts with invocation ${conflict.existingInvocationId}: ${conflict.reasons.join(", ")}`);
|
|
11
|
+
this.conflict = conflict;
|
|
12
|
+
this.name = "IdempotencyConflictError";
|
|
13
|
+
}
|
|
14
|
+
conflict;
|
|
15
|
+
code = "IDEMPOTENCY_CONFLICT";
|
|
16
|
+
};
|
|
17
|
+
var PARAMETER_DIGEST_ALGORITHM = "fabric-canonical-json-sha256-v1";
|
|
18
|
+
function canonicalJson(value) {
|
|
19
|
+
return JSON.stringify(sort(value));
|
|
20
|
+
}
|
|
21
|
+
function sort(value) {
|
|
22
|
+
if (Array.isArray(value)) return value.map(sort);
|
|
23
|
+
if (value && typeof value === "object") {
|
|
24
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sort(child)]));
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function digestParameters(parameters) {
|
|
29
|
+
return createHash("sha256").update(canonicalJson(parameters)).digest("hex");
|
|
30
|
+
}
|
|
31
|
+
var ATTRIBUTE_NAME = /^[a-z][a-z0-9-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)+$/;
|
|
32
|
+
var INVOCATION_SOURCES = /* @__PURE__ */ new Set(["sdui", "api", "agent", "worker", "system"]);
|
|
33
|
+
function normalizeProvenance(input, options) {
|
|
34
|
+
if (!input) return void 0;
|
|
35
|
+
if (!INVOCATION_SOURCES.has(input.source)) throw new Error(`Invalid invocation provenance source: ${input.source}`);
|
|
36
|
+
const maxAttributes = options?.maxAttributes ?? 16;
|
|
37
|
+
const maxValueLength = options?.maxValueLength ?? 256;
|
|
38
|
+
const allowlist = new Set(options?.auditAttributeAllowlist ?? []);
|
|
39
|
+
const validate = (attributes, durable) => {
|
|
40
|
+
if (!attributes) return void 0;
|
|
41
|
+
const entries = Object.entries(attributes);
|
|
42
|
+
if (entries.length > maxAttributes) throw new Error(`Invocation provenance exceeds ${maxAttributes} attributes.`);
|
|
43
|
+
for (const [key, value] of entries) {
|
|
44
|
+
if (!ATTRIBUTE_NAME.test(key)) throw new Error(`Invalid provenance attribute name: ${key}`);
|
|
45
|
+
if (value.length > maxValueLength) throw new Error(`Invocation provenance attribute "${key}" exceeds ${maxValueLength} characters.`);
|
|
46
|
+
if (durable && !allowlist.has(key)) throw new Error(`Audit provenance attribute "${key}" is not allowlisted.`);
|
|
47
|
+
}
|
|
48
|
+
return Object.fromEntries(entries);
|
|
49
|
+
};
|
|
50
|
+
validate(input.traceAttributes, false);
|
|
51
|
+
const redactedAuditAttributes = input.auditAttributes ? options?.redactAuditAttributes?.({ ...input.auditAttributes }) ?? input.auditAttributes : void 0;
|
|
52
|
+
const auditAttributes = validate(redactedAuditAttributes, true);
|
|
53
|
+
options?.onTrace?.({
|
|
54
|
+
...input,
|
|
55
|
+
...auditAttributes ? { auditAttributes } : { auditAttributes: void 0 }
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
source: input.source,
|
|
59
|
+
correlationId: input.correlationId,
|
|
60
|
+
...input.causationId ? { causationId: input.causationId } : {},
|
|
61
|
+
...auditAttributes ? { auditAttributes } : {}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
7
64
|
|
|
8
65
|
// src/host.ts
|
|
9
66
|
var DEFAULT_EXTRACT_EVENTS = (data) => {
|
|
@@ -27,6 +84,9 @@ function createGovernedActionHost(options) {
|
|
|
27
84
|
const actionResolver = options.resolveAction ?? resolveAction;
|
|
28
85
|
const eventResultFields = options.eventResultFields ?? ["_events"];
|
|
29
86
|
async function submitAction(input) {
|
|
87
|
+
if (input.executionReason && !["initial", "offline_replay"].includes(input.executionReason)) {
|
|
88
|
+
throw new Error(`Unsupported submitted execution reason: ${input.executionReason}`);
|
|
89
|
+
}
|
|
30
90
|
const action = actionResolver(input.actionId);
|
|
31
91
|
if (!action) throw new Error(`Unknown action: ${input.actionId}`);
|
|
32
92
|
const authorizationInput = toAuthorizationInput(action, input);
|
|
@@ -37,8 +97,16 @@ function createGovernedActionHost(options) {
|
|
|
37
97
|
throw new Error(`Actor ${input.actorId} is not authorized for action ${input.actionId}`);
|
|
38
98
|
}
|
|
39
99
|
const actionInvocationId = createFabricId("act");
|
|
40
|
-
|
|
100
|
+
if (input.correlationId && input.provenance?.correlationId && input.correlationId !== input.provenance.correlationId) throw new Error("Invocation provenance correlationId does not match submission correlationId.");
|
|
101
|
+
if (input.causationId && input.provenance?.causationId && input.causationId !== input.provenance.causationId) throw new Error("Invocation provenance causationId does not match submission causationId.");
|
|
102
|
+
const correlationId = input.correlationId ?? input.provenance?.correlationId ?? createFabricId("corr");
|
|
41
103
|
const durableParameters = options.redactActionParameters ? options.redactActionParameters(input.actionId, input.parameters) : input.parameters;
|
|
104
|
+
const parameterDigest = digestParameters(input.parameters);
|
|
105
|
+
if (input.authorizationBinding) validateAuthorizationBinding(input, parameterDigest, action.execution?.authorityMoment, now());
|
|
106
|
+
const durableProvenance = normalizeProvenance(
|
|
107
|
+
input.provenance,
|
|
108
|
+
options.provenance
|
|
109
|
+
);
|
|
42
110
|
const runtimeEvidence = {
|
|
43
111
|
governanceContractVersion: FABRIC_GOVERNANCE_CONTRACT_VERSION,
|
|
44
112
|
hostContractVersion: PLATFORM_HOST_CONTRACT_VERSION,
|
|
@@ -58,12 +126,41 @@ function createGovernedActionHost(options) {
|
|
|
58
126
|
result: {},
|
|
59
127
|
runtimeEvidence,
|
|
60
128
|
correlationId,
|
|
61
|
-
...input.causationId ? { causationId: input.causationId } : {},
|
|
129
|
+
...input.causationId ?? input.provenance?.causationId ? { causationId: input.causationId ?? input.provenance?.causationId } : {},
|
|
62
130
|
...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
|
|
131
|
+
...input.idempotencyKey ? {
|
|
132
|
+
parameterDigest,
|
|
133
|
+
parameterDigestAlgorithm: PARAMETER_DIGEST_ALGORITHM,
|
|
134
|
+
idempotencyActorId: input.actorId,
|
|
135
|
+
...input.authorizationBindingId ? { idempotencyAuthorizationBindingId: input.authorizationBindingId } : {}
|
|
136
|
+
} : {},
|
|
137
|
+
...durableProvenance ? { provenance: durableProvenance } : {},
|
|
138
|
+
...input.authorizationBinding ? { authorizationBinding: input.authorizationBinding } : {},
|
|
139
|
+
...input.executionReason ? { executionReason: input.executionReason } : {},
|
|
63
140
|
...input.authorizationBindingId ? { authorizationBindingId: input.authorizationBindingId } : {}
|
|
64
141
|
});
|
|
142
|
+
if (durableInvocation.id !== actionInvocationId && input.idempotencyKey) {
|
|
143
|
+
const conflict = idempotencyConflict(durableInvocation, {
|
|
144
|
+
actorId: input.actorId,
|
|
145
|
+
authorizationBindingId: input.authorizationBindingId,
|
|
146
|
+
actionVersion: action.version,
|
|
147
|
+
parameterDigest,
|
|
148
|
+
idempotencyKey: input.idempotencyKey
|
|
149
|
+
});
|
|
150
|
+
if (!durableInvocation.parameterDigest) {
|
|
151
|
+
options.idempotency?.onLegacyRecord?.(durableInvocation);
|
|
152
|
+
const enforceableLegacyConflict = conflict && { ...conflict, reasons: conflict.reasons.filter((reason) => reason !== "parameters") };
|
|
153
|
+
if (enforceableLegacyConflict && enforceableLegacyConflict.reasons.length) {
|
|
154
|
+
options.idempotency?.onConflict?.(enforceableLegacyConflict);
|
|
155
|
+
if ((options.idempotency?.conflictMode ?? "enforce") === "enforce") throw new IdempotencyConflictError(enforceableLegacyConflict);
|
|
156
|
+
}
|
|
157
|
+
} else if (conflict) {
|
|
158
|
+
options.idempotency?.onConflict?.(conflict);
|
|
159
|
+
if ((options.idempotency?.conflictMode ?? "enforce") === "enforce") throw new IdempotencyConflictError(conflict);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
65
162
|
const durableWorkflowId = `action-invocation-${durableInvocation.id}`;
|
|
66
|
-
if (durableInvocation.id !== actionInvocationId
|
|
163
|
+
if (durableInvocation.id !== actionInvocationId) {
|
|
67
164
|
return {
|
|
68
165
|
actionInvocationId: durableInvocation.id,
|
|
69
166
|
status: durableInvocation.status,
|
|
@@ -71,7 +168,8 @@ function createGovernedActionHost(options) {
|
|
|
71
168
|
result: durableInvocation.result,
|
|
72
169
|
...durableInvocation.error ? { error: durableInvocation.error } : {},
|
|
73
170
|
...durableInvocation.hitlRoute ? { hitlRoute: durableInvocation.hitlRoute } : {},
|
|
74
|
-
...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {}
|
|
171
|
+
...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {},
|
|
172
|
+
...durableInvocation.authorizationReconciliation ? { reconciliation: durableInvocation.authorizationReconciliation } : {}
|
|
75
173
|
};
|
|
76
174
|
}
|
|
77
175
|
if (options.dispatcher) {
|
|
@@ -219,7 +317,7 @@ function createGovernedActionHost(options) {
|
|
|
219
317
|
}
|
|
220
318
|
}
|
|
221
319
|
const authorizationInput = toAuthorizationInput(action, invocation);
|
|
222
|
-
const executionReason = executionReasonOverride ?? (invocation.attemptCount > 1 || resumingRunningInvocation && invocation.attemptCount === 0 ? "recovery" : "initial");
|
|
320
|
+
const executionReason = executionReasonOverride ?? (invocation.attemptCount > 1 || resumingRunningInvocation && invocation.attemptCount === 0 ? "recovery" : invocation.executionReason ?? "initial");
|
|
223
321
|
if (!await options.authorization.checkEntitlement(authorizationInput)) {
|
|
224
322
|
return fail(
|
|
225
323
|
invocation,
|
|
@@ -227,7 +325,32 @@ function createGovernedActionHost(options) {
|
|
|
227
325
|
`Module "${action.namespace}" is no longer enabled for tenant ${tenantId}`
|
|
228
326
|
);
|
|
229
327
|
}
|
|
230
|
-
const
|
|
328
|
+
const authorityMoment = action.execution?.authorityMoment;
|
|
329
|
+
if (authorityMoment === "capture" && !invocation.authorizationBinding) {
|
|
330
|
+
const reconciliation = {
|
|
331
|
+
kind: "authorization_missing_capture_evidence",
|
|
332
|
+
governingMoment: authorityMoment,
|
|
333
|
+
executionReason,
|
|
334
|
+
...invocation.provenance ? { provenance: invocation.provenance } : {},
|
|
335
|
+
message: `Action ${action.actionId} requires durable capture-time authorization evidence`
|
|
336
|
+
};
|
|
337
|
+
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
|
|
338
|
+
return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
|
|
339
|
+
}
|
|
340
|
+
const bindingExpired = authorityMoment !== "capture" && invocation.authorizationBinding?.expiresAt !== void 0 && Date.parse(invocation.authorizationBinding.expiresAt) <= now().getTime();
|
|
341
|
+
if (bindingExpired) {
|
|
342
|
+
const reconciliation = {
|
|
343
|
+
kind: "authorization_expired",
|
|
344
|
+
governingMoment: authorityMoment ?? "both",
|
|
345
|
+
executionReason,
|
|
346
|
+
...invocation.authorizationBindingId ? { authorizationBindingId: invocation.authorizationBindingId } : {},
|
|
347
|
+
...invocation.provenance ? { provenance: invocation.provenance } : {},
|
|
348
|
+
message: `Authorization binding for action ${action.actionId} expired before execution`
|
|
349
|
+
};
|
|
350
|
+
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
|
|
351
|
+
return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
|
|
352
|
+
}
|
|
353
|
+
const executionAuthorized = authorityMoment === "capture" ? invocation.authorizationBinding !== void 0 : options.authorization.authorizeExecution ? await options.authorization.authorizeExecution({
|
|
231
354
|
...authorizationInput,
|
|
232
355
|
actionInvocationId,
|
|
233
356
|
parameters: parsed.data,
|
|
@@ -235,11 +358,19 @@ function createGovernedActionHost(options) {
|
|
|
235
358
|
executionReason
|
|
236
359
|
}) : await options.authorization.authorize(authorizationInput);
|
|
237
360
|
if (!executionAuthorized) {
|
|
238
|
-
|
|
239
|
-
invocation,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
361
|
+
if (!action.execution) {
|
|
362
|
+
return fail(invocation, "failed", `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`);
|
|
363
|
+
}
|
|
364
|
+
const reconciliation = {
|
|
365
|
+
kind: "authorization_denied",
|
|
366
|
+
governingMoment: action.execution?.authorityMoment ?? "both",
|
|
367
|
+
executionReason,
|
|
368
|
+
...invocation.authorizationBindingId ? { authorizationBindingId: invocation.authorizationBindingId } : {},
|
|
369
|
+
...invocation.provenance ? { provenance: invocation.provenance } : {},
|
|
370
|
+
message: `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`
|
|
371
|
+
};
|
|
372
|
+
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
|
|
373
|
+
return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
|
|
243
374
|
}
|
|
244
375
|
const definitions = options.resolvePolicies ? await options.resolvePolicies({
|
|
245
376
|
...authorizationInput,
|
|
@@ -326,6 +457,9 @@ function createGovernedActionHost(options) {
|
|
|
326
457
|
let domainEvents = [];
|
|
327
458
|
try {
|
|
328
459
|
const runHandler = async (db, transaction) => {
|
|
460
|
+
if (options.outbox && !transaction?.appendEventWithOutbox) {
|
|
461
|
+
throw new Error("Outbox egress requires transactionWithEvents to provide appendEventWithOutbox before handler execution.");
|
|
462
|
+
}
|
|
329
463
|
const handlerResult = action.handler ? await action.handler(
|
|
330
464
|
{
|
|
331
465
|
actionInvocationId,
|
|
@@ -335,6 +469,7 @@ function createGovernedActionHost(options) {
|
|
|
335
469
|
actorType: invocation.actorType,
|
|
336
470
|
correlationId: invocation.correlationId,
|
|
337
471
|
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
472
|
+
...invocation.provenance ? { provenance: invocation.provenance } : {},
|
|
338
473
|
db,
|
|
339
474
|
services: options.services
|
|
340
475
|
},
|
|
@@ -689,13 +824,22 @@ function createGovernedActionHost(options) {
|
|
|
689
824
|
occurredAt: timestamp,
|
|
690
825
|
recordedAt: timestamp,
|
|
691
826
|
correlationId: invocation.correlationId,
|
|
692
|
-
...invocation.causationId ? { causationId: invocation.causationId } : {}
|
|
827
|
+
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
828
|
+
...invocation.provenance ? { provenance: { source: invocation.provenance.source, ...invocation.provenance.auditAttributes ? { auditAttributes: invocation.provenance.auditAttributes } : {} } } : {}
|
|
693
829
|
};
|
|
694
830
|
if (options.outbox) {
|
|
831
|
+
const hostLifecycleEvent = (/* @__PURE__ */ new Set(["AdapterInvocationStarted", "AdapterInvocationSucceeded", "AdapterInvocationFailed", "ComplianceBlocked"])).has(envelope.eventType);
|
|
832
|
+
const shouldPublish = options.outbox.shouldPublish?.(envelope) ?? !hostLifecycleEvent;
|
|
833
|
+
if (!shouldPublish) {
|
|
834
|
+
await (transaction ?? options.store).appendEvent(envelope);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
695
837
|
const traceContext = options.outbox.traceContext?.(envelope);
|
|
838
|
+
const payloadClassification = options.outbox.classifyPayload(envelope);
|
|
696
839
|
const metadata = {
|
|
697
840
|
producerModuleVersion: options.outbox.producerModuleVersion(invocation.actionId, invocation.actionVersion),
|
|
698
|
-
payloadClassification
|
|
841
|
+
payloadClassification,
|
|
842
|
+
includeProvenance: options.outbox.includeProvenance?.(envelope, payloadClassification) ?? payloadClassification !== "restricted",
|
|
699
843
|
...traceContext ? { traceContext } : {}
|
|
700
844
|
};
|
|
701
845
|
if (transaction) {
|
|
@@ -734,7 +878,8 @@ function actionResult(invocation) {
|
|
|
734
878
|
result: invocation.result,
|
|
735
879
|
...invocation.error ? { error: invocation.error } : {},
|
|
736
880
|
...invocation.hitlRoute ? { hitlRoute: invocation.hitlRoute } : {},
|
|
737
|
-
...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {}
|
|
881
|
+
...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {},
|
|
882
|
+
...invocation.authorizationReconciliation ? { reconciliation: invocation.authorizationReconciliation } : {}
|
|
738
883
|
};
|
|
739
884
|
}
|
|
740
885
|
function asApprovalStore(store) {
|
|
@@ -787,7 +932,7 @@ function initialState(entityType) {
|
|
|
787
932
|
return Object.values(machine?.states ?? {}).find((state) => state.stateClass === "initial")?.id ?? "none";
|
|
788
933
|
}
|
|
789
934
|
function isTerminal(status) {
|
|
790
|
-
return ["completed", "failed", "blocked_by_policy", "validation_failed"].includes(status);
|
|
935
|
+
return ["completed", "failed", "blocked_by_policy", "reconciliation_required", "validation_failed"].includes(status);
|
|
791
936
|
}
|
|
792
937
|
function withoutPrivateHostFields(data, eventResultFields) {
|
|
793
938
|
return Object.fromEntries(
|
|
@@ -804,6 +949,30 @@ function lifecycleId(prefix, invocationId, key) {
|
|
|
804
949
|
const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 96);
|
|
805
950
|
return `${prefix}_${invocationId}_${safeKey}`;
|
|
806
951
|
}
|
|
952
|
+
function validateAuthorizationBinding(input, parameterDigest, authorityMoment, currentTime = /* @__PURE__ */ new Date()) {
|
|
953
|
+
const binding = input.authorizationBinding;
|
|
954
|
+
if (binding.id !== input.authorizationBindingId && input.authorizationBindingId) throw new Error("Authorization binding ID does not match authorizationBindingId.");
|
|
955
|
+
if (binding.tenantId !== input.tenantId || binding.actorId !== input.actorId || binding.actionId !== input.actionId || binding.parameterDigest !== parameterDigest) {
|
|
956
|
+
throw new Error("Authorization binding does not match the submitted command identity.");
|
|
957
|
+
}
|
|
958
|
+
if (authorityMoment && binding.governingMoment !== authorityMoment) throw new Error("Authorization binding governingMoment does not match the action execution contract.");
|
|
959
|
+
const capturedAt = Date.parse(binding.capturedAt);
|
|
960
|
+
const expiresAt = binding.expiresAt === void 0 ? void 0 : Date.parse(binding.expiresAt);
|
|
961
|
+
if (!Number.isFinite(capturedAt) || expiresAt !== void 0 && !Number.isFinite(expiresAt)) {
|
|
962
|
+
throw new Error("Authorization binding timestamps must be valid ISO-8601 values.");
|
|
963
|
+
}
|
|
964
|
+
if (expiresAt !== void 0 && (expiresAt <= capturedAt || authorityMoment === "capture" && expiresAt <= currentTime.getTime())) {
|
|
965
|
+
throw new Error("Authorization binding must be unexpired at capture and expire after capturedAt.");
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function idempotencyConflict(existing, incoming) {
|
|
969
|
+
const reasons = [];
|
|
970
|
+
if ((existing.idempotencyActorId ?? existing.actorId) !== incoming.actorId) reasons.push("actor");
|
|
971
|
+
if ((existing.idempotencyAuthorizationBindingId ?? existing.authorizationBindingId) !== incoming.authorizationBindingId) reasons.push("authority_binding");
|
|
972
|
+
if (existing.actionVersion !== incoming.actionVersion) reasons.push("action_version");
|
|
973
|
+
if (existing.parameterDigest !== incoming.parameterDigest) reasons.push("parameters");
|
|
974
|
+
return reasons.length ? { code: "IDEMPOTENCY_CONFLICT", idempotencyKey: incoming.idempotencyKey, existingInvocationId: existing.id, reasons } : void 0;
|
|
975
|
+
}
|
|
807
976
|
|
|
808
977
|
// src/outbox.ts
|
|
809
978
|
function toEnterpriseEventEnvelope(event, metadata) {
|
|
@@ -824,6 +993,7 @@ function toEnterpriseEventEnvelope(event, metadata) {
|
|
|
824
993
|
producerModuleVersion: metadata.producerModuleVersion,
|
|
825
994
|
payloadClassification: metadata.payloadClassification,
|
|
826
995
|
...metadata.traceContext ? { traceContext: { ...metadata.traceContext } } : {},
|
|
996
|
+
...metadata.includeProvenance && event.provenance ? { provenance: event.provenance } : {},
|
|
827
997
|
payload: event.payload
|
|
828
998
|
};
|
|
829
999
|
}
|
|
@@ -847,13 +1017,13 @@ async function runOutboxRelayCycle(options) {
|
|
|
847
1017
|
continue;
|
|
848
1018
|
}
|
|
849
1019
|
result.published += 1;
|
|
850
|
-
} catch
|
|
1020
|
+
} catch {
|
|
851
1021
|
const deadLetter = record.attemptCount >= maxAttempts;
|
|
852
1022
|
try {
|
|
853
1023
|
await options.store.markOutboxFailed({
|
|
854
1024
|
id: record.id,
|
|
855
1025
|
workerId: options.workerId,
|
|
856
|
-
error:
|
|
1026
|
+
error: "Event publisher failed",
|
|
857
1027
|
availableAt: new Date(now().getTime() + (options.retryDelayMs?.(record.attemptCount) ?? 1e3)),
|
|
858
1028
|
deadLetter
|
|
859
1029
|
});
|
|
@@ -874,10 +1044,14 @@ function cloneOutboxRecord(record) {
|
|
|
874
1044
|
|
|
875
1045
|
// src/memory-store.ts
|
|
876
1046
|
var MemoryPlatformHostStore = class {
|
|
877
|
-
constructor(db) {
|
|
1047
|
+
constructor(db, transactionProvider) {
|
|
878
1048
|
this.db = db;
|
|
1049
|
+
this.transactionalOutbox = transactionProvider !== void 0;
|
|
1050
|
+
if (transactionProvider) this.transactionWithEvents = (run) => this.runTransactionWithEvents(run, transactionProvider);
|
|
879
1051
|
}
|
|
880
1052
|
db;
|
|
1053
|
+
transactionalOutbox;
|
|
1054
|
+
transactionTail = Promise.resolve();
|
|
881
1055
|
invocations = [];
|
|
882
1056
|
policyEvaluations = [];
|
|
883
1057
|
adapterInvocations = [];
|
|
@@ -889,6 +1063,50 @@ var MemoryPlatformHostStore = class {
|
|
|
889
1063
|
async transaction(run) {
|
|
890
1064
|
return run(this.db);
|
|
891
1065
|
}
|
|
1066
|
+
async runTransactionWithEvents(run, transactionProvider) {
|
|
1067
|
+
let release;
|
|
1068
|
+
const previous = this.transactionTail;
|
|
1069
|
+
this.transactionTail = new Promise((resolve) => {
|
|
1070
|
+
release = resolve;
|
|
1071
|
+
});
|
|
1072
|
+
await previous;
|
|
1073
|
+
let domainSnapshot;
|
|
1074
|
+
let snapshotCreated = false;
|
|
1075
|
+
try {
|
|
1076
|
+
domainSnapshot = transactionProvider.snapshot(this.db);
|
|
1077
|
+
snapshotCreated = true;
|
|
1078
|
+
const pendingEvents = [];
|
|
1079
|
+
const pendingUpdates = [];
|
|
1080
|
+
const appendPending = async (event, metadata) => {
|
|
1081
|
+
if (this.events.some((candidate) => candidate.id === event.id) || pendingEvents.some((candidate) => candidate.event.id === event.id)) return;
|
|
1082
|
+
pendingEvents.push({ event, ...metadata ? { metadata } : {} });
|
|
1083
|
+
};
|
|
1084
|
+
const result = await run({
|
|
1085
|
+
db: this.db,
|
|
1086
|
+
appendEvent: (event) => appendPending(event),
|
|
1087
|
+
appendEventWithOutbox: (event, metadata) => appendPending(event, metadata),
|
|
1088
|
+
nextEventSequence: async (tenantId, spaceId) => (await this.listEvents(tenantId, spaceId)).length + pendingEvents.filter((candidate) => candidate.event.tenantId === tenantId && candidate.event.spaceId === spaceId).length + 1,
|
|
1089
|
+
listEvents: async (tenantId, spaceId) => [...await this.listEvents(tenantId, spaceId), ...pendingEvents.map((candidate) => candidate.event).filter((event) => event.tenantId === tenantId && event.spaceId === spaceId)],
|
|
1090
|
+
updateActionInvocation: async (id, tenantId, spaceId, patch) => {
|
|
1091
|
+
pendingUpdates.push({ id, tenantId, spaceId, patch });
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
for (const update of pendingUpdates) {
|
|
1095
|
+
if (!await this.getActionInvocation(update.id, update.tenantId, update.spaceId)) throw new Error(`ActionInvocation not found: ${update.id}`);
|
|
1096
|
+
}
|
|
1097
|
+
for (const pending of pendingEvents) {
|
|
1098
|
+
if (pending.metadata) await this.appendEventWithOutbox(pending.event, pending.metadata);
|
|
1099
|
+
else await this.appendEvent(pending.event);
|
|
1100
|
+
}
|
|
1101
|
+
for (const update of pendingUpdates) await this.updateActionInvocation(update.id, update.tenantId, update.spaceId, update.patch);
|
|
1102
|
+
return result;
|
|
1103
|
+
} catch (error) {
|
|
1104
|
+
if (snapshotCreated) transactionProvider.restore(this.db, domainSnapshot);
|
|
1105
|
+
throw error;
|
|
1106
|
+
} finally {
|
|
1107
|
+
release();
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
892
1110
|
async createActionInvocation(input) {
|
|
893
1111
|
if (input.idempotencyKey) {
|
|
894
1112
|
const existing = this.invocations.find(
|
|
@@ -910,7 +1128,7 @@ var MemoryPlatformHostStore = class {
|
|
|
910
1128
|
const record = await this.getActionInvocation(id, tenantId, spaceId);
|
|
911
1129
|
if (!record) throw new Error(`ActionInvocation not found: ${id}`);
|
|
912
1130
|
Object.assign(record, patch, { updatedAt: /* @__PURE__ */ new Date() });
|
|
913
|
-
if (patch.status === "waiting_for_approval" || patch.status === "completed" || patch.status === "failed" || patch.status === "blocked_by_policy" || patch.status === "validation_failed") {
|
|
1131
|
+
if (patch.status === "waiting_for_approval" || patch.status === "completed" || patch.status === "failed" || patch.status === "blocked_by_policy" || patch.status === "reconciliation_required" || patch.status === "validation_failed") {
|
|
914
1132
|
delete record.leaseOwner;
|
|
915
1133
|
delete record.leaseExpiresAt;
|
|
916
1134
|
}
|
|
@@ -1122,6 +1340,9 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1122
1340
|
actor_id text NOT NULL, actor_type text NOT NULL, status text NOT NULL,
|
|
1123
1341
|
parameters jsonb NOT NULL, result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1124
1342
|
correlation_id text NOT NULL, causation_id text, idempotency_key text,
|
|
1343
|
+
parameter_digest text, parameter_digest_algorithm text, idempotency_actor_id text,
|
|
1344
|
+
idempotency_authorization_binding_id text, invocation_provenance jsonb,
|
|
1345
|
+
authorization_binding jsonb, execution_reason text, authorization_reconciliation jsonb,
|
|
1125
1346
|
authorization_binding_id text, error text,
|
|
1126
1347
|
attempt_count integer NOT NULL DEFAULT 0, lease_owner text,
|
|
1127
1348
|
lease_expires_at timestamptz, hitl_route text, hitl_risk_tier text,
|
|
@@ -1131,6 +1352,14 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1131
1352
|
);
|
|
1132
1353
|
ALTER TABLE fabric_platform.action_invocations
|
|
1133
1354
|
ADD COLUMN IF NOT EXISTS idempotency_key text,
|
|
1355
|
+
ADD COLUMN IF NOT EXISTS parameter_digest text,
|
|
1356
|
+
ADD COLUMN IF NOT EXISTS parameter_digest_algorithm text,
|
|
1357
|
+
ADD COLUMN IF NOT EXISTS idempotency_actor_id text,
|
|
1358
|
+
ADD COLUMN IF NOT EXISTS idempotency_authorization_binding_id text,
|
|
1359
|
+
ADD COLUMN IF NOT EXISTS invocation_provenance jsonb,
|
|
1360
|
+
ADD COLUMN IF NOT EXISTS authorization_binding jsonb,
|
|
1361
|
+
ADD COLUMN IF NOT EXISTS execution_reason text,
|
|
1362
|
+
ADD COLUMN IF NOT EXISTS authorization_reconciliation jsonb,
|
|
1134
1363
|
ADD COLUMN IF NOT EXISTS authorization_binding_id text,
|
|
1135
1364
|
ADD COLUMN IF NOT EXISTS attempt_count integer NOT NULL DEFAULT 0,
|
|
1136
1365
|
ADD COLUMN IF NOT EXISTS lease_owner text,
|
|
@@ -1147,6 +1376,12 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1147
1376
|
ON fabric_platform.action_invocations
|
|
1148
1377
|
(tenant_id, space_id, action_id, idempotency_key)
|
|
1149
1378
|
WHERE idempotency_key IS NOT NULL;
|
|
1379
|
+
CREATE INDEX IF NOT EXISTS action_invocations_parameter_digest_idx
|
|
1380
|
+
ON fabric_platform.action_invocations (tenant_id, parameter_digest)
|
|
1381
|
+
WHERE parameter_digest IS NOT NULL;
|
|
1382
|
+
CREATE INDEX IF NOT EXISTS action_invocations_authority_binding_idx
|
|
1383
|
+
ON fabric_platform.action_invocations (tenant_id, authorization_binding_id)
|
|
1384
|
+
WHERE authorization_binding_id IS NOT NULL;
|
|
1150
1385
|
CREATE INDEX IF NOT EXISTS action_invocations_worker_idx
|
|
1151
1386
|
ON fabric_platform.action_invocations (status, lease_expires_at, created_at);
|
|
1152
1387
|
CREATE TABLE IF NOT EXISTS fabric_platform.policy_evaluations (
|
|
@@ -1190,9 +1425,10 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1190
1425
|
actor_id text NOT NULL, actor_type text NOT NULL, action_invocation_id text,
|
|
1191
1426
|
payload jsonb NOT NULL, sequence bigint NOT NULL,
|
|
1192
1427
|
occurred_at timestamptz NOT NULL, recorded_at timestamptz NOT NULL,
|
|
1193
|
-
correlation_id text NOT NULL, causation_id text,
|
|
1428
|
+
correlation_id text NOT NULL, causation_id text, provenance jsonb,
|
|
1194
1429
|
UNIQUE (tenant_id, space_id, sequence)
|
|
1195
1430
|
);
|
|
1431
|
+
ALTER TABLE fabric_platform.asset_events ADD COLUMN IF NOT EXISTS provenance jsonb;
|
|
1196
1432
|
CREATE INDEX IF NOT EXISTS asset_events_subject_idx
|
|
1197
1433
|
ON fabric_platform.asset_events
|
|
1198
1434
|
(tenant_id, space_id, subject_type, subject_id, sequence);
|
|
@@ -1216,8 +1452,10 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1216
1452
|
`INSERT INTO fabric_platform.action_invocations
|
|
1217
1453
|
(id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
|
|
1218
1454
|
parameters,result,correlation_id,causation_id,idempotency_key,
|
|
1455
|
+
parameter_digest,parameter_digest_algorithm,idempotency_actor_id,
|
|
1456
|
+
idempotency_authorization_binding_id,invocation_provenance,authorization_binding,execution_reason,
|
|
1219
1457
|
authorization_binding_id,error,runtime_evidence,created_at,updated_at)
|
|
1220
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16::jsonb,$
|
|
1458
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16,$17,$18::jsonb,$19::jsonb,$20,$21,$22,$23::jsonb,$24,$24)
|
|
1221
1459
|
ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
|
|
1222
1460
|
WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
1223
1461
|
RETURNING *`,
|
|
@@ -1235,6 +1473,13 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1235
1473
|
input.correlationId,
|
|
1236
1474
|
input.causationId ?? null,
|
|
1237
1475
|
input.idempotencyKey ?? null,
|
|
1476
|
+
input.parameterDigest ?? null,
|
|
1477
|
+
input.parameterDigestAlgorithm ?? null,
|
|
1478
|
+
input.idempotencyActorId ?? null,
|
|
1479
|
+
input.idempotencyAuthorizationBindingId ?? null,
|
|
1480
|
+
input.provenance ? JSON.stringify(input.provenance) : null,
|
|
1481
|
+
input.authorizationBinding ? JSON.stringify(input.authorizationBinding) : null,
|
|
1482
|
+
input.executionReason ?? null,
|
|
1238
1483
|
input.authorizationBindingId ?? null,
|
|
1239
1484
|
input.error ?? null,
|
|
1240
1485
|
input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
|
|
@@ -1256,9 +1501,10 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1256
1501
|
`UPDATE fabric_platform.action_invocations SET
|
|
1257
1502
|
status=COALESCE($4,status), result=COALESCE($5::jsonb,result),
|
|
1258
1503
|
error=CASE WHEN $6::boolean THEN $7 ELSE error END,
|
|
1259
|
-
|
|
1504
|
+
authorization_reconciliation=CASE WHEN $8::boolean THEN $9::jsonb ELSE authorization_reconciliation END,
|
|
1505
|
+
lease_owner=CASE WHEN $4 IN ('waiting_for_approval','completed','failed','blocked_by_policy','reconciliation_required','validation_failed')
|
|
1260
1506
|
THEN NULL ELSE lease_owner END,
|
|
1261
|
-
lease_expires_at=CASE WHEN $4 IN ('waiting_for_approval','completed','failed','blocked_by_policy','validation_failed')
|
|
1507
|
+
lease_expires_at=CASE WHEN $4 IN ('waiting_for_approval','completed','failed','blocked_by_policy','reconciliation_required','validation_failed')
|
|
1262
1508
|
THEN NULL ELSE lease_expires_at END,
|
|
1263
1509
|
updated_at=now()
|
|
1264
1510
|
WHERE id=$1 AND tenant_id=$2 AND space_id=$3`,
|
|
@@ -1269,7 +1515,9 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1269
1515
|
patch.status ?? null,
|
|
1270
1516
|
patch.result === void 0 ? null : JSON.stringify(patch.result),
|
|
1271
1517
|
Object.hasOwn(patch, "error"),
|
|
1272
|
-
patch.error ?? null
|
|
1518
|
+
patch.error ?? null,
|
|
1519
|
+
Object.hasOwn(patch, "authorizationReconciliation"),
|
|
1520
|
+
patch.authorizationReconciliation ? JSON.stringify(patch.authorizationReconciliation) : null
|
|
1273
1521
|
]
|
|
1274
1522
|
);
|
|
1275
1523
|
}
|
|
@@ -1464,8 +1712,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1464
1712
|
`INSERT INTO fabric_platform.asset_events
|
|
1465
1713
|
(id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
|
|
1466
1714
|
actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
|
|
1467
|
-
correlation_id,causation_id)
|
|
1468
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16)
|
|
1715
|
+
correlation_id,causation_id,provenance)
|
|
1716
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb)
|
|
1469
1717
|
ON CONFLICT (id) DO NOTHING`,
|
|
1470
1718
|
[
|
|
1471
1719
|
event.id,
|
|
@@ -1483,7 +1731,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1483
1731
|
event.occurredAt,
|
|
1484
1732
|
event.recordedAt,
|
|
1485
1733
|
event.correlationId,
|
|
1486
|
-
event.causationId ?? null
|
|
1734
|
+
event.causationId ?? null,
|
|
1735
|
+
event.provenance ? JSON.stringify(event.provenance) : null
|
|
1487
1736
|
]
|
|
1488
1737
|
);
|
|
1489
1738
|
}
|
|
@@ -1494,13 +1743,13 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1494
1743
|
INSERT INTO fabric_platform.asset_events
|
|
1495
1744
|
(id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
|
|
1496
1745
|
actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
|
|
1497
|
-
correlation_id,causation_id)
|
|
1498
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16)
|
|
1746
|
+
correlation_id,causation_id,provenance)
|
|
1747
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb)
|
|
1499
1748
|
ON CONFLICT (id) DO NOTHING RETURNING id
|
|
1500
1749
|
)
|
|
1501
1750
|
INSERT INTO fabric_platform.event_outbox
|
|
1502
1751
|
(id,tenant_id,space_id,event,status,attempt_count,available_at,created_at)
|
|
1503
|
-
SELECT $1,$2,$3,$
|
|
1752
|
+
SELECT $1,$2,$3,$18::jsonb,'pending',0,$14,$14 FROM inserted_event
|
|
1504
1753
|
ON CONFLICT (id) DO NOTHING`,
|
|
1505
1754
|
[
|
|
1506
1755
|
event.id,
|
|
@@ -1519,6 +1768,7 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1519
1768
|
event.recordedAt,
|
|
1520
1769
|
event.correlationId,
|
|
1521
1770
|
event.causationId ?? null,
|
|
1771
|
+
event.provenance ? JSON.stringify(event.provenance) : null,
|
|
1522
1772
|
JSON.stringify(envelope)
|
|
1523
1773
|
]
|
|
1524
1774
|
);
|
|
@@ -1679,6 +1929,14 @@ function toActionRecord(row) {
|
|
|
1679
1929
|
correlationId: String(row.correlation_id),
|
|
1680
1930
|
...row.causation_id ? { causationId: String(row.causation_id) } : {},
|
|
1681
1931
|
...row.idempotency_key ? { idempotencyKey: String(row.idempotency_key) } : {},
|
|
1932
|
+
...row.parameter_digest ? { parameterDigest: String(row.parameter_digest) } : {},
|
|
1933
|
+
...row.parameter_digest_algorithm ? { parameterDigestAlgorithm: String(row.parameter_digest_algorithm) } : {},
|
|
1934
|
+
...row.idempotency_actor_id ? { idempotencyActorId: String(row.idempotency_actor_id) } : {},
|
|
1935
|
+
...row.idempotency_authorization_binding_id ? { idempotencyAuthorizationBindingId: String(row.idempotency_authorization_binding_id) } : {},
|
|
1936
|
+
...row.invocation_provenance ? { provenance: row.invocation_provenance } : {},
|
|
1937
|
+
...row.authorization_binding ? { authorizationBinding: row.authorization_binding } : {},
|
|
1938
|
+
...row.execution_reason ? { executionReason: String(row.execution_reason) } : {},
|
|
1939
|
+
...row.authorization_reconciliation ? { authorizationReconciliation: row.authorization_reconciliation } : {},
|
|
1682
1940
|
...row.authorization_binding_id ? { authorizationBindingId: String(row.authorization_binding_id) } : {},
|
|
1683
1941
|
attemptCount: Number(row.attempt_count ?? 0),
|
|
1684
1942
|
...row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {},
|
|
@@ -1752,7 +2010,8 @@ function toEventRecord(row) {
|
|
|
1752
2010
|
occurredAt: new Date(row.occurred_at),
|
|
1753
2011
|
recordedAt: new Date(row.recorded_at),
|
|
1754
2012
|
correlationId: String(row.correlation_id),
|
|
1755
|
-
...row.causation_id ? { causationId: String(row.causation_id) } : {}
|
|
2013
|
+
...row.causation_id ? { causationId: String(row.causation_id) } : {},
|
|
2014
|
+
...row.provenance ? { provenance: row.provenance } : {}
|
|
1756
2015
|
};
|
|
1757
2016
|
}
|
|
1758
2017
|
function toOutboxRecord(row) {
|
|
@@ -1773,6 +2032,7 @@ function toOutboxRecord(row) {
|
|
|
1773
2032
|
...event.actionInvocationId ? { actionInvocationId: String(event.actionInvocationId) } : {},
|
|
1774
2033
|
correlationId: String(event.correlationId),
|
|
1775
2034
|
...event.causationId ? { causationId: String(event.causationId) } : {},
|
|
2035
|
+
...event.provenance ? { provenance: event.provenance } : {},
|
|
1776
2036
|
occurredAt: new Date(event.occurredAt),
|
|
1777
2037
|
recordedAt: new Date(event.recordedAt),
|
|
1778
2038
|
producerModuleVersion: String(event.producerModuleVersion),
|
|
@@ -1859,6 +2119,6 @@ async function abortableDelay(milliseconds, signal) {
|
|
|
1859
2119
|
});
|
|
1860
2120
|
}
|
|
1861
2121
|
|
|
1862
|
-
export { MemoryPlatformHostStore, PLATFORM_HOST_CONTRACT_VERSION, PostgresPlatformHostStore, cloneOutboxRecord, createGovernedActionHost, createStoreBackedActionDispatcher, runOutboxRelayCycle, runPlatformActionWorker, runPlatformActionWorkerCycle, toEnterpriseEventEnvelope };
|
|
2122
|
+
export { IdempotencyConflictError, MemoryPlatformHostStore, PARAMETER_DIGEST_ALGORITHM, PLATFORM_HOST_CONTRACT_VERSION, PostgresPlatformHostStore, canonicalJson, cloneOutboxRecord, createGovernedActionHost, createStoreBackedActionDispatcher, digestParameters, runOutboxRelayCycle, runPlatformActionWorker, runPlatformActionWorkerCycle, toEnterpriseEventEnvelope };
|
|
1863
2123
|
//# sourceMappingURL=index.js.map
|
|
1864
2124
|
//# sourceMappingURL=index.js.map
|