@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @fabricorg/platform-host
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- Deliver Phase 1 correctness and contracts: payload-bound idempotency with staged enforcement,
|
|
8
|
+
capture/replay authority evidence, bounded invocation provenance, capability execution and view usage
|
|
9
|
+
semantics, event-schema compatibility classification, and deterministic conformance plans.
|
|
10
|
+
- Default payload-bound conflicts to enforcement in Host 3 and return existing nonterminal invocations
|
|
11
|
+
without redispatch; dispatcher deployments must use the documented store-backed recovery worker.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies
|
|
16
|
+
- @fabricorg/platform@0.12.0
|
|
17
|
+
|
|
3
18
|
## 2.0.1
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Payload-bound idempotency rollout
|
|
2
|
+
|
|
3
|
+
`ensureSchema()` adds nullable parameter-digest, actor/binding, provenance, authority, and reconciliation
|
|
4
|
+
columns. Existing rows remain valid. New idempotent submissions always record
|
|
5
|
+
`fabric-canonical-json-sha256-v1`.
|
|
6
|
+
|
|
7
|
+
1. Before adopting Host 3's enforcement default, deploy with `conflictMode: "audit-only"` and capture `onConflict`/`onLegacyRecord` metrics.
|
|
8
|
+
2. Soak until modified-payload key reuse has been removed from callers.
|
|
9
|
+
3. Opt selected applications into `conflictMode: "enforce"`; the same key with different command
|
|
10
|
+
identity now throws `IDEMPOTENCY_CONFLICT`.
|
|
11
|
+
4. Remove the override after upgrading to Host 3; enforcement is its default.
|
|
12
|
+
|
|
13
|
+
Rollback to audit-only is configuration-only. Do not drop digest columns during rollback; they are
|
|
14
|
+
additive evidence and permit enforcement to resume without rebuilding history. Legacy null-digest rows
|
|
15
|
+
continue old-key payload recovery because their original payload identity cannot be reconstructed safely.
|
|
16
|
+
Host 3 still rejects actor, authority-binding, and action-version conflicts for those rows; review these
|
|
17
|
+
identities during the audit-only soak before enabling enforcement.
|
|
18
|
+
|
|
19
|
+
The additive `reconciliation_required` action status is terminal in Phase 1 and requires an operator-
|
|
20
|
+
owned reconciliation path; exhaustive `ActionStatus` consumers must handle it. Custom stores must
|
|
21
|
+
persist the widened invocation fields, including `parameterDigest`, actor/authority binding, provenance,
|
|
22
|
+
and `authorizationReconciliation`. A store that drops them cannot claim payload-bound enforcement or
|
|
23
|
+
durable reconciliation evidence.
|
|
24
|
+
|
|
25
|
+
An action-version change is part of command identity. A retry using a key first recorded under a prior
|
|
26
|
+
action version is reported in audit-only mode and rejected in enforce mode; drain or retain the prior
|
|
27
|
+
action version for the retry window before deploying a breaking action revision.
|
|
28
|
+
|
|
29
|
+
Duplicate submission now returns an existing nonterminal invocation without redispatching it, which
|
|
30
|
+
prevents concurrent callers from driving the same mutation twice. Production asynchronous deployments
|
|
31
|
+
must run the store-backed worker/recovery loop; a dispatcher-only deployment cannot use client retry as
|
|
32
|
+
recovery for a crash between durable creation and dispatch.
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
The canonical host for the `@fabricorg/platform` mutation pipeline.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
pnpm add @fabricorg/platform@^0.
|
|
6
|
+
pnpm add @fabricorg/platform@^0.12.0 @fabricorg/platform-host@^3.0.0
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
## AI agent integration boundary
|
|
@@ -15,7 +15,7 @@ registration, and submit stable idempotent commands. The gateway derives tenant
|
|
|
15
15
|
calls `submitAction()`; an agent must never call handlers, adapters, workflow internals, or database
|
|
16
16
|
writes directly.
|
|
17
17
|
|
|
18
|
-
See the [Platform Host
|
|
18
|
+
See the [Platform Host 3.x agent integration
|
|
19
19
|
guide](https://platform.fabric.pro/docs/platform/reference/platform-host) for application wiring,
|
|
20
20
|
execution-time authorization, the PostgreSQL transaction binder, recovery semantics, and the external
|
|
21
21
|
gateway contract.
|
|
@@ -45,6 +45,12 @@ bounded batches with an atomic lease and `FOR UPDATE SKIP LOCKED`, and an expire
|
|
|
45
45
|
recoverable after interruption. Pass a stable `idempotencyKey` to `submitAction()` so retries resolve
|
|
46
46
|
to the original tenant-scoped invocation instead of creating another mutation.
|
|
47
47
|
|
|
48
|
+
New rows bind that key to the action version, actor/authority binding, and a versioned canonical
|
|
49
|
+
parameter digest. Host 3 defaults to `enforce`, which throws `IdempotencyConflictError`; configure
|
|
50
|
+
`idempotency.conflictMode` as `audit-only` for a preflight soak. Legacy null-digest rows skip only the
|
|
51
|
+
parameter comparison and call `onLegacyRecord`; actor, authority-binding, and action-version mismatches
|
|
52
|
+
still reject in enforce mode. See `MIGRATION-2-IDEMPOTENCY.md`.
|
|
53
|
+
|
|
48
54
|
Lifecycle records use deterministic checkpoint ids. Recovered idempotent actions do not repeat an
|
|
49
55
|
adapter that already reached `succeeded`, and event, policy, and adapter writes are append-safe. A
|
|
50
56
|
stale action that was not declared idempotent fails terminally for manual reconciliation instead of
|
|
@@ -58,6 +64,20 @@ schema-parsed durable parameters, canonical invocation, opaque `authorizationBin
|
|
|
58
64
|
`executionReason` of `initial`, `approval_resume`, or `recovery` immediately before policies and
|
|
59
65
|
mutation code run.
|
|
60
66
|
|
|
67
|
+
`offline_replay` is the extensible evidence value for disconnected commands. Actions with execution
|
|
68
|
+
semantics can declare whether capture, execution, or both govern authority. A denied governed replay
|
|
69
|
+
returns structured `reconciliation_required` evidence.
|
|
70
|
+
|
|
71
|
+
For `execution` and `both`, the Host rejects an expired `AuthorizationBinding` before calling mutation
|
|
72
|
+
code and returns `authorization_expired` reconciliation evidence. `resourceScope` remains an opaque,
|
|
73
|
+
audit-safe scope identifier; the application-owned `authorizeExecution` implementation resolves and
|
|
74
|
+
checks it against current resource state.
|
|
75
|
+
|
|
76
|
+
`InvocationProvenance` carries namespaced trace and audit attributes. Durable audit attributes fail
|
|
77
|
+
closed when no allowlist is configured, pass through `redactAuditAttributes`, and remain bounded.
|
|
78
|
+
Trace attributes go only to the configured trace callback. Restricted outbox payloads omit provenance
|
|
79
|
+
by default; `outbox.includeProvenance` is the explicit classification-aware override.
|
|
80
|
+
|
|
61
81
|
```ts
|
|
62
82
|
const host = createGovernedActionHost({
|
|
63
83
|
store,
|
|
@@ -178,7 +198,7 @@ explain which contract and provider adapter governed a mutation after dependenci
|
|
|
178
198
|
const host = createGovernedActionHost({
|
|
179
199
|
// ...
|
|
180
200
|
runtimeEvidence: {
|
|
181
|
-
hostPackageVersion: "
|
|
201
|
+
hostPackageVersion: "3.0.0",
|
|
182
202
|
policyRulesetVersion: "gtm-rules.v8",
|
|
183
203
|
providerBridge: { name: "@fabric-harness/databricks", version: "1" },
|
|
184
204
|
},
|
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,68 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var platform = require('@fabricorg/platform');
|
|
4
|
+
var crypto = require('crypto');
|
|
4
5
|
|
|
5
6
|
// src/host.ts
|
|
6
7
|
|
|
7
8
|
// src/types.ts
|
|
8
9
|
var PLATFORM_HOST_CONTRACT_VERSION = 2;
|
|
10
|
+
var IdempotencyConflictError = class extends Error {
|
|
11
|
+
constructor(conflict) {
|
|
12
|
+
super(`Idempotency key "${conflict.idempotencyKey}" conflicts with invocation ${conflict.existingInvocationId}: ${conflict.reasons.join(", ")}`);
|
|
13
|
+
this.conflict = conflict;
|
|
14
|
+
this.name = "IdempotencyConflictError";
|
|
15
|
+
}
|
|
16
|
+
conflict;
|
|
17
|
+
code = "IDEMPOTENCY_CONFLICT";
|
|
18
|
+
};
|
|
19
|
+
var PARAMETER_DIGEST_ALGORITHM = "fabric-canonical-json-sha256-v1";
|
|
20
|
+
function canonicalJson(value) {
|
|
21
|
+
return JSON.stringify(sort(value));
|
|
22
|
+
}
|
|
23
|
+
function sort(value) {
|
|
24
|
+
if (Array.isArray(value)) return value.map(sort);
|
|
25
|
+
if (value && typeof value === "object") {
|
|
26
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sort(child)]));
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function digestParameters(parameters) {
|
|
31
|
+
return crypto.createHash("sha256").update(canonicalJson(parameters)).digest("hex");
|
|
32
|
+
}
|
|
33
|
+
var ATTRIBUTE_NAME = /^[a-z][a-z0-9-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)+$/;
|
|
34
|
+
var INVOCATION_SOURCES = /* @__PURE__ */ new Set(["sdui", "api", "agent", "worker", "system"]);
|
|
35
|
+
function normalizeProvenance(input, options) {
|
|
36
|
+
if (!input) return void 0;
|
|
37
|
+
if (!INVOCATION_SOURCES.has(input.source)) throw new Error(`Invalid invocation provenance source: ${input.source}`);
|
|
38
|
+
const maxAttributes = options?.maxAttributes ?? 16;
|
|
39
|
+
const maxValueLength = options?.maxValueLength ?? 256;
|
|
40
|
+
const allowlist = new Set(options?.auditAttributeAllowlist ?? []);
|
|
41
|
+
const validate = (attributes, durable) => {
|
|
42
|
+
if (!attributes) return void 0;
|
|
43
|
+
const entries = Object.entries(attributes);
|
|
44
|
+
if (entries.length > maxAttributes) throw new Error(`Invocation provenance exceeds ${maxAttributes} attributes.`);
|
|
45
|
+
for (const [key, value] of entries) {
|
|
46
|
+
if (!ATTRIBUTE_NAME.test(key)) throw new Error(`Invalid provenance attribute name: ${key}`);
|
|
47
|
+
if (value.length > maxValueLength) throw new Error(`Invocation provenance attribute "${key}" exceeds ${maxValueLength} characters.`);
|
|
48
|
+
if (durable && !allowlist.has(key)) throw new Error(`Audit provenance attribute "${key}" is not allowlisted.`);
|
|
49
|
+
}
|
|
50
|
+
return Object.fromEntries(entries);
|
|
51
|
+
};
|
|
52
|
+
validate(input.traceAttributes, false);
|
|
53
|
+
const redactedAuditAttributes = input.auditAttributes ? options?.redactAuditAttributes?.({ ...input.auditAttributes }) ?? input.auditAttributes : void 0;
|
|
54
|
+
const auditAttributes = validate(redactedAuditAttributes, true);
|
|
55
|
+
options?.onTrace?.({
|
|
56
|
+
...input,
|
|
57
|
+
...auditAttributes ? { auditAttributes } : { auditAttributes: void 0 }
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
source: input.source,
|
|
61
|
+
correlationId: input.correlationId,
|
|
62
|
+
...input.causationId ? { causationId: input.causationId } : {},
|
|
63
|
+
...auditAttributes ? { auditAttributes } : {}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
9
66
|
|
|
10
67
|
// src/host.ts
|
|
11
68
|
var DEFAULT_EXTRACT_EVENTS = (data) => {
|
|
@@ -29,6 +86,9 @@ function createGovernedActionHost(options) {
|
|
|
29
86
|
const actionResolver = options.resolveAction ?? platform.resolveAction;
|
|
30
87
|
const eventResultFields = options.eventResultFields ?? ["_events"];
|
|
31
88
|
async function submitAction(input) {
|
|
89
|
+
if (input.executionReason && !["initial", "offline_replay"].includes(input.executionReason)) {
|
|
90
|
+
throw new Error(`Unsupported submitted execution reason: ${input.executionReason}`);
|
|
91
|
+
}
|
|
32
92
|
const action = actionResolver(input.actionId);
|
|
33
93
|
if (!action) throw new Error(`Unknown action: ${input.actionId}`);
|
|
34
94
|
const authorizationInput = toAuthorizationInput(action, input);
|
|
@@ -39,8 +99,16 @@ function createGovernedActionHost(options) {
|
|
|
39
99
|
throw new Error(`Actor ${input.actorId} is not authorized for action ${input.actionId}`);
|
|
40
100
|
}
|
|
41
101
|
const actionInvocationId = platform.createFabricId("act");
|
|
42
|
-
|
|
102
|
+
if (input.correlationId && input.provenance?.correlationId && input.correlationId !== input.provenance.correlationId) throw new Error("Invocation provenance correlationId does not match submission correlationId.");
|
|
103
|
+
if (input.causationId && input.provenance?.causationId && input.causationId !== input.provenance.causationId) throw new Error("Invocation provenance causationId does not match submission causationId.");
|
|
104
|
+
const correlationId = input.correlationId ?? input.provenance?.correlationId ?? platform.createFabricId("corr");
|
|
43
105
|
const durableParameters = options.redactActionParameters ? options.redactActionParameters(input.actionId, input.parameters) : input.parameters;
|
|
106
|
+
const parameterDigest = digestParameters(input.parameters);
|
|
107
|
+
if (input.authorizationBinding) validateAuthorizationBinding(input, parameterDigest, action.execution?.authorityMoment, now());
|
|
108
|
+
const durableProvenance = normalizeProvenance(
|
|
109
|
+
input.provenance,
|
|
110
|
+
options.provenance
|
|
111
|
+
);
|
|
44
112
|
const runtimeEvidence = {
|
|
45
113
|
governanceContractVersion: platform.FABRIC_GOVERNANCE_CONTRACT_VERSION,
|
|
46
114
|
hostContractVersion: PLATFORM_HOST_CONTRACT_VERSION,
|
|
@@ -60,12 +128,41 @@ function createGovernedActionHost(options) {
|
|
|
60
128
|
result: {},
|
|
61
129
|
runtimeEvidence,
|
|
62
130
|
correlationId,
|
|
63
|
-
...input.causationId ? { causationId: input.causationId } : {},
|
|
131
|
+
...input.causationId ?? input.provenance?.causationId ? { causationId: input.causationId ?? input.provenance?.causationId } : {},
|
|
64
132
|
...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
|
|
133
|
+
...input.idempotencyKey ? {
|
|
134
|
+
parameterDigest,
|
|
135
|
+
parameterDigestAlgorithm: PARAMETER_DIGEST_ALGORITHM,
|
|
136
|
+
idempotencyActorId: input.actorId,
|
|
137
|
+
...input.authorizationBindingId ? { idempotencyAuthorizationBindingId: input.authorizationBindingId } : {}
|
|
138
|
+
} : {},
|
|
139
|
+
...durableProvenance ? { provenance: durableProvenance } : {},
|
|
140
|
+
...input.authorizationBinding ? { authorizationBinding: input.authorizationBinding } : {},
|
|
141
|
+
...input.executionReason ? { executionReason: input.executionReason } : {},
|
|
65
142
|
...input.authorizationBindingId ? { authorizationBindingId: input.authorizationBindingId } : {}
|
|
66
143
|
});
|
|
144
|
+
if (durableInvocation.id !== actionInvocationId && input.idempotencyKey) {
|
|
145
|
+
const conflict = idempotencyConflict(durableInvocation, {
|
|
146
|
+
actorId: input.actorId,
|
|
147
|
+
authorizationBindingId: input.authorizationBindingId,
|
|
148
|
+
actionVersion: action.version,
|
|
149
|
+
parameterDigest,
|
|
150
|
+
idempotencyKey: input.idempotencyKey
|
|
151
|
+
});
|
|
152
|
+
if (!durableInvocation.parameterDigest) {
|
|
153
|
+
options.idempotency?.onLegacyRecord?.(durableInvocation);
|
|
154
|
+
const enforceableLegacyConflict = conflict && { ...conflict, reasons: conflict.reasons.filter((reason) => reason !== "parameters") };
|
|
155
|
+
if (enforceableLegacyConflict && enforceableLegacyConflict.reasons.length) {
|
|
156
|
+
options.idempotency?.onConflict?.(enforceableLegacyConflict);
|
|
157
|
+
if ((options.idempotency?.conflictMode ?? "enforce") === "enforce") throw new IdempotencyConflictError(enforceableLegacyConflict);
|
|
158
|
+
}
|
|
159
|
+
} else if (conflict) {
|
|
160
|
+
options.idempotency?.onConflict?.(conflict);
|
|
161
|
+
if ((options.idempotency?.conflictMode ?? "enforce") === "enforce") throw new IdempotencyConflictError(conflict);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
67
164
|
const durableWorkflowId = `action-invocation-${durableInvocation.id}`;
|
|
68
|
-
if (durableInvocation.id !== actionInvocationId
|
|
165
|
+
if (durableInvocation.id !== actionInvocationId) {
|
|
69
166
|
return {
|
|
70
167
|
actionInvocationId: durableInvocation.id,
|
|
71
168
|
status: durableInvocation.status,
|
|
@@ -73,7 +170,8 @@ function createGovernedActionHost(options) {
|
|
|
73
170
|
result: durableInvocation.result,
|
|
74
171
|
...durableInvocation.error ? { error: durableInvocation.error } : {},
|
|
75
172
|
...durableInvocation.hitlRoute ? { hitlRoute: durableInvocation.hitlRoute } : {},
|
|
76
|
-
...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {}
|
|
173
|
+
...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {},
|
|
174
|
+
...durableInvocation.authorizationReconciliation ? { reconciliation: durableInvocation.authorizationReconciliation } : {}
|
|
77
175
|
};
|
|
78
176
|
}
|
|
79
177
|
if (options.dispatcher) {
|
|
@@ -221,7 +319,7 @@ function createGovernedActionHost(options) {
|
|
|
221
319
|
}
|
|
222
320
|
}
|
|
223
321
|
const authorizationInput = toAuthorizationInput(action, invocation);
|
|
224
|
-
const executionReason = executionReasonOverride ?? (invocation.attemptCount > 1 || resumingRunningInvocation && invocation.attemptCount === 0 ? "recovery" : "initial");
|
|
322
|
+
const executionReason = executionReasonOverride ?? (invocation.attemptCount > 1 || resumingRunningInvocation && invocation.attemptCount === 0 ? "recovery" : invocation.executionReason ?? "initial");
|
|
225
323
|
if (!await options.authorization.checkEntitlement(authorizationInput)) {
|
|
226
324
|
return fail(
|
|
227
325
|
invocation,
|
|
@@ -229,7 +327,32 @@ function createGovernedActionHost(options) {
|
|
|
229
327
|
`Module "${action.namespace}" is no longer enabled for tenant ${tenantId}`
|
|
230
328
|
);
|
|
231
329
|
}
|
|
232
|
-
const
|
|
330
|
+
const authorityMoment = action.execution?.authorityMoment;
|
|
331
|
+
if (authorityMoment === "capture" && !invocation.authorizationBinding) {
|
|
332
|
+
const reconciliation = {
|
|
333
|
+
kind: "authorization_missing_capture_evidence",
|
|
334
|
+
governingMoment: authorityMoment,
|
|
335
|
+
executionReason,
|
|
336
|
+
...invocation.provenance ? { provenance: invocation.provenance } : {},
|
|
337
|
+
message: `Action ${action.actionId} requires durable capture-time authorization evidence`
|
|
338
|
+
};
|
|
339
|
+
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
|
|
340
|
+
return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
|
|
341
|
+
}
|
|
342
|
+
const bindingExpired = authorityMoment !== "capture" && invocation.authorizationBinding?.expiresAt !== void 0 && Date.parse(invocation.authorizationBinding.expiresAt) <= now().getTime();
|
|
343
|
+
if (bindingExpired) {
|
|
344
|
+
const reconciliation = {
|
|
345
|
+
kind: "authorization_expired",
|
|
346
|
+
governingMoment: authorityMoment ?? "both",
|
|
347
|
+
executionReason,
|
|
348
|
+
...invocation.authorizationBindingId ? { authorizationBindingId: invocation.authorizationBindingId } : {},
|
|
349
|
+
...invocation.provenance ? { provenance: invocation.provenance } : {},
|
|
350
|
+
message: `Authorization binding for action ${action.actionId} expired before execution`
|
|
351
|
+
};
|
|
352
|
+
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
|
|
353
|
+
return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
|
|
354
|
+
}
|
|
355
|
+
const executionAuthorized = authorityMoment === "capture" ? invocation.authorizationBinding !== void 0 : options.authorization.authorizeExecution ? await options.authorization.authorizeExecution({
|
|
233
356
|
...authorizationInput,
|
|
234
357
|
actionInvocationId,
|
|
235
358
|
parameters: parsed.data,
|
|
@@ -237,11 +360,19 @@ function createGovernedActionHost(options) {
|
|
|
237
360
|
executionReason
|
|
238
361
|
}) : await options.authorization.authorize(authorizationInput);
|
|
239
362
|
if (!executionAuthorized) {
|
|
240
|
-
|
|
241
|
-
invocation,
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
363
|
+
if (!action.execution) {
|
|
364
|
+
return fail(invocation, "failed", `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`);
|
|
365
|
+
}
|
|
366
|
+
const reconciliation = {
|
|
367
|
+
kind: "authorization_denied",
|
|
368
|
+
governingMoment: action.execution?.authorityMoment ?? "both",
|
|
369
|
+
executionReason,
|
|
370
|
+
...invocation.authorizationBindingId ? { authorizationBindingId: invocation.authorizationBindingId } : {},
|
|
371
|
+
...invocation.provenance ? { provenance: invocation.provenance } : {},
|
|
372
|
+
message: `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`
|
|
373
|
+
};
|
|
374
|
+
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
|
|
375
|
+
return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
|
|
245
376
|
}
|
|
246
377
|
const definitions = options.resolvePolicies ? await options.resolvePolicies({
|
|
247
378
|
...authorizationInput,
|
|
@@ -340,6 +471,7 @@ function createGovernedActionHost(options) {
|
|
|
340
471
|
actorType: invocation.actorType,
|
|
341
472
|
correlationId: invocation.correlationId,
|
|
342
473
|
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
474
|
+
...invocation.provenance ? { provenance: invocation.provenance } : {},
|
|
343
475
|
db,
|
|
344
476
|
services: options.services
|
|
345
477
|
},
|
|
@@ -694,7 +826,8 @@ function createGovernedActionHost(options) {
|
|
|
694
826
|
occurredAt: timestamp,
|
|
695
827
|
recordedAt: timestamp,
|
|
696
828
|
correlationId: invocation.correlationId,
|
|
697
|
-
...invocation.causationId ? { causationId: invocation.causationId } : {}
|
|
829
|
+
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
830
|
+
...invocation.provenance ? { provenance: { source: invocation.provenance.source, ...invocation.provenance.auditAttributes ? { auditAttributes: invocation.provenance.auditAttributes } : {} } } : {}
|
|
698
831
|
};
|
|
699
832
|
if (options.outbox) {
|
|
700
833
|
const hostLifecycleEvent = (/* @__PURE__ */ new Set(["AdapterInvocationStarted", "AdapterInvocationSucceeded", "AdapterInvocationFailed", "ComplianceBlocked"])).has(envelope.eventType);
|
|
@@ -704,9 +837,11 @@ function createGovernedActionHost(options) {
|
|
|
704
837
|
return;
|
|
705
838
|
}
|
|
706
839
|
const traceContext = options.outbox.traceContext?.(envelope);
|
|
840
|
+
const payloadClassification = options.outbox.classifyPayload(envelope);
|
|
707
841
|
const metadata = {
|
|
708
842
|
producerModuleVersion: options.outbox.producerModuleVersion(invocation.actionId, invocation.actionVersion),
|
|
709
|
-
payloadClassification
|
|
843
|
+
payloadClassification,
|
|
844
|
+
includeProvenance: options.outbox.includeProvenance?.(envelope, payloadClassification) ?? payloadClassification !== "restricted",
|
|
710
845
|
...traceContext ? { traceContext } : {}
|
|
711
846
|
};
|
|
712
847
|
if (transaction) {
|
|
@@ -745,7 +880,8 @@ function actionResult(invocation) {
|
|
|
745
880
|
result: invocation.result,
|
|
746
881
|
...invocation.error ? { error: invocation.error } : {},
|
|
747
882
|
...invocation.hitlRoute ? { hitlRoute: invocation.hitlRoute } : {},
|
|
748
|
-
...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {}
|
|
883
|
+
...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {},
|
|
884
|
+
...invocation.authorizationReconciliation ? { reconciliation: invocation.authorizationReconciliation } : {}
|
|
749
885
|
};
|
|
750
886
|
}
|
|
751
887
|
function asApprovalStore(store) {
|
|
@@ -798,7 +934,7 @@ function initialState(entityType) {
|
|
|
798
934
|
return Object.values(machine?.states ?? {}).find((state) => state.stateClass === "initial")?.id ?? "none";
|
|
799
935
|
}
|
|
800
936
|
function isTerminal(status) {
|
|
801
|
-
return ["completed", "failed", "blocked_by_policy", "validation_failed"].includes(status);
|
|
937
|
+
return ["completed", "failed", "blocked_by_policy", "reconciliation_required", "validation_failed"].includes(status);
|
|
802
938
|
}
|
|
803
939
|
function withoutPrivateHostFields(data, eventResultFields) {
|
|
804
940
|
return Object.fromEntries(
|
|
@@ -815,6 +951,30 @@ function lifecycleId(prefix, invocationId, key) {
|
|
|
815
951
|
const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 96);
|
|
816
952
|
return `${prefix}_${invocationId}_${safeKey}`;
|
|
817
953
|
}
|
|
954
|
+
function validateAuthorizationBinding(input, parameterDigest, authorityMoment, currentTime = /* @__PURE__ */ new Date()) {
|
|
955
|
+
const binding = input.authorizationBinding;
|
|
956
|
+
if (binding.id !== input.authorizationBindingId && input.authorizationBindingId) throw new Error("Authorization binding ID does not match authorizationBindingId.");
|
|
957
|
+
if (binding.tenantId !== input.tenantId || binding.actorId !== input.actorId || binding.actionId !== input.actionId || binding.parameterDigest !== parameterDigest) {
|
|
958
|
+
throw new Error("Authorization binding does not match the submitted command identity.");
|
|
959
|
+
}
|
|
960
|
+
if (authorityMoment && binding.governingMoment !== authorityMoment) throw new Error("Authorization binding governingMoment does not match the action execution contract.");
|
|
961
|
+
const capturedAt = Date.parse(binding.capturedAt);
|
|
962
|
+
const expiresAt = binding.expiresAt === void 0 ? void 0 : Date.parse(binding.expiresAt);
|
|
963
|
+
if (!Number.isFinite(capturedAt) || expiresAt !== void 0 && !Number.isFinite(expiresAt)) {
|
|
964
|
+
throw new Error("Authorization binding timestamps must be valid ISO-8601 values.");
|
|
965
|
+
}
|
|
966
|
+
if (expiresAt !== void 0 && (expiresAt <= capturedAt || authorityMoment === "capture" && expiresAt <= currentTime.getTime())) {
|
|
967
|
+
throw new Error("Authorization binding must be unexpired at capture and expire after capturedAt.");
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function idempotencyConflict(existing, incoming) {
|
|
971
|
+
const reasons = [];
|
|
972
|
+
if ((existing.idempotencyActorId ?? existing.actorId) !== incoming.actorId) reasons.push("actor");
|
|
973
|
+
if ((existing.idempotencyAuthorizationBindingId ?? existing.authorizationBindingId) !== incoming.authorizationBindingId) reasons.push("authority_binding");
|
|
974
|
+
if (existing.actionVersion !== incoming.actionVersion) reasons.push("action_version");
|
|
975
|
+
if (existing.parameterDigest !== incoming.parameterDigest) reasons.push("parameters");
|
|
976
|
+
return reasons.length ? { code: "IDEMPOTENCY_CONFLICT", idempotencyKey: incoming.idempotencyKey, existingInvocationId: existing.id, reasons } : void 0;
|
|
977
|
+
}
|
|
818
978
|
|
|
819
979
|
// src/outbox.ts
|
|
820
980
|
function toEnterpriseEventEnvelope(event, metadata) {
|
|
@@ -835,6 +995,7 @@ function toEnterpriseEventEnvelope(event, metadata) {
|
|
|
835
995
|
producerModuleVersion: metadata.producerModuleVersion,
|
|
836
996
|
payloadClassification: metadata.payloadClassification,
|
|
837
997
|
...metadata.traceContext ? { traceContext: { ...metadata.traceContext } } : {},
|
|
998
|
+
...metadata.includeProvenance && event.provenance ? { provenance: event.provenance } : {},
|
|
838
999
|
payload: event.payload
|
|
839
1000
|
};
|
|
840
1001
|
}
|
|
@@ -969,7 +1130,7 @@ var MemoryPlatformHostStore = class {
|
|
|
969
1130
|
const record = await this.getActionInvocation(id, tenantId, spaceId);
|
|
970
1131
|
if (!record) throw new Error(`ActionInvocation not found: ${id}`);
|
|
971
1132
|
Object.assign(record, patch, { updatedAt: /* @__PURE__ */ new Date() });
|
|
972
|
-
if (patch.status === "waiting_for_approval" || patch.status === "completed" || patch.status === "failed" || patch.status === "blocked_by_policy" || patch.status === "validation_failed") {
|
|
1133
|
+
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") {
|
|
973
1134
|
delete record.leaseOwner;
|
|
974
1135
|
delete record.leaseExpiresAt;
|
|
975
1136
|
}
|
|
@@ -1181,6 +1342,9 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1181
1342
|
actor_id text NOT NULL, actor_type text NOT NULL, status text NOT NULL,
|
|
1182
1343
|
parameters jsonb NOT NULL, result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1183
1344
|
correlation_id text NOT NULL, causation_id text, idempotency_key text,
|
|
1345
|
+
parameter_digest text, parameter_digest_algorithm text, idempotency_actor_id text,
|
|
1346
|
+
idempotency_authorization_binding_id text, invocation_provenance jsonb,
|
|
1347
|
+
authorization_binding jsonb, execution_reason text, authorization_reconciliation jsonb,
|
|
1184
1348
|
authorization_binding_id text, error text,
|
|
1185
1349
|
attempt_count integer NOT NULL DEFAULT 0, lease_owner text,
|
|
1186
1350
|
lease_expires_at timestamptz, hitl_route text, hitl_risk_tier text,
|
|
@@ -1190,6 +1354,14 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1190
1354
|
);
|
|
1191
1355
|
ALTER TABLE fabric_platform.action_invocations
|
|
1192
1356
|
ADD COLUMN IF NOT EXISTS idempotency_key text,
|
|
1357
|
+
ADD COLUMN IF NOT EXISTS parameter_digest text,
|
|
1358
|
+
ADD COLUMN IF NOT EXISTS parameter_digest_algorithm text,
|
|
1359
|
+
ADD COLUMN IF NOT EXISTS idempotency_actor_id text,
|
|
1360
|
+
ADD COLUMN IF NOT EXISTS idempotency_authorization_binding_id text,
|
|
1361
|
+
ADD COLUMN IF NOT EXISTS invocation_provenance jsonb,
|
|
1362
|
+
ADD COLUMN IF NOT EXISTS authorization_binding jsonb,
|
|
1363
|
+
ADD COLUMN IF NOT EXISTS execution_reason text,
|
|
1364
|
+
ADD COLUMN IF NOT EXISTS authorization_reconciliation jsonb,
|
|
1193
1365
|
ADD COLUMN IF NOT EXISTS authorization_binding_id text,
|
|
1194
1366
|
ADD COLUMN IF NOT EXISTS attempt_count integer NOT NULL DEFAULT 0,
|
|
1195
1367
|
ADD COLUMN IF NOT EXISTS lease_owner text,
|
|
@@ -1206,6 +1378,12 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1206
1378
|
ON fabric_platform.action_invocations
|
|
1207
1379
|
(tenant_id, space_id, action_id, idempotency_key)
|
|
1208
1380
|
WHERE idempotency_key IS NOT NULL;
|
|
1381
|
+
CREATE INDEX IF NOT EXISTS action_invocations_parameter_digest_idx
|
|
1382
|
+
ON fabric_platform.action_invocations (tenant_id, parameter_digest)
|
|
1383
|
+
WHERE parameter_digest IS NOT NULL;
|
|
1384
|
+
CREATE INDEX IF NOT EXISTS action_invocations_authority_binding_idx
|
|
1385
|
+
ON fabric_platform.action_invocations (tenant_id, authorization_binding_id)
|
|
1386
|
+
WHERE authorization_binding_id IS NOT NULL;
|
|
1209
1387
|
CREATE INDEX IF NOT EXISTS action_invocations_worker_idx
|
|
1210
1388
|
ON fabric_platform.action_invocations (status, lease_expires_at, created_at);
|
|
1211
1389
|
CREATE TABLE IF NOT EXISTS fabric_platform.policy_evaluations (
|
|
@@ -1249,9 +1427,10 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1249
1427
|
actor_id text NOT NULL, actor_type text NOT NULL, action_invocation_id text,
|
|
1250
1428
|
payload jsonb NOT NULL, sequence bigint NOT NULL,
|
|
1251
1429
|
occurred_at timestamptz NOT NULL, recorded_at timestamptz NOT NULL,
|
|
1252
|
-
correlation_id text NOT NULL, causation_id text,
|
|
1430
|
+
correlation_id text NOT NULL, causation_id text, provenance jsonb,
|
|
1253
1431
|
UNIQUE (tenant_id, space_id, sequence)
|
|
1254
1432
|
);
|
|
1433
|
+
ALTER TABLE fabric_platform.asset_events ADD COLUMN IF NOT EXISTS provenance jsonb;
|
|
1255
1434
|
CREATE INDEX IF NOT EXISTS asset_events_subject_idx
|
|
1256
1435
|
ON fabric_platform.asset_events
|
|
1257
1436
|
(tenant_id, space_id, subject_type, subject_id, sequence);
|
|
@@ -1275,8 +1454,10 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1275
1454
|
`INSERT INTO fabric_platform.action_invocations
|
|
1276
1455
|
(id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
|
|
1277
1456
|
parameters,result,correlation_id,causation_id,idempotency_key,
|
|
1457
|
+
parameter_digest,parameter_digest_algorithm,idempotency_actor_id,
|
|
1458
|
+
idempotency_authorization_binding_id,invocation_provenance,authorization_binding,execution_reason,
|
|
1278
1459
|
authorization_binding_id,error,runtime_evidence,created_at,updated_at)
|
|
1279
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16::jsonb,$
|
|
1460
|
+
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)
|
|
1280
1461
|
ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
|
|
1281
1462
|
WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
1282
1463
|
RETURNING *`,
|
|
@@ -1294,6 +1475,13 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1294
1475
|
input.correlationId,
|
|
1295
1476
|
input.causationId ?? null,
|
|
1296
1477
|
input.idempotencyKey ?? null,
|
|
1478
|
+
input.parameterDigest ?? null,
|
|
1479
|
+
input.parameterDigestAlgorithm ?? null,
|
|
1480
|
+
input.idempotencyActorId ?? null,
|
|
1481
|
+
input.idempotencyAuthorizationBindingId ?? null,
|
|
1482
|
+
input.provenance ? JSON.stringify(input.provenance) : null,
|
|
1483
|
+
input.authorizationBinding ? JSON.stringify(input.authorizationBinding) : null,
|
|
1484
|
+
input.executionReason ?? null,
|
|
1297
1485
|
input.authorizationBindingId ?? null,
|
|
1298
1486
|
input.error ?? null,
|
|
1299
1487
|
input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
|
|
@@ -1315,9 +1503,10 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1315
1503
|
`UPDATE fabric_platform.action_invocations SET
|
|
1316
1504
|
status=COALESCE($4,status), result=COALESCE($5::jsonb,result),
|
|
1317
1505
|
error=CASE WHEN $6::boolean THEN $7 ELSE error END,
|
|
1318
|
-
|
|
1506
|
+
authorization_reconciliation=CASE WHEN $8::boolean THEN $9::jsonb ELSE authorization_reconciliation END,
|
|
1507
|
+
lease_owner=CASE WHEN $4 IN ('waiting_for_approval','completed','failed','blocked_by_policy','reconciliation_required','validation_failed')
|
|
1319
1508
|
THEN NULL ELSE lease_owner END,
|
|
1320
|
-
lease_expires_at=CASE WHEN $4 IN ('waiting_for_approval','completed','failed','blocked_by_policy','validation_failed')
|
|
1509
|
+
lease_expires_at=CASE WHEN $4 IN ('waiting_for_approval','completed','failed','blocked_by_policy','reconciliation_required','validation_failed')
|
|
1321
1510
|
THEN NULL ELSE lease_expires_at END,
|
|
1322
1511
|
updated_at=now()
|
|
1323
1512
|
WHERE id=$1 AND tenant_id=$2 AND space_id=$3`,
|
|
@@ -1328,7 +1517,9 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1328
1517
|
patch.status ?? null,
|
|
1329
1518
|
patch.result === void 0 ? null : JSON.stringify(patch.result),
|
|
1330
1519
|
Object.hasOwn(patch, "error"),
|
|
1331
|
-
patch.error ?? null
|
|
1520
|
+
patch.error ?? null,
|
|
1521
|
+
Object.hasOwn(patch, "authorizationReconciliation"),
|
|
1522
|
+
patch.authorizationReconciliation ? JSON.stringify(patch.authorizationReconciliation) : null
|
|
1332
1523
|
]
|
|
1333
1524
|
);
|
|
1334
1525
|
}
|
|
@@ -1523,8 +1714,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1523
1714
|
`INSERT INTO fabric_platform.asset_events
|
|
1524
1715
|
(id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
|
|
1525
1716
|
actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
|
|
1526
|
-
correlation_id,causation_id)
|
|
1527
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16)
|
|
1717
|
+
correlation_id,causation_id,provenance)
|
|
1718
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb)
|
|
1528
1719
|
ON CONFLICT (id) DO NOTHING`,
|
|
1529
1720
|
[
|
|
1530
1721
|
event.id,
|
|
@@ -1542,7 +1733,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1542
1733
|
event.occurredAt,
|
|
1543
1734
|
event.recordedAt,
|
|
1544
1735
|
event.correlationId,
|
|
1545
|
-
event.causationId ?? null
|
|
1736
|
+
event.causationId ?? null,
|
|
1737
|
+
event.provenance ? JSON.stringify(event.provenance) : null
|
|
1546
1738
|
]
|
|
1547
1739
|
);
|
|
1548
1740
|
}
|
|
@@ -1553,13 +1745,13 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1553
1745
|
INSERT INTO fabric_platform.asset_events
|
|
1554
1746
|
(id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
|
|
1555
1747
|
actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
|
|
1556
|
-
correlation_id,causation_id)
|
|
1557
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16)
|
|
1748
|
+
correlation_id,causation_id,provenance)
|
|
1749
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb)
|
|
1558
1750
|
ON CONFLICT (id) DO NOTHING RETURNING id
|
|
1559
1751
|
)
|
|
1560
1752
|
INSERT INTO fabric_platform.event_outbox
|
|
1561
1753
|
(id,tenant_id,space_id,event,status,attempt_count,available_at,created_at)
|
|
1562
|
-
SELECT $1,$2,$3,$
|
|
1754
|
+
SELECT $1,$2,$3,$18::jsonb,'pending',0,$14,$14 FROM inserted_event
|
|
1563
1755
|
ON CONFLICT (id) DO NOTHING`,
|
|
1564
1756
|
[
|
|
1565
1757
|
event.id,
|
|
@@ -1578,6 +1770,7 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1578
1770
|
event.recordedAt,
|
|
1579
1771
|
event.correlationId,
|
|
1580
1772
|
event.causationId ?? null,
|
|
1773
|
+
event.provenance ? JSON.stringify(event.provenance) : null,
|
|
1581
1774
|
JSON.stringify(envelope)
|
|
1582
1775
|
]
|
|
1583
1776
|
);
|
|
@@ -1738,6 +1931,14 @@ function toActionRecord(row) {
|
|
|
1738
1931
|
correlationId: String(row.correlation_id),
|
|
1739
1932
|
...row.causation_id ? { causationId: String(row.causation_id) } : {},
|
|
1740
1933
|
...row.idempotency_key ? { idempotencyKey: String(row.idempotency_key) } : {},
|
|
1934
|
+
...row.parameter_digest ? { parameterDigest: String(row.parameter_digest) } : {},
|
|
1935
|
+
...row.parameter_digest_algorithm ? { parameterDigestAlgorithm: String(row.parameter_digest_algorithm) } : {},
|
|
1936
|
+
...row.idempotency_actor_id ? { idempotencyActorId: String(row.idempotency_actor_id) } : {},
|
|
1937
|
+
...row.idempotency_authorization_binding_id ? { idempotencyAuthorizationBindingId: String(row.idempotency_authorization_binding_id) } : {},
|
|
1938
|
+
...row.invocation_provenance ? { provenance: row.invocation_provenance } : {},
|
|
1939
|
+
...row.authorization_binding ? { authorizationBinding: row.authorization_binding } : {},
|
|
1940
|
+
...row.execution_reason ? { executionReason: String(row.execution_reason) } : {},
|
|
1941
|
+
...row.authorization_reconciliation ? { authorizationReconciliation: row.authorization_reconciliation } : {},
|
|
1741
1942
|
...row.authorization_binding_id ? { authorizationBindingId: String(row.authorization_binding_id) } : {},
|
|
1742
1943
|
attemptCount: Number(row.attempt_count ?? 0),
|
|
1743
1944
|
...row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {},
|
|
@@ -1811,7 +2012,8 @@ function toEventRecord(row) {
|
|
|
1811
2012
|
occurredAt: new Date(row.occurred_at),
|
|
1812
2013
|
recordedAt: new Date(row.recorded_at),
|
|
1813
2014
|
correlationId: String(row.correlation_id),
|
|
1814
|
-
...row.causation_id ? { causationId: String(row.causation_id) } : {}
|
|
2015
|
+
...row.causation_id ? { causationId: String(row.causation_id) } : {},
|
|
2016
|
+
...row.provenance ? { provenance: row.provenance } : {}
|
|
1815
2017
|
};
|
|
1816
2018
|
}
|
|
1817
2019
|
function toOutboxRecord(row) {
|
|
@@ -1832,6 +2034,7 @@ function toOutboxRecord(row) {
|
|
|
1832
2034
|
...event.actionInvocationId ? { actionInvocationId: String(event.actionInvocationId) } : {},
|
|
1833
2035
|
correlationId: String(event.correlationId),
|
|
1834
2036
|
...event.causationId ? { causationId: String(event.causationId) } : {},
|
|
2037
|
+
...event.provenance ? { provenance: event.provenance } : {},
|
|
1835
2038
|
occurredAt: new Date(event.occurredAt),
|
|
1836
2039
|
recordedAt: new Date(event.recordedAt),
|
|
1837
2040
|
producerModuleVersion: String(event.producerModuleVersion),
|
|
@@ -1918,12 +2121,16 @@ async function abortableDelay(milliseconds, signal) {
|
|
|
1918
2121
|
});
|
|
1919
2122
|
}
|
|
1920
2123
|
|
|
2124
|
+
exports.IdempotencyConflictError = IdempotencyConflictError;
|
|
1921
2125
|
exports.MemoryPlatformHostStore = MemoryPlatformHostStore;
|
|
2126
|
+
exports.PARAMETER_DIGEST_ALGORITHM = PARAMETER_DIGEST_ALGORITHM;
|
|
1922
2127
|
exports.PLATFORM_HOST_CONTRACT_VERSION = PLATFORM_HOST_CONTRACT_VERSION;
|
|
1923
2128
|
exports.PostgresPlatformHostStore = PostgresPlatformHostStore;
|
|
2129
|
+
exports.canonicalJson = canonicalJson;
|
|
1924
2130
|
exports.cloneOutboxRecord = cloneOutboxRecord;
|
|
1925
2131
|
exports.createGovernedActionHost = createGovernedActionHost;
|
|
1926
2132
|
exports.createStoreBackedActionDispatcher = createStoreBackedActionDispatcher;
|
|
2133
|
+
exports.digestParameters = digestParameters;
|
|
1927
2134
|
exports.runOutboxRelayCycle = runOutboxRelayCycle;
|
|
1928
2135
|
exports.runPlatformActionWorker = runPlatformActionWorker;
|
|
1929
2136
|
exports.runPlatformActionWorkerCycle = runPlatformActionWorkerCycle;
|