@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/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
- const correlationId = input.correlationId ?? createFabricId("corr");
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 && isTerminal(durableInvocation.status)) {
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 executionAuthorized = options.authorization.authorizeExecution ? await options.authorization.authorizeExecution({
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
- return fail(
239
- invocation,
240
- "failed",
241
- `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`
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: options.outbox.classifyPayload(envelope),
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,$17,$17)
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
- lease_owner=CASE WHEN $4 IN ('waiting_for_approval','completed','failed','blocked_by_policy','validation_failed')
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,$17::jsonb,'pending',0,$14,$14 FROM inserted_event
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