@fabricorg/platform-host 2.0.1 → 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 +15 -0
- package/MIGRATION-2-IDEMPOTENCY.md +32 -0
- package/README.md +23 -3
- package/dist/index.cjs +235 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +83 -7
- package/dist/index.d.ts +83 -7
- package/dist/index.js +232 -29
- 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,
|
|
@@ -338,6 +469,7 @@ function createGovernedActionHost(options) {
|
|
|
338
469
|
actorType: invocation.actorType,
|
|
339
470
|
correlationId: invocation.correlationId,
|
|
340
471
|
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
472
|
+
...invocation.provenance ? { provenance: invocation.provenance } : {},
|
|
341
473
|
db,
|
|
342
474
|
services: options.services
|
|
343
475
|
},
|
|
@@ -692,7 +824,8 @@ function createGovernedActionHost(options) {
|
|
|
692
824
|
occurredAt: timestamp,
|
|
693
825
|
recordedAt: timestamp,
|
|
694
826
|
correlationId: invocation.correlationId,
|
|
695
|
-
...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 } : {} } } : {}
|
|
696
829
|
};
|
|
697
830
|
if (options.outbox) {
|
|
698
831
|
const hostLifecycleEvent = (/* @__PURE__ */ new Set(["AdapterInvocationStarted", "AdapterInvocationSucceeded", "AdapterInvocationFailed", "ComplianceBlocked"])).has(envelope.eventType);
|
|
@@ -702,9 +835,11 @@ function createGovernedActionHost(options) {
|
|
|
702
835
|
return;
|
|
703
836
|
}
|
|
704
837
|
const traceContext = options.outbox.traceContext?.(envelope);
|
|
838
|
+
const payloadClassification = options.outbox.classifyPayload(envelope);
|
|
705
839
|
const metadata = {
|
|
706
840
|
producerModuleVersion: options.outbox.producerModuleVersion(invocation.actionId, invocation.actionVersion),
|
|
707
|
-
payloadClassification
|
|
841
|
+
payloadClassification,
|
|
842
|
+
includeProvenance: options.outbox.includeProvenance?.(envelope, payloadClassification) ?? payloadClassification !== "restricted",
|
|
708
843
|
...traceContext ? { traceContext } : {}
|
|
709
844
|
};
|
|
710
845
|
if (transaction) {
|
|
@@ -743,7 +878,8 @@ function actionResult(invocation) {
|
|
|
743
878
|
result: invocation.result,
|
|
744
879
|
...invocation.error ? { error: invocation.error } : {},
|
|
745
880
|
...invocation.hitlRoute ? { hitlRoute: invocation.hitlRoute } : {},
|
|
746
|
-
...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {}
|
|
881
|
+
...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {},
|
|
882
|
+
...invocation.authorizationReconciliation ? { reconciliation: invocation.authorizationReconciliation } : {}
|
|
747
883
|
};
|
|
748
884
|
}
|
|
749
885
|
function asApprovalStore(store) {
|
|
@@ -796,7 +932,7 @@ function initialState(entityType) {
|
|
|
796
932
|
return Object.values(machine?.states ?? {}).find((state) => state.stateClass === "initial")?.id ?? "none";
|
|
797
933
|
}
|
|
798
934
|
function isTerminal(status) {
|
|
799
|
-
return ["completed", "failed", "blocked_by_policy", "validation_failed"].includes(status);
|
|
935
|
+
return ["completed", "failed", "blocked_by_policy", "reconciliation_required", "validation_failed"].includes(status);
|
|
800
936
|
}
|
|
801
937
|
function withoutPrivateHostFields(data, eventResultFields) {
|
|
802
938
|
return Object.fromEntries(
|
|
@@ -813,6 +949,30 @@ function lifecycleId(prefix, invocationId, key) {
|
|
|
813
949
|
const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 96);
|
|
814
950
|
return `${prefix}_${invocationId}_${safeKey}`;
|
|
815
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
|
+
}
|
|
816
976
|
|
|
817
977
|
// src/outbox.ts
|
|
818
978
|
function toEnterpriseEventEnvelope(event, metadata) {
|
|
@@ -833,6 +993,7 @@ function toEnterpriseEventEnvelope(event, metadata) {
|
|
|
833
993
|
producerModuleVersion: metadata.producerModuleVersion,
|
|
834
994
|
payloadClassification: metadata.payloadClassification,
|
|
835
995
|
...metadata.traceContext ? { traceContext: { ...metadata.traceContext } } : {},
|
|
996
|
+
...metadata.includeProvenance && event.provenance ? { provenance: event.provenance } : {},
|
|
836
997
|
payload: event.payload
|
|
837
998
|
};
|
|
838
999
|
}
|
|
@@ -967,7 +1128,7 @@ var MemoryPlatformHostStore = class {
|
|
|
967
1128
|
const record = await this.getActionInvocation(id, tenantId, spaceId);
|
|
968
1129
|
if (!record) throw new Error(`ActionInvocation not found: ${id}`);
|
|
969
1130
|
Object.assign(record, patch, { updatedAt: /* @__PURE__ */ new Date() });
|
|
970
|
-
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") {
|
|
971
1132
|
delete record.leaseOwner;
|
|
972
1133
|
delete record.leaseExpiresAt;
|
|
973
1134
|
}
|
|
@@ -1179,6 +1340,9 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1179
1340
|
actor_id text NOT NULL, actor_type text NOT NULL, status text NOT NULL,
|
|
1180
1341
|
parameters jsonb NOT NULL, result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1181
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,
|
|
1182
1346
|
authorization_binding_id text, error text,
|
|
1183
1347
|
attempt_count integer NOT NULL DEFAULT 0, lease_owner text,
|
|
1184
1348
|
lease_expires_at timestamptz, hitl_route text, hitl_risk_tier text,
|
|
@@ -1188,6 +1352,14 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1188
1352
|
);
|
|
1189
1353
|
ALTER TABLE fabric_platform.action_invocations
|
|
1190
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,
|
|
1191
1363
|
ADD COLUMN IF NOT EXISTS authorization_binding_id text,
|
|
1192
1364
|
ADD COLUMN IF NOT EXISTS attempt_count integer NOT NULL DEFAULT 0,
|
|
1193
1365
|
ADD COLUMN IF NOT EXISTS lease_owner text,
|
|
@@ -1204,6 +1376,12 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1204
1376
|
ON fabric_platform.action_invocations
|
|
1205
1377
|
(tenant_id, space_id, action_id, idempotency_key)
|
|
1206
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;
|
|
1207
1385
|
CREATE INDEX IF NOT EXISTS action_invocations_worker_idx
|
|
1208
1386
|
ON fabric_platform.action_invocations (status, lease_expires_at, created_at);
|
|
1209
1387
|
CREATE TABLE IF NOT EXISTS fabric_platform.policy_evaluations (
|
|
@@ -1247,9 +1425,10 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1247
1425
|
actor_id text NOT NULL, actor_type text NOT NULL, action_invocation_id text,
|
|
1248
1426
|
payload jsonb NOT NULL, sequence bigint NOT NULL,
|
|
1249
1427
|
occurred_at timestamptz NOT NULL, recorded_at timestamptz NOT NULL,
|
|
1250
|
-
correlation_id text NOT NULL, causation_id text,
|
|
1428
|
+
correlation_id text NOT NULL, causation_id text, provenance jsonb,
|
|
1251
1429
|
UNIQUE (tenant_id, space_id, sequence)
|
|
1252
1430
|
);
|
|
1431
|
+
ALTER TABLE fabric_platform.asset_events ADD COLUMN IF NOT EXISTS provenance jsonb;
|
|
1253
1432
|
CREATE INDEX IF NOT EXISTS asset_events_subject_idx
|
|
1254
1433
|
ON fabric_platform.asset_events
|
|
1255
1434
|
(tenant_id, space_id, subject_type, subject_id, sequence);
|
|
@@ -1273,8 +1452,10 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1273
1452
|
`INSERT INTO fabric_platform.action_invocations
|
|
1274
1453
|
(id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
|
|
1275
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,
|
|
1276
1457
|
authorization_binding_id,error,runtime_evidence,created_at,updated_at)
|
|
1277
|
-
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)
|
|
1278
1459
|
ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
|
|
1279
1460
|
WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
1280
1461
|
RETURNING *`,
|
|
@@ -1292,6 +1473,13 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1292
1473
|
input.correlationId,
|
|
1293
1474
|
input.causationId ?? null,
|
|
1294
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,
|
|
1295
1483
|
input.authorizationBindingId ?? null,
|
|
1296
1484
|
input.error ?? null,
|
|
1297
1485
|
input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
|
|
@@ -1313,9 +1501,10 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1313
1501
|
`UPDATE fabric_platform.action_invocations SET
|
|
1314
1502
|
status=COALESCE($4,status), result=COALESCE($5::jsonb,result),
|
|
1315
1503
|
error=CASE WHEN $6::boolean THEN $7 ELSE error END,
|
|
1316
|
-
|
|
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')
|
|
1317
1506
|
THEN NULL ELSE lease_owner END,
|
|
1318
|
-
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')
|
|
1319
1508
|
THEN NULL ELSE lease_expires_at END,
|
|
1320
1509
|
updated_at=now()
|
|
1321
1510
|
WHERE id=$1 AND tenant_id=$2 AND space_id=$3`,
|
|
@@ -1326,7 +1515,9 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1326
1515
|
patch.status ?? null,
|
|
1327
1516
|
patch.result === void 0 ? null : JSON.stringify(patch.result),
|
|
1328
1517
|
Object.hasOwn(patch, "error"),
|
|
1329
|
-
patch.error ?? null
|
|
1518
|
+
patch.error ?? null,
|
|
1519
|
+
Object.hasOwn(patch, "authorizationReconciliation"),
|
|
1520
|
+
patch.authorizationReconciliation ? JSON.stringify(patch.authorizationReconciliation) : null
|
|
1330
1521
|
]
|
|
1331
1522
|
);
|
|
1332
1523
|
}
|
|
@@ -1521,8 +1712,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1521
1712
|
`INSERT INTO fabric_platform.asset_events
|
|
1522
1713
|
(id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
|
|
1523
1714
|
actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
|
|
1524
|
-
correlation_id,causation_id)
|
|
1525
|
-
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)
|
|
1526
1717
|
ON CONFLICT (id) DO NOTHING`,
|
|
1527
1718
|
[
|
|
1528
1719
|
event.id,
|
|
@@ -1540,7 +1731,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1540
1731
|
event.occurredAt,
|
|
1541
1732
|
event.recordedAt,
|
|
1542
1733
|
event.correlationId,
|
|
1543
|
-
event.causationId ?? null
|
|
1734
|
+
event.causationId ?? null,
|
|
1735
|
+
event.provenance ? JSON.stringify(event.provenance) : null
|
|
1544
1736
|
]
|
|
1545
1737
|
);
|
|
1546
1738
|
}
|
|
@@ -1551,13 +1743,13 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1551
1743
|
INSERT INTO fabric_platform.asset_events
|
|
1552
1744
|
(id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
|
|
1553
1745
|
actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
|
|
1554
|
-
correlation_id,causation_id)
|
|
1555
|
-
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)
|
|
1556
1748
|
ON CONFLICT (id) DO NOTHING RETURNING id
|
|
1557
1749
|
)
|
|
1558
1750
|
INSERT INTO fabric_platform.event_outbox
|
|
1559
1751
|
(id,tenant_id,space_id,event,status,attempt_count,available_at,created_at)
|
|
1560
|
-
SELECT $1,$2,$3,$
|
|
1752
|
+
SELECT $1,$2,$3,$18::jsonb,'pending',0,$14,$14 FROM inserted_event
|
|
1561
1753
|
ON CONFLICT (id) DO NOTHING`,
|
|
1562
1754
|
[
|
|
1563
1755
|
event.id,
|
|
@@ -1576,6 +1768,7 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1576
1768
|
event.recordedAt,
|
|
1577
1769
|
event.correlationId,
|
|
1578
1770
|
event.causationId ?? null,
|
|
1771
|
+
event.provenance ? JSON.stringify(event.provenance) : null,
|
|
1579
1772
|
JSON.stringify(envelope)
|
|
1580
1773
|
]
|
|
1581
1774
|
);
|
|
@@ -1736,6 +1929,14 @@ function toActionRecord(row) {
|
|
|
1736
1929
|
correlationId: String(row.correlation_id),
|
|
1737
1930
|
...row.causation_id ? { causationId: String(row.causation_id) } : {},
|
|
1738
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 } : {},
|
|
1739
1940
|
...row.authorization_binding_id ? { authorizationBindingId: String(row.authorization_binding_id) } : {},
|
|
1740
1941
|
attemptCount: Number(row.attempt_count ?? 0),
|
|
1741
1942
|
...row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {},
|
|
@@ -1809,7 +2010,8 @@ function toEventRecord(row) {
|
|
|
1809
2010
|
occurredAt: new Date(row.occurred_at),
|
|
1810
2011
|
recordedAt: new Date(row.recorded_at),
|
|
1811
2012
|
correlationId: String(row.correlation_id),
|
|
1812
|
-
...row.causation_id ? { causationId: String(row.causation_id) } : {}
|
|
2013
|
+
...row.causation_id ? { causationId: String(row.causation_id) } : {},
|
|
2014
|
+
...row.provenance ? { provenance: row.provenance } : {}
|
|
1813
2015
|
};
|
|
1814
2016
|
}
|
|
1815
2017
|
function toOutboxRecord(row) {
|
|
@@ -1830,6 +2032,7 @@ function toOutboxRecord(row) {
|
|
|
1830
2032
|
...event.actionInvocationId ? { actionInvocationId: String(event.actionInvocationId) } : {},
|
|
1831
2033
|
correlationId: String(event.correlationId),
|
|
1832
2034
|
...event.causationId ? { causationId: String(event.causationId) } : {},
|
|
2035
|
+
...event.provenance ? { provenance: event.provenance } : {},
|
|
1833
2036
|
occurredAt: new Date(event.occurredAt),
|
|
1834
2037
|
recordedAt: new Date(event.recordedAt),
|
|
1835
2038
|
producerModuleVersion: String(event.producerModuleVersion),
|
|
@@ -1916,6 +2119,6 @@ async function abortableDelay(milliseconds, signal) {
|
|
|
1916
2119
|
});
|
|
1917
2120
|
}
|
|
1918
2121
|
|
|
1919
|
-
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 };
|
|
1920
2123
|
//# sourceMappingURL=index.js.map
|
|
1921
2124
|
//# sourceMappingURL=index.js.map
|