@fabricorg/platform-host 4.0.0 → 5.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.d.cts CHANGED
@@ -213,6 +213,8 @@ interface EnterpriseEventEnvelope {
213
213
  source: InvocationSource;
214
214
  auditAttributes?: NamespacedAttributes;
215
215
  };
216
+ /** Present only for events an action declaring provisional consistency emitted. */
217
+ consistency?: "authoritative-now" | "provisional-until-reconciled";
216
218
  payload: unknown;
217
219
  }
218
220
  interface OutboxRecord {
@@ -393,6 +395,8 @@ interface SubmitActionResult {
393
395
  result?: Record<string, unknown>;
394
396
  error?: string;
395
397
  reconciliation?: AuthorizationReconciliationOutcome;
398
+ /** Mirrors the action's declared consistency so a caller knows whether the result is settled. */
399
+ consistency?: "authoritative-now" | "provisional-until-reconciled";
396
400
  hitlRoute?: AgentActionRoute;
397
401
  hitlRiskTier?: AgentActionRiskTier;
398
402
  }
@@ -402,6 +406,8 @@ interface ExecuteActionResult {
402
406
  result?: Record<string, unknown>;
403
407
  error?: string;
404
408
  reconciliation?: AuthorizationReconciliationOutcome;
409
+ /** Mirrors the action's declared consistency so a caller knows whether the result is settled. */
410
+ consistency?: "authoritative-now" | "provisional-until-reconciled";
405
411
  hitlRoute?: AgentActionRoute;
406
412
  hitlRiskTier?: AgentActionRiskTier;
407
413
  }
package/dist/index.d.ts CHANGED
@@ -213,6 +213,8 @@ interface EnterpriseEventEnvelope {
213
213
  source: InvocationSource;
214
214
  auditAttributes?: NamespacedAttributes;
215
215
  };
216
+ /** Present only for events an action declaring provisional consistency emitted. */
217
+ consistency?: "authoritative-now" | "provisional-until-reconciled";
216
218
  payload: unknown;
217
219
  }
