@fabricorg/platform-host 0.5.1 → 0.7.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
@@ -1,5 +1,7 @@
1
- import { ActorType, ActionId, ActionStatus, AgentActionRoute, AgentActionRiskTier, MutationFootprint, ExecutionPrincipal, PolicyOutcome, AssetEventEnvelope, ExecutionAttestation, ExternalReconciliation, PolicyObligation, PolicyObligationStatus, ActionDefinition, AdapterImplementation, FabricRuntimeServices, AgentActionPolicyEvaluator, MutationGovernanceResolver, RuntimePolicyDefinition } from '@fabricorg/platform';
1
+ import { ActorType, ActionId, ActionStatus, AgentActionRoute, AgentActionRiskTier, MutationFootprint, ExecutionPrincipal, GovernanceRuntimeEvidence, PolicyOutcome, AssetEventEnvelope, ExecutionAttestation, ExternalReconciliation, PolicyObligation, PolicyObligationStatus, ActionDefinition, AdapterImplementation, FabricRuntimeServices, AgentActionPolicyEvaluator, MutationGovernanceResolver, RuntimePolicyDefinition } from '@fabricorg/platform';
2
2
 
3
+ /** Durable Host lifecycle generation, independent from the npm package version. */
4
+ declare const PLATFORM_HOST_CONTRACT_VERSION: 2;
3
5
  interface PendingAssetEvent {
4
6
  eventType: string;
5
7
  subjectType: string;
@@ -21,6 +23,8 @@ interface ActionInvocationRecord {
21
23
  correlationId: string;
22
24
  causationId?: string;
23
25
  idempotencyKey?: string;
26
+ /** Opaque, non-secret reference used to revalidate delegated execution authority. */
27
+ authorizationBindingId?: string;
24
28
  attemptCount: number;
25
29
  leaseOwner?: string;
26
30
  leaseExpiresAt?: Date;
@@ -31,6 +35,7 @@ interface ActionInvocationRecord {
31
35
  approvalDecision?: PersistedActionApprovalDecision;
32
36
  mutationFootprint?: MutationFootprint;
33
37
  executionPrincipal?: ExecutionPrincipal;
38
+ runtimeEvidence?: GovernanceRuntimeEvidence;
34
39
  error?: string;
35
40
  createdAt: Date;
36
41
  updatedAt: Date;
@@ -115,6 +120,31 @@ interface PlatformHostStore<TDb> {
115
120
  nextEventSequence(tenantId: string, spaceId: string): Promise<number>;
116
121
  getEntityState(tenantId: string, spaceId: string, entityType: string, entityId: string): Promise<string | undefined>;
117
122
  }
123
+ /**
124
+ * Transaction-scoped Host operations used to commit domain writes and their
125
+ * canonical events as one unit of work.
126
+ *
127
+ * The supplied `db` must be bound to the same database transaction as every
128
+ * lifecycle method on this object. Implementations must roll the whole unit
129
+ * back when `run` throws.
130
+ */
131
+ interface PlatformHostMutationTransaction<TDb> {
132
+ readonly db: TDb;
133
+ appendEvent(event: AssetEventEnvelope): Promise<void>;
134
+ nextEventSequence(tenantId: string, spaceId: string): Promise<number>;
135
+ listEvents(tenantId: string, spaceId: string): Promise<AssetEventEnvelope[]>;
136
+ updateActionInvocation(id: string, tenantId: string, spaceId: string, patch: Partial<Pick<ActionInvocationRecord, "status" | "result" | "error">>): Promise<void>;
137
+ }
138
+ /**
139
+ * Optional production capability for atomic domain/event persistence.
140
+ *
141
+ * A mutating Host action uses this capability when available. Stores that do
142
+ * not implement it retain the legacy transaction boundary for compatibility,
143
+ * but cannot claim atomic domain-write/event persistence.
144
+ */
145
+ interface AtomicMutationPlatformHostStore<TDb> extends PlatformHostStore<TDb> {
146
+ transactionWithEvents<TResult>(run: (transaction: PlatformHostMutationTransaction<TDb>) => Promise<TResult>): Promise<TResult>;
147
+ }
118
148
  interface BeginApprovalDecisionInput {
119
149
  actionInvocationId: string;
120
150
  tenantId: string;
@@ -177,9 +207,27 @@ interface ActionAuthorizationInput {
177
207
  actorId: string;
178
208
  actorType: ActorType;
179
209
  }
210
+ type ActionExecutionReason = "initial" | "approval_resume" | "recovery";
211
+ /**
212
+ * Trusted execution-boundary authorization input.
213
+ *
214
+ * Parameters are the schema-parsed durable parameters, never caller-supplied
215
+ * transient values. `invocation` is the canonical record that will execute.
216
+ */
217
+ interface ActionExecutionAuthorizationInput extends ActionAuthorizationInput {
218
+ actionInvocationId: string;
219
+ parameters: unknown;
220
+ invocation: Readonly<ActionInvocationRecord>;
221
+ executionReason: ActionExecutionReason;
222
+ }
180
223
  interface PlatformHostAuthorization {
181
224
  checkEntitlement(input: ActionAuthorizationInput): Promise<boolean>;
182
225
  authorize(input: ActionAuthorizationInput): Promise<boolean>;
226
+ /**
227
+ * Optional final authorization immediately before policies and mutation
228
+ * execution. When absent, the Host reuses `authorize` at this boundary.
229
+ */
230
+ authorizeExecution?(input: ActionExecutionAuthorizationInput): Promise<boolean>;
183
231
  /** Optional distinct authorization boundary for human/system approval decisions. */
184
232
  authorizeApproval?(input: ActionAuthorizationInput & {
185
233
  decision: ActionApprovalDecision;
@@ -209,6 +257,8 @@ interface SubmitActionInput {
209
257
  causationId?: string;
210
258
  /** Stable logical command key. Reusing it returns/re-dispatches the original invocation. */
211
259
  idempotencyKey?: string;
260
+ /** Opaque, non-secret authorization/admission reference persisted with the invocation. */
261
+ authorizationBindingId?: string;
212
262
  }
213
263
  interface SubmitActionResult {
214
264
  actionInvocationId: string;
@@ -280,6 +330,8 @@ interface PlatformHostOptions<TDb> {
280
330
  completedAt: Date;
281
331
  output: Record<string, unknown>;
282
332
  }) => Promise<ExecutionAttestation | undefined> | ExecutionAttestation | undefined;
333
+ /** Package/provider/ruleset generations persisted with every newly created invocation. */
334
+ runtimeEvidence?: Partial<GovernanceRuntimeEvidence>;
283
335
  /** Lease held while an approved invocation resumes; defaults to five minutes. */
284
336
  approvalResumeLeaseDurationMs?: number;
285
337
  /** Remove forbidden/sensitive fields before durable invocation persistence. */
@@ -352,11 +404,26 @@ interface PlatformHostSqlResult<Row = Record<string, unknown>> {
352
404
  interface PlatformHostSqlClient {
353
405
  query<Row = Record<string, unknown>>(sql: string, values?: unknown[]): Promise<PlatformHostSqlResult<Row>>;
354
406
  }
407
+ interface PostgresPlatformHostTransactionContext<TDb> {
408
+ db: TDb;
409
+ sql: PlatformHostSqlClient;
410
+ }
411
+ /**
412
+ * Application-owned transaction binder.
413
+ *
414
+ * Platform Host cannot infer how an application's `TDb` is rebound to a
415
+ * `pg.PoolClient`. Production applications provide this adapter so domain
416
+ * methods and Host ledger writes share one PostgreSQL transaction.
417
+ */
418
+ interface PostgresPlatformHostTransactionProvider<TDb> {
419
+ run<TResult>(run: (context: PostgresPlatformHostTransactionContext<TDb>) => Promise<TResult>): Promise<TResult>;
420
+ }
355
421
  /** Durable mutation-pipeline ledger for Databricks Lakebase or standard Postgres. */
356
422
  declare class PostgresPlatformHostStore<TDb> implements RecoverablePlatformHostStore<TDb>, ApprovalPlatformHostStore<TDb>, GovernancePlatformHostStore<TDb> {
357
423
  readonly db: TDb;
358
424
  private readonly sql;
359
- constructor(db: TDb, sql: PlatformHostSqlClient);
425
+ readonly transactionWithEvents?: AtomicMutationPlatformHostStore<TDb>["transactionWithEvents"];
426
+ constructor(db: TDb, sql: PlatformHostSqlClient, transactionProvider?: PostgresPlatformHostTransactionProvider<TDb>);
360
427
  ensureSchema(): Promise<void>;
361
428
  transaction<TResult>(run: (db: TDb) => Promise<TResult>): Promise<TResult>;
362
429
  createActionInvocation(input: CreateActionInvocationInput): Promise<ActionInvocationRecord>;
@@ -396,4 +463,4 @@ declare function runPlatformActionWorkerCycle<TDb>(options: PlatformActionWorker
396
463
  /** Poll until aborted. Each iteration is bounded and waits without busy-spinning. */
397
464
  declare function runPlatformActionWorker<TDb>(options: PlatformActionWorkerOptions<TDb>): Promise<void>;
398
465
 
399
- export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type CreateActionInvocationInput, type DispatchActionInput, type DispatchActionResult, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type ListActionInvocationsInput, MemoryPlatformHostStore, type PendingAssetEvent, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostOptions, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PolicyEvaluationRecord, type PolicyObligationRecord, PostgresPlatformHostStore, type RecoverablePlatformHostStore, type SubmitActionInput, type SubmitActionResult, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
466
+ export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionExecutionAuthorizationInput, type ActionExecutionReason, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type AtomicMutationPlatformHostStore, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type CreateActionInvocationInput, type DispatchActionInput, type DispatchActionResult, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type ListActionInvocationsInput, MemoryPlatformHostStore, PLATFORM_HOST_CONTRACT_VERSION, type PendingAssetEvent, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostMutationTransaction, type PlatformHostOptions, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PolicyEvaluationRecord, type PolicyObligationRecord, PostgresPlatformHostStore, type PostgresPlatformHostTransactionContext, type PostgresPlatformHostTransactionProvider, type RecoverablePlatformHostStore, type SubmitActionInput, type SubmitActionResult, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { ActorType, ActionId, ActionStatus, AgentActionRoute, AgentActionRiskTier, MutationFootprint, ExecutionPrincipal, PolicyOutcome, AssetEventEnvelope, ExecutionAttestation, ExternalReconciliation, PolicyObligation, PolicyObligationStatus, ActionDefinition, AdapterImplementation, FabricRuntimeServices, AgentActionPolicyEvaluator, MutationGovernanceResolver, RuntimePolicyDefinition } from '@fabricorg/platform';
1
+ import { ActorType, ActionId, ActionStatus, AgentActionRoute, AgentActionRiskTier, MutationFootprint, ExecutionPrincipal, GovernanceRuntimeEvidence, PolicyOutcome, AssetEventEnvelope, ExecutionAttestation, ExternalReconciliation, PolicyObligation, PolicyObligationStatus, ActionDefinition, AdapterImplementation, FabricRuntimeServices, AgentActionPolicyEvaluator, MutationGovernanceResolver, RuntimePolicyDefinition } from '@fabricorg/platform';
2
2
 
3
+ /** Durable Host lifecycle generation, independent from the npm package version. */
4
+ declare const PLATFORM_HOST_CONTRACT_VERSION: 2;
3
5
  interface PendingAssetEvent {
4
6
  eventType: string;
5
7
  subjectType: string;
@@ -21,6 +23,8 @@ interface ActionInvocationRecord {
21
23
  correlationId: string;
22
24
  causationId?: string;
23
25
  idempotencyKey?: string;
26
+ /** Opaque, non-secret reference used to revalidate delegated execution authority. */
27
+ authorizationBindingId?: string;
24
28
  attemptCount: number;
25
29
  leaseOwner?: string;
26
30
  leaseExpiresAt?: Date;
@@ -31,6 +35,7 @@ interface ActionInvocationRecord {
31
35
  approvalDecision?: PersistedActionApprovalDecision;
32
36
  mutationFootprint?: MutationFootprint;
33
37
  executionPrincipal?: ExecutionPrincipal;
38
+ runtimeEvidence?: GovernanceRuntimeEvidence;
34
39
  error?: string;
35
40
  createdAt: Date;
36
41
  updatedAt: Date;
@@ -115,6 +120,31 @@ interface PlatformHostStore<TDb> {
115
120
  nextEventSequence(tenantId: string, spaceId: string): Promise<number>;
116
121
  getEntityState(tenantId: string, spaceId: string, entityType: string, entityId: string): Promise<string | undefined>;
117
122
  }
123
+ /**
124
+ * Transaction-scoped Host operations used to commit domain writes and their
125
+ * canonical events as one unit of work.
126
+ *
127
+ * The supplied `db` must be bound to the same database transaction as every
128
+ * lifecycle method on this object. Implementations must roll the whole unit
129
+ * back when `run` throws.
130
+ */
131
+ interface PlatformHostMutationTransaction<TDb> {
132
+ readonly db: TDb;
133
+ appendEvent(event: AssetEventEnvelope): Promise<void>;
134
+ nextEventSequence(tenantId: string, spaceId: string): Promise<number>;
135
+ listEvents(tenantId: string, spaceId: string): Promise<AssetEventEnvelope[]>;
136
+ updateActionInvocation(id: string, tenantId: string, spaceId: string, patch: Partial<Pick<ActionInvocationRecord, "status" | "result" | "error">>): Promise<void>;
137
+ }
138
+ /**
139
+ * Optional production capability for atomic domain/event persistence.
140
+ *
141
+ * A mutating Host action uses this capability when available. Stores that do
142
+ * not implement it retain the legacy transaction boundary for compatibility,
143
+ * but cannot claim atomic domain-write/event persistence.
144
+ */
145
+ interface AtomicMutationPlatformHostStore<TDb> extends PlatformHostStore<TDb> {
146
+ transactionWithEvents<TResult>(run: (transaction: PlatformHostMutationTransaction<TDb>) => Promise<TResult>): Promise<TResult>;
147
+ }
118
148
  interface BeginApprovalDecisionInput {
119
149
  actionInvocationId: string;
120
150
  tenantId: string;
@@ -177,9 +207,27 @@ interface ActionAuthorizationInput {
177
207
  actorId: string;
178
208
  actorType: ActorType;
179
209
  }
210
+ type ActionExecutionReason = "initial" | "approval_resume" | "recovery";
211
+ /**
212
+ * Trusted execution-boundary authorization input.
213
+ *
214
+ * Parameters are the schema-parsed durable parameters, never caller-supplied
215
+ * transient values. `invocation` is the canonical record that will execute.
216
+ */
217
+ interface ActionExecutionAuthorizationInput extends ActionAuthorizationInput {
218
+ actionInvocationId: string;
219
+ parameters: unknown;
220
+ invocation: Readonly<ActionInvocationRecord>;
221
+ executionReason: ActionExecutionReason;
222
+ }
180
223
  interface PlatformHostAuthorization {
181
224
  checkEntitlement(input: ActionAuthorizationInput): Promise<boolean>;
182
225
  authorize(input: ActionAuthorizationInput): Promise<boolean>;
226
+ /**
227
+ * Optional final authorization immediately before policies and mutation
228
+ * execution. When absent, the Host reuses `authorize` at this boundary.
229
+ */
230
+ authorizeExecution?(input: ActionExecutionAuthorizationInput): Promise<boolean>;
183
231
  /** Optional distinct authorization boundary for human/system approval decisions. */
184
232
  authorizeApproval?(input: ActionAuthorizationInput & {
185
233
  decision: ActionApprovalDecision;
@@ -209,6 +257,8 @@ interface SubmitActionInput {
209
257
  causationId?: string;
210
258
  /** Stable logical command key. Reusing it returns/re-dispatches the original invocation. */
211
259
  idempotencyKey?: string;
260
+ /** Opaque, non-secret authorization/admission reference persisted with the invocation. */
261
+ authorizationBindingId?: string;
212
262
  }
213
263
  interface SubmitActionResult {
214
264
  actionInvocationId: string;
@@ -280,6 +330,8 @@ interface PlatformHostOptions<TDb> {
280
330
  completedAt: Date;
281
331
  output: Record<string, unknown>;
282
332
  }) => Promise<ExecutionAttestation | undefined> | ExecutionAttestation | undefined;
333
+ /** Package/provider/ruleset generations persisted with every newly created invocation. */
334
+ runtimeEvidence?: Partial<GovernanceRuntimeEvidence>;
283
335
  /** Lease held while an approved invocation resumes; defaults to five minutes. */
284
336
  approvalResumeLeaseDurationMs?: number;
285
337
  /** Remove forbidden/sensitive fields before durable invocation persistence. */
@@ -352,11 +404,26 @@ interface PlatformHostSqlResult<Row = Record<string, unknown>> {
352
404
  interface PlatformHostSqlClient {
353
405
  query<Row = Record<string, unknown>>(sql: string, values?: unknown[]): Promise<PlatformHostSqlResult<Row>>;
354
406
  }
407
+ interface PostgresPlatformHostTransactionContext<TDb> {
408
+ db: TDb;
409
+ sql: PlatformHostSqlClient;
410
+ }
411
+ /**
412
+ * Application-owned transaction binder.
413
+ *
414
+ * Platform Host cannot infer how an application's `TDb` is rebound to a
415
+ * `pg.PoolClient`. Production applications provide this adapter so domain
416
+ * methods and Host ledger writes share one PostgreSQL transaction.
417
+ */
418
+ interface PostgresPlatformHostTransactionProvider<TDb> {
419
+ run<TResult>(run: (context: PostgresPlatformHostTransactionContext<TDb>) => Promise<TResult>): Promise<TResult>;
420
+ }
355
421
  /** Durable mutation-pipeline ledger for Databricks Lakebase or standard Postgres. */
356
422
  declare class PostgresPlatformHostStore<TDb> implements RecoverablePlatformHostStore<TDb>, ApprovalPlatformHostStore<TDb>, GovernancePlatformHostStore<TDb> {
357
423
  readonly db: TDb;
358
424
  private readonly sql;
359
- constructor(db: TDb, sql: PlatformHostSqlClient);
425
+ readonly transactionWithEvents?: AtomicMutationPlatformHostStore<TDb>["transactionWithEvents"];
426
+ constructor(db: TDb, sql: PlatformHostSqlClient, transactionProvider?: PostgresPlatformHostTransactionProvider<TDb>);
360
427
  ensureSchema(): Promise<void>;
361
428
  transaction<TResult>(run: (db: TDb) => Promise<TResult>): Promise<TResult>;
362
429
  createActionInvocation(input: CreateActionInvocationInput): Promise<ActionInvocationRecord>;
@@ -396,4 +463,4 @@ declare function runPlatformActionWorkerCycle<TDb>(options: PlatformActionWorker
396
463
  /** Poll until aborted. Each iteration is bounded and waits without busy-spinning. */
397
464
  declare function runPlatformActionWorker<TDb>(options: PlatformActionWorkerOptions<TDb>): Promise<void>;
398
465
 
399
- export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type CreateActionInvocationInput, type DispatchActionInput, type DispatchActionResult, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type ListActionInvocationsInput, MemoryPlatformHostStore, type PendingAssetEvent, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostOptions, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PolicyEvaluationRecord, type PolicyObligationRecord, PostgresPlatformHostStore, type RecoverablePlatformHostStore, type SubmitActionInput, type SubmitActionResult, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
466
+ export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionExecutionAuthorizationInput, type ActionExecutionReason, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type AtomicMutationPlatformHostStore, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type CreateActionInvocationInput, type DispatchActionInput, type DispatchActionResult, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type ListActionInvocationsInput, MemoryPlatformHostStore, PLATFORM_HOST_CONTRACT_VERSION, type PendingAssetEvent, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostMutationTransaction, type PlatformHostOptions, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PolicyEvaluationRecord, type PolicyObligationRecord, PostgresPlatformHostStore, type PostgresPlatformHostTransactionContext, type PostgresPlatformHostTransactionProvider, type RecoverablePlatformHostStore, type SubmitActionInput, type SubmitActionResult, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
package/dist/index.js CHANGED
@@ -1,10 +1,18 @@
1
- import { AdapterRegistry, resolveAction, createFabricId, assertMutationGovernanceContext, evaluatePolicyDefinitions, aggregatePolicyOutcomes, validateTransition, executeWithAdapterRetry, resolveStateMachine } from '@fabricorg/platform';
1
+ import { AdapterRegistry, resolveAction, createFabricId, assertGovernanceRuntimeEvidence, FABRIC_GOVERNANCE_CONTRACT_VERSION, assertMutationGovernanceContext, evaluatePolicyDefinitions, aggregatePolicyOutcomes, validateTransition, executeWithAdapterRetry, resolveStateMachine } from '@fabricorg/platform';
2
+
3
+ // src/host.ts
4
+
5
+ // src/types.ts
6
+ var PLATFORM_HOST_CONTRACT_VERSION = 2;
2
7
 
3
8
  // src/host.ts
4
9
  var DEFAULT_EXTRACT_EVENTS = (data) => {
5
10
  const value = data._events;
6
11
  return Array.isArray(value) ? value : [];
7
12
  };
13
+ var RecoverableFinalizationError = class extends Error {
14
+ name = "RecoverableFinalizationError";
15
+ };
8
16
  function createGovernedActionHost(options) {
9
17
  const adapters = new AdapterRegistry();
10
18
  for (const adapter of options.adapters ?? []) adapters.register(adapter);
@@ -25,6 +33,12 @@ function createGovernedActionHost(options) {
25
33
  const actionInvocationId = createFabricId("act");
26
34
  const correlationId = input.correlationId ?? createFabricId("corr");
27
35
  const durableParameters = options.redactActionParameters ? options.redactActionParameters(input.actionId, input.parameters) : input.parameters;
36
+ const runtimeEvidence = {
37
+ governanceContractVersion: FABRIC_GOVERNANCE_CONTRACT_VERSION,
38
+ hostContractVersion: PLATFORM_HOST_CONTRACT_VERSION,
39
+ ...options.runtimeEvidence
40
+ };
41
+ assertGovernanceRuntimeEvidence(runtimeEvidence);
28
42
  const durableInvocation = await options.store.createActionInvocation({
29
43
  id: actionInvocationId,
30
44
  tenantId: input.tenantId,
@@ -36,9 +50,11 @@ function createGovernedActionHost(options) {
36
50
  status: "pending",
37
51
  parameters: durableParameters,
38
52
  result: {},
53
+ runtimeEvidence,
39
54
  correlationId,
40
55
  ...input.causationId ? { causationId: input.causationId } : {},
41
- ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
56
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
57
+ ...input.authorizationBindingId ? { authorizationBindingId: input.authorizationBindingId } : {}
42
58
  });
43
59
  const durableWorkflowId = `action-invocation-${durableInvocation.id}`;
44
60
  if (durableInvocation.id !== actionInvocationId && isTerminal(durableInvocation.status)) {
@@ -86,7 +102,7 @@ function createGovernedActionHost(options) {
86
102
  );
87
103
  return { ...executed, workflowId: durableWorkflowId };
88
104
  }
89
- async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}) {
105
+ async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}, executionReasonOverride) {
90
106
  const loadedInvocation = await options.store.getActionInvocation(
91
107
  actionInvocationId,
92
108
  tenantId,
@@ -95,6 +111,7 @@ function createGovernedActionHost(options) {
95
111
  if (!loadedInvocation) {
96
112
  throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
97
113
  }
114
+ const resumingRunningInvocation = loadedInvocation.status === "running";
98
115
  let invocation = loadedInvocation;
99
116
  if (isTerminal(invocation.status) || invocation.status === "waiting_for_approval") {
100
117
  return actionResult(invocation);
@@ -196,6 +213,28 @@ function createGovernedActionHost(options) {
196
213
  }
197
214
  }
198
215
  const authorizationInput = toAuthorizationInput(action, invocation);
216
+ const executionReason = executionReasonOverride ?? (invocation.attemptCount > 1 || resumingRunningInvocation && invocation.attemptCount === 0 ? "recovery" : "initial");
217
+ if (!await options.authorization.checkEntitlement(authorizationInput)) {
218
+ return fail(
219
+ invocation,
220
+ "failed",
221
+ `Module "${action.namespace}" is no longer enabled for tenant ${tenantId}`
222
+ );
223
+ }
224
+ const executionAuthorized = options.authorization.authorizeExecution ? await options.authorization.authorizeExecution({
225
+ ...authorizationInput,
226
+ actionInvocationId,
227
+ parameters: parsed.data,
228
+ invocation,
229
+ executionReason
230
+ }) : await options.authorization.authorize(authorizationInput);
231
+ if (!executionAuthorized) {
232
+ return fail(
233
+ invocation,
234
+ "failed",
235
+ `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`
236
+ );
237
+ }
199
238
  const definitions = options.resolvePolicies ? await options.resolvePolicies({
200
239
  ...authorizationInput,
201
240
  declaredPolicyIds: action.policies ?? []
@@ -280,8 +319,8 @@ function createGovernedActionHost(options) {
280
319
  let data;
281
320
  let domainEvents = [];
282
321
  try {
283
- const handlerResult = await options.store.transaction(
284
- (db) => action.handler ? action.handler(
322
+ const runHandler = async (db, transaction) => {
323
+ const handlerResult = action.handler ? await action.handler(
285
324
  {
286
325
  actionInvocationId,
287
326
  tenantId,
@@ -294,28 +333,39 @@ function createGovernedActionHost(options) {
294
333
  services: options.services
295
334
  },
296
335
  parsed.data
297
- ) : Promise.resolve({ success: true, data: {} })
298
- );
299
- if (!handlerResult.success) {
300
- return fail(invocation, "failed", handlerResult.error ?? "Action handler failed");
301
- }
302
- data = handlerResult.data ?? {};
303
- domainEvents = extractEvents(data);
304
- const undeclaredEvent = domainEvents.find(
305
- (event) => !action.emitsEvents.includes(event.eventType)
306
- );
307
- if (undeclaredEvent) {
308
- return fail(
309
- invocation,
310
- "failed",
311
- `${action.actionId} emitted undeclared event type ${undeclaredEvent.eventType}`
336
+ ) : { success: true, data: {} };
337
+ if (!handlerResult.success) {
338
+ throw new Error(handlerResult.error ?? "Action handler failed");
339
+ }
340
+ const handlerData = handlerResult.data ?? {};
341
+ const events = extractEvents(handlerData);
342
+ const undeclaredEvent = events.find(
343
+ (event) => !action.emitsEvents.includes(event.eventType)
312
344
  );
313
- }
314
- if (action.eventPhase !== "after_adapters") {
315
- for (const [index, event] of domainEvents.entries()) {
316
- await appendEvent(invocation, event, `domain:${index}`, action.version);
345
+ if (undeclaredEvent) {
346
+ throw new Error(
347
+ `${action.actionId} emitted undeclared event type ${undeclaredEvent.eventType}`
348
+ );
317
349
  }
318
- }
350
+ if (action.eventPhase !== "after_adapters") {
351
+ for (const [index, event] of events.entries()) {
352
+ await appendEvent(
353
+ invocation,
354
+ event,
355
+ `domain:${index}`,
356
+ action.version,
357
+ transaction
358
+ );
359
+ }
360
+ }
361
+ return { data: handlerData, domainEvents: events };
362
+ };
363
+ const atomicStore = asAtomicMutationStore(options.store);
364
+ const executed = atomicStore ? await atomicStore.transactionWithEvents(
365
+ (transaction) => runHandler(transaction.db, transaction)
366
+ ) : await options.store.transaction((db) => runHandler(db));
367
+ data = executed.data;
368
+ domainEvents = executed.domainEvents;
319
369
  } catch (error) {
320
370
  return fail(invocation, "failed", errorMessage(error));
321
371
  }
@@ -446,11 +496,6 @@ function createGovernedActionHost(options) {
446
496
  return fail(invocation, "failed", message);
447
497
  }
448
498
  }
449
- if (action.eventPhase === "after_adapters") {
450
- for (const [index, event] of domainEvents.entries()) {
451
- await appendEvent(invocation, event, `domain:${index}`, action.version);
452
- }
453
- }
454
499
  const governanceStore = asGovernanceStore(options.store);
455
500
  if (governanceStore && options.resolvePolicyObligations) {
456
501
  const obligations = await governanceStore.listPolicyObligations(actionInvocationId, tenantId, spaceId);
@@ -460,10 +505,43 @@ function createGovernedActionHost(options) {
460
505
  }
461
506
  }
462
507
  const result = withoutPrivateHostFields(data, eventResultFields);
463
- await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
464
- status: "completed",
465
- result
466
- });
508
+ try {
509
+ const atomicStore = asAtomicMutationStore(options.store);
510
+ if (action.eventPhase === "after_adapters" && atomicStore) {
511
+ await atomicStore.transactionWithEvents(async (transaction) => {
512
+ for (const [index, event] of domainEvents.entries()) {
513
+ await appendEvent(
514
+ invocation,
515
+ event,
516
+ `domain:${index}`,
517
+ action.version,
518
+ transaction
519
+ );
520
+ }
521
+ await transaction.updateActionInvocation(
522
+ actionInvocationId,
523
+ tenantId,
524
+ spaceId,
525
+ { status: "completed", result }
526
+ );
527
+ });
528
+ } else {
529
+ if (action.eventPhase === "after_adapters") {
530
+ for (const [index, event] of domainEvents.entries()) {
531
+ await appendEvent(invocation, event, `domain:${index}`, action.version);
532
+ }
533
+ }
534
+ await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
535
+ status: "completed",
536
+ result
537
+ });
538
+ }
539
+ } catch (error) {
540
+ if (action.eventPhase === "after_adapters") {
541
+ throw new RecoverableFinalizationError(errorMessage(error));
542
+ }
543
+ throw error;
544
+ }
467
545
  return {
468
546
  actionInvocationId,
469
547
  status: "completed",
@@ -472,6 +550,7 @@ function createGovernedActionHost(options) {
472
550
  ...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {}
473
551
  };
474
552
  } catch (error) {
553
+ if (error instanceof RecoverableFinalizationError) throw error;
475
554
  return fail(invocation, "failed", errorMessage(error));
476
555
  }
477
556
  }
@@ -536,7 +615,13 @@ function createGovernedActionHost(options) {
536
615
  return actionResult(transitioned);
537
616
  }
538
617
  if (!decision.approved) return actionResult(transitioned);
539
- return executeInvocation(actionInvocationId, tenantId, spaceId, { leaseOwner });
618
+ return executeInvocation(
619
+ actionInvocationId,
620
+ tenantId,
621
+ spaceId,
622
+ { leaseOwner },
623
+ "approval_resume"
624
+ );
540
625
  }
541
626
  async function recordExecutionAttestation(actionInvocationId, tenantId, spaceId, attestation, adapterInvocationId) {
542
627
  const governanceStore = asGovernanceStore(options.store);
@@ -570,7 +655,7 @@ function createGovernedActionHost(options) {
570
655
  spaceId
571
656
  });
572
657
  }
573
- async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1) {
658
+ async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1, transaction) {
574
659
  const timestamp = now();
575
660
  const envelope = {
576
661
  id: lifecycleId("evt", invocation.id, deduplicationKey),
@@ -584,7 +669,7 @@ function createGovernedActionHost(options) {
584
669
  actorType: invocation.actorType,
585
670
  actionInvocationId: invocation.id,
586
671
  payload: event.payload,
587
- sequence: await options.store.nextEventSequence(
672
+ sequence: await (transaction ?? options.store).nextEventSequence(
588
673
  invocation.tenantId,
589
674
  invocation.spaceId
590
675
  ),
@@ -593,7 +678,7 @@ function createGovernedActionHost(options) {
593
678
  correlationId: invocation.correlationId,
594
679
  ...invocation.causationId ? { causationId: invocation.causationId } : {}
595
680
  };
596
- await options.store.appendEvent(envelope);
681
+ await (transaction ?? options.store).appendEvent(envelope);
597
682
  }
598
683
  async function fail(invocation, status, error) {
599
684
  await options.store.updateActionInvocation(
@@ -626,6 +711,10 @@ function asApprovalStore(store) {
626
711
  const candidate = store;
627
712
  return typeof candidate.recordHitlDecision === "function" && typeof candidate.beginApprovalDecision === "function" ? store : void 0;
628
713
  }
714
+ function asAtomicMutationStore(store) {
715
+ const candidate = store;
716
+ return typeof candidate.transactionWithEvents === "function" ? store : void 0;
717
+ }
629
718
  function asGovernanceStore(store) {
630
719
  const candidate = store;
631
720
  return typeof candidate.recordMutationGovernance === "function" && typeof candidate.appendPolicyObligations === "function" && typeof candidate.appendExecutionAttestation === "function" ? store : void 0;
@@ -852,13 +941,26 @@ var MemoryPlatformHostStore = class {
852
941
  };
853
942
 
854
943
  // src/postgres-store.ts
855
- var PostgresPlatformHostStore = class {
856
- constructor(db, sql) {
944
+ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
945
+ constructor(db, sql, transactionProvider) {
857
946
  this.db = db;
858
947
  this.sql = sql;
948
+ if (transactionProvider) {
949
+ this.transactionWithEvents = async (run) => transactionProvider.run(async ({ db: db2, sql: sql2 }) => {
950
+ const scoped = new _PostgresPlatformHostStore(db2, sql2);
951
+ return run({
952
+ db: db2,
953
+ appendEvent: (event) => scoped.appendEvent(event),
954
+ nextEventSequence: (tenantId, spaceId) => scoped.nextEventSequence(tenantId, spaceId),
955
+ listEvents: (tenantId, spaceId) => scoped.listEvents(tenantId, spaceId),
956
+ updateActionInvocation: (id, tenantId, spaceId, patch) => scoped.updateActionInvocation(id, tenantId, spaceId, patch)
957
+ });
958
+ });
959
+ }
859
960
  }
860
961
  db;
861
962
  sql;
963
+ transactionWithEvents;
862
964
  async ensureSchema() {
863
965
  await this.sql.query(`
864
966
  CREATE SCHEMA IF NOT EXISTS fabric_platform;
@@ -867,15 +969,17 @@ var PostgresPlatformHostStore = class {
867
969
  action_id text NOT NULL, action_version integer NOT NULL,
868
970
  actor_id text NOT NULL, actor_type text NOT NULL, status text NOT NULL,
869
971
  parameters jsonb NOT NULL, result jsonb NOT NULL DEFAULT '{}'::jsonb,
870
- correlation_id text NOT NULL, causation_id text, idempotency_key text, error text,
972
+ correlation_id text NOT NULL, causation_id text, idempotency_key text,
973
+ authorization_binding_id text, error text,
871
974
  attempt_count integer NOT NULL DEFAULT 0, lease_owner text,
872
975
  lease_expires_at timestamptz, hitl_route text, hitl_risk_tier text,
873
976
  hitl_reason text, hitl_policy_version text, approval_decision jsonb,
874
- mutation_footprint jsonb, execution_principal jsonb,
977
+ mutation_footprint jsonb, execution_principal jsonb, runtime_evidence jsonb,
875
978
  created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL
876
979
  );
877
980
  ALTER TABLE fabric_platform.action_invocations
878
981
  ADD COLUMN IF NOT EXISTS idempotency_key text,
982
+ ADD COLUMN IF NOT EXISTS authorization_binding_id text,
879
983
  ADD COLUMN IF NOT EXISTS attempt_count integer NOT NULL DEFAULT 0,
880
984
  ADD COLUMN IF NOT EXISTS lease_owner text,
881
985
  ADD COLUMN IF NOT EXISTS lease_expires_at timestamptz,
@@ -885,7 +989,8 @@ var PostgresPlatformHostStore = class {
885
989
  ADD COLUMN IF NOT EXISTS hitl_policy_version text,
886
990
  ADD COLUMN IF NOT EXISTS approval_decision jsonb,
887
991
  ADD COLUMN IF NOT EXISTS mutation_footprint jsonb,
888
- ADD COLUMN IF NOT EXISTS execution_principal jsonb;
992
+ ADD COLUMN IF NOT EXISTS execution_principal jsonb,
993
+ ADD COLUMN IF NOT EXISTS runtime_evidence jsonb;
889
994
  CREATE UNIQUE INDEX IF NOT EXISTS action_invocations_idempotency_idx
890
995
  ON fabric_platform.action_invocations
891
996
  (tenant_id, space_id, action_id, idempotency_key)
@@ -949,8 +1054,9 @@ var PostgresPlatformHostStore = class {
949
1054
  const result = await this.sql.query(
950
1055
  `INSERT INTO fabric_platform.action_invocations
951
1056
  (id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
952
- parameters,result,correlation_id,causation_id,idempotency_key,error,created_at,updated_at)
953
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$15)
1057
+ parameters,result,correlation_id,causation_id,idempotency_key,
1058
+ authorization_binding_id,error,runtime_evidence,created_at,updated_at)
1059
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16::jsonb,$17,$17)
954
1060
  ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
955
1061
  WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
956
1062
  RETURNING *`,
@@ -968,7 +1074,9 @@ var PostgresPlatformHostStore = class {
968
1074
  input.correlationId,
969
1075
  input.causationId ?? null,
970
1076
  input.idempotencyKey ?? null,
1077
+ input.authorizationBindingId ?? null,
971
1078
  input.error ?? null,
1079
+ input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
972
1080
  now
973
1081
  ]
974
1082
  );
@@ -1330,6 +1438,7 @@ function toActionRecord(row) {
1330
1438
  correlationId: String(row.correlation_id),
1331
1439
  ...row.causation_id ? { causationId: String(row.causation_id) } : {},
1332
1440
  ...row.idempotency_key ? { idempotencyKey: String(row.idempotency_key) } : {},
1441
+ ...row.authorization_binding_id ? { authorizationBindingId: String(row.authorization_binding_id) } : {},
1333
1442
  attemptCount: Number(row.attempt_count ?? 0),
1334
1443
  ...row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {},
1335
1444
  ...row.lease_expires_at ? { leaseExpiresAt: new Date(row.lease_expires_at) } : {},
@@ -1340,6 +1449,7 @@ function toActionRecord(row) {
1340
1449
  ...row.approval_decision ? { approvalDecision: toApprovalDecision(row.approval_decision) } : {},
1341
1450
  ...row.mutation_footprint ? { mutationFootprint: row.mutation_footprint } : {},
1342
1451
  ...row.execution_principal ? { executionPrincipal: row.execution_principal } : {},
1452
+ ...row.runtime_evidence ? { runtimeEvidence: row.runtime_evidence } : {},
1343
1453
  ...row.error ? { error: String(row.error) } : {},
1344
1454
  createdAt: new Date(row.created_at),
1345
1455
  updatedAt: new Date(row.updated_at)
@@ -1473,6 +1583,6 @@ async function abortableDelay(milliseconds, signal) {
1473
1583
  });
1474
1584
  }
1475
1585
 
1476
- export { MemoryPlatformHostStore, PostgresPlatformHostStore, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
1586
+ export { MemoryPlatformHostStore, PLATFORM_HOST_CONTRACT_VERSION, PostgresPlatformHostStore, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
1477
1587
  //# sourceMappingURL=index.js.map
1478
1588
  //# sourceMappingURL=index.js.map