218
220
  interface OutboxRecord {
@@ -393,6 +395,8 @@ interface SubmitActionResult {
393
395
  result?: Record<string, unknown>;
394
396
  error?: string;
395
397
  reconciliation?: AuthorizationReconciliationOutcome;
398
+ /** Mirrors the action's declared consistency so a caller knows whether the result is settled. */
399
+ consistency?: "authoritative-now" | "provisional-until-reconciled";
396
400
  hitlRoute?: AgentActionRoute;
397
401
  hitlRiskTier?: AgentActionRiskTier;
398
402
  }
@@ -402,6 +406,8 @@ interface ExecuteActionResult {
402
406
  result?: Record<string, unknown>;
403
407
  error?: string;
404
408
  reconciliation?: AuthorizationReconciliationOutcome;
409
+ /** Mirrors the action's declared consistency so a caller knows whether the result is settled. */
410
+ consistency?: "authoritative-now" | "provisional-until-reconciled";
405
411
  hitlRoute?: AgentActionRoute;
406
412
  hitlRiskTier?: AgentActionRiskTier;
407
413
  }
package/dist/index.js CHANGED
@@ -92,6 +92,12 @@ function createGovernedActionHost(options) {
92
92
  }
93
93
  const action = actionResolver(input.actionId);
94
94
  if (!action) throw new Error(`Unknown action: ${input.actionId}`);
95
+ if (action.execution?.connectivity === "online-required" && input.executionReason === "offline_replay") {
96
+ throw new Error(`Action ${input.actionId} is online-required and cannot be submitted as an offline replay.`);
97
+ }
98
+ if (action.execution?.sensitivity === "restricted" && Object.keys(input.provenance?.auditAttributes ?? {}).length > 0) {
99
+ throw new Error(`Action ${input.actionId} declares restricted sensitivity; provenance audit attributes must not be durably recorded.`);
100
+ }
95
101
  const authorizationInput = toAuthorizationInput(action, input);
96
102
  if (!await options.authorization.checkEntitlement(authorizationInput)) {
97
103
  throw new Error(`Module "${action.namespace}" is not enabled for tenant ${input.tenantId}`);
@@ -164,7 +170,7 @@ function createGovernedActionHost(options) {
164
170
  }
165
171
  const durableWorkflowId = `action-invocation-${durableInvocation.id}`;
166
172
  if (durableInvocation.id !== actionInvocationId) {
167
- return {
173
+ return withConsistency({
168
174
  actionInvocationId: durableInvocation.id,
169
175
  status: durableInvocation.status,
170
176
  workflowId: durableWorkflowId,
@@ -173,7 +179,7 @@ function createGovernedActionHost(options) {
173
179
  ...durableInvocation.hitlRoute ? { hitlRoute: durableInvocation.hitlRoute } : {},
174
180
  ...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {},
175
181
  ...durableInvocation.authorizationReconciliation ? { reconciliation: durableInvocation.authorizationReconciliation } : {}
176
- };
182
+ }, input.actionId);
177
183
  }
178
184
  if (options.dispatcher) {
179
185
  try {
@@ -183,14 +189,14 @@ function createGovernedActionHost(options) {
183
189
  spaceId: input.spaceId,
184
190
  workflowId: durableWorkflowId
185
191
  });
186
- return {
192
+ return withConsistency({
187
193
  actionInvocationId: durableInvocation.id,
188
194
  status: durableInvocation.status,
189
195
  workflowId: dispatched.workflowId,
190
196
  ...dispatched.runId ? { runId: dispatched.runId } : {},
191
197
  ...durableInvocation.hitlRoute ? { hitlRoute: durableInvocation.hitlRoute } : {},
192
198
  ...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {}
193
- };
199
+ }, input.actionId);
194
200
  } catch (error) {
195
201
  const message = errorMessage(error);
196
202
  await options.store.updateActionInvocation(
@@ -207,7 +213,7 @@ function createGovernedActionHost(options) {
207
213
  input.tenantId,
208
214
  input.spaceId
209
215
  );
210
- return { ...executed, workflowId: durableWorkflowId };
216
+ return { ...withConsistency(executed, input.actionId), workflowId: durableWorkflowId };
211
217
  }
212
218
  async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}, executionReasonOverride) {
213
219
  const loadedInvocation = await options.store.getActionInvocation(
@@ -221,7 +227,7 @@ function createGovernedActionHost(options) {
221
227
  const resumingRunningInvocation = loadedInvocation.status === "running";
222
228
  let invocation = loadedInvocation;
223
229
  if (isTerminal(invocation.status) || invocation.status === "waiting_for_approval") {
224
- return actionResult(invocation);
230
+ return withConsistency(actionResult(invocation), invocation.actionId);
225
231
  }
226
232
  if (invocation.status === "running" && invocation.leaseOwner && executionOptions.leaseOwner !== invocation.leaseOwner) {
227
233
  return {
@@ -338,7 +344,7 @@ function createGovernedActionHost(options) {
338
344
  message: `Action ${action.actionId} requires durable capture-time authorization evidence`
339
345
  };
340
346
  await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
341
- return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
347
+ return withConsistency({ actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation }, action.actionId);
342
348
  }
343
349
  const bindingExpired = authorityMoment !== "capture" && invocation.authorizationBinding?.expiresAt !== void 0 && Date.parse(invocation.authorizationBinding.expiresAt) <= now().getTime();
344
350
  if (bindingExpired) {
@@ -351,7 +357,7 @@ function createGovernedActionHost(options) {
351
357
  message: `Authorization binding for action ${action.actionId} expired before execution`
352
358
  };
353
359
  await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
354
- return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
360
+ return withConsistency({ actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation }, action.actionId);
355
361
  }
356
362
  const executionAuthorized = authorityMoment === "capture" ? invocation.authorizationBinding !== void 0 : options.authorization.authorizeExecution ? await options.authorization.authorizeExecution({
357
363
  ...authorizationInput,
@@ -373,7 +379,7 @@ function createGovernedActionHost(options) {
373
379
  message: `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`
374
380
  };
375
381
  await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, { status: "reconciliation_required", error: reconciliation.message, authorizationReconciliation: reconciliation });
376
- return { actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation };
382
+ return withConsistency({ actionInvocationId, status: "reconciliation_required", error: reconciliation.message, reconciliation }, action.actionId);
377
383
  }
378
384
  const definitions = options.resolvePolicies ? await options.resolvePolicies({
379
385
  ...authorizationInput,
@@ -715,7 +721,7 @@ function createGovernedActionHost(options) {
715
721
  spaceId
716
722
  );
717
723
  if (!invocation) throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
718
- if (isTerminal(invocation.status)) return actionResult(invocation);
724
+ if (isTerminal(invocation.status)) return withConsistency(actionResult(invocation), invocation.actionId);
719
725
  if (invocation.status !== "waiting_for_approval") {
720
726
  return {
721
727
  ...actionResult(invocation),
@@ -765,9 +771,9 @@ function createGovernedActionHost(options) {
765
771
  const transitioned = transition.invocation ?? await options.store.getActionInvocation(actionInvocationId, tenantId, spaceId);
766
772
  if (!transition.applied || !transitioned) {
767
773
  if (!transitioned) throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
768
- return actionResult(transitioned);
774
+ return withConsistency(actionResult(transitioned), transitioned.actionId);
769
775
  }
770
- if (!decision.approved) return actionResult(transitioned);
776
+ if (!decision.approved) return withConsistency(actionResult(transitioned), transitioned.actionId);
771
777
  return executeInvocation(
772
778
  actionInvocationId,
773
779
  tenantId,
@@ -808,6 +814,13 @@ function createGovernedActionHost(options) {
808
814
  spaceId
809
815
  });
810
816
  }
817
+ function declaredConsistency(actionId) {
818
+ return actionResolver(actionId)?.execution?.consistency;
819
+ }
820
+ function withConsistency(result, actionId) {
821
+ const consistency = declaredConsistency(actionId);
822
+ return consistency ? { ...result, consistency } : result;
823
+ }
811
824
  async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1, transaction) {
812
825
  const timestamp = now();
813
826
  const envelope = {
@@ -830,7 +843,8 @@ function createGovernedActionHost(options) {
830
843
  recordedAt: timestamp,
831
844
  correlationId: invocation.correlationId,
832
845
  ...invocation.causationId ? { causationId: invocation.causationId } : {},
833
- ...invocation.provenance ? { provenance: { source: invocation.provenance.source, ...invocation.provenance.auditAttributes ? { auditAttributes: invocation.provenance.auditAttributes } : {} } } : {}
846
+ ...invocation.provenance ? { provenance: { source: invocation.provenance.source, ...invocation.provenance.auditAttributes ? { auditAttributes: invocation.provenance.auditAttributes } : {} } } : {},
847
+ ...declaredConsistency(invocation.actionId) === "provisional-until-reconciled" ? { consistency: "provisional-until-reconciled" } : {}
834
848
  };
835
849
  if (options.outbox) {
836
850
  const hostLifecycleEvent = (/* @__PURE__ */ new Set(["AdapterInvocationStarted", "AdapterInvocationSucceeded", "AdapterInvocationFailed", "ComplianceBlocked"])).has(envelope.eventType);
@@ -998,6 +1012,8 @@ function toEnterpriseEventEnvelope(event, metadata) {
998
1012
  payloadClassification: metadata.payloadClassification,
999
1013
  ...metadata.traceContext ? { traceContext: { ...metadata.traceContext } } : {},
1000
1014
  ...metadata.includeProvenance && event.provenance ? { provenance: event.provenance } : {},
1015
+ // A provisional fact must stay marked as provisional once it leaves the platform.
1016
+ ...event.consistency ? { consistency: event.consistency } : {},
1001
1017
  payload: event.payload
1002
1018
  };
1003
1019
  }
@@ -1429,10 +1445,11 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
1429
1445
  actor_id text NOT NULL, actor_type text NOT NULL, action_invocation_id text,
1430
1446
  payload jsonb NOT NULL, sequence bigint NOT NULL,
1431
1447
  occurred_at timestamptz NOT NULL, recorded_at timestamptz NOT NULL,
1432
- correlation_id text NOT NULL, causation_id text, provenance jsonb,
1448
+ correlation_id text NOT NULL, causation_id text, provenance jsonb, consistency text,
1433
1449
  UNIQUE (tenant_id, space_id, sequence)
1434
1450
  );
1435
1451
  ALTER TABLE fabric_platform.asset_events ADD COLUMN IF NOT EXISTS provenance jsonb;
1452
+ ALTER TABLE fabric_platform.asset_events ADD COLUMN IF NOT EXISTS consistency text;
1436
1453
  CREATE INDEX IF NOT EXISTS asset_events_subject_idx
1437
1454
  ON fabric_platform.asset_events
1438
1455
  (tenant_id, space_id, subject_type, subject_id, sequence);
@@ -1716,8 +1733,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
1716
1733
  `INSERT INTO fabric_platform.asset_events
1717
1734
  (id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
1718
1735
  actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
1719
- correlation_id,causation_id,provenance)
1720
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb)
1736
+ correlation_id,causation_id,provenance,consistency)
1737
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb,$18)
1721
1738
  ON CONFLICT (id) DO NOTHING`,
1722
1739
  [
1723
1740
  event.id,
@@ -1736,7 +1753,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
1736
1753
  event.recordedAt,
1737
1754
  event.correlationId,
1738
1755
  event.causationId ?? null,
1739
- event.provenance ? JSON.stringify(event.provenance) : null
1756
+ event.provenance ? JSON.stringify(event.provenance) : null,
1757
+ event.consistency ?? null
1740
1758
  ]
1741
1759
  );
1742
1760
  }
@@ -1747,8 +1765,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
1747
1765
  INSERT INTO fabric_platform.asset_events
1748
1766
  (id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
1749
1767
  actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
1750
- correlation_id,causation_id,provenance)
1751
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb)
1768
+ correlation_id,causation_id,provenance,consistency)
1769
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16,$17::jsonb,$19)
1752
1770
  ON CONFLICT (id) DO NOTHING RETURNING id
1753
1771
  )
1754
1772
  INSERT INTO fabric_platform.event_outbox
@@ -1773,7 +1791,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
1773
1791
  event.correlationId,
1774
1792
  event.causationId ?? null,
1775
1793
  event.provenance ? JSON.stringify(event.provenance) : null,
1776
- JSON.stringify(envelope)
1794
+ JSON.stringify(envelope),
1795
+ event.consistency ?? null
1777
1796
  ]
1778
1797
  );
1779
1798
  }
@@ -2015,7 +2034,8 @@ function toEventRecord(row) {
2015
2034
  recordedAt: new Date(row.recorded_at),
2016
2035
  correlationId: String(row.correlation_id),
2017
2036
  ...row.causation_id ? { causationId: String(row.causation_id) } : {},
2018
- ...row.provenance ? { provenance: row.provenance } : {}
2037
+ ...row.provenance ? { provenance: row.provenance } : {},
2038
+ ...row.consistency ? { consistency: row.consistency } : {}
2019
2039
  };
2020
2040
  }
2021
2041
  function toOutboxRecord(row) {