@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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @fabricorg/platform-host
2
2
 
3
+ ## 0.7.0 — 2026-07-27
4
+
5
+ - Add parameter-aware execution authorization immediately before policies and mutation execution,
6
+ including approval resume and interrupted-work recovery.
7
+ - Persist an opaque authorization binding with each invocation so applications can revalidate the
8
+ exact admission without retaining credentials or caller proofs.
9
+ - Add an optional transaction-scoped mutation unit of work that commits domain writes and
10
+ before-adapter events atomically.
11
+ - Atomically append `after_adapters` completion events with invocation completion when the store
12
+ provides the unit-of-work capability; finalization failures remain recoverable and do not repeat
13
+ succeeded adapter checkpoints.
14
+ - Advance the durable Host lifecycle contract to version 2.
15
+
16
+ ## 0.6.0
17
+
18
+ - Persist governance, Host, provider-bridge, package, and policy-ruleset generations with every new invocation.
19
+ - Add PostgreSQL `runtime_evidence` migration and in-memory/PostgreSQL conformance coverage.
20
+ - Run the shared external-mutation fixture used by the .NET parity suite.
21
+
3
22
  ## 0.5.1 — 2026-07-20
4
23
 
5
24
  - Include the audit-safe, redacted adapter input in execution-attestation extraction so integrations can identify external resources even when provider responses are minimal.
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.7.0 @fabricorg/platform-host@^0.4.1
6
+ pnpm add @fabricorg/platform@^0.9.0 @fabricorg/platform-host@^0.7.0
7
7
  ```
8
8
 
9
9
  Vertical packages register `FabricModule` definitions. Host applications provide tenant authorization,
@@ -36,6 +36,51 @@ adapter that already reached `succeeded`, and event, policy, and adapter writes
36
36
  stale action that was not declared idempotent fails terminally for manual reconciliation instead of
37
37
  silently rerunning unknown side effects.
38
38
 
39
+ ## Execution authorization
40
+
41
+ Submission authorization proves that a caller may create an invocation. Applications that delegate
42
+ resource-scoped authority should also configure `authorizeExecution`. The Host calls it with the
43
+ schema-parsed durable parameters, canonical invocation, opaque `authorizationBindingId`, and an
44
+ `executionReason` of `initial`, `approval_resume`, or `recovery` immediately before policies and
45
+ mutation code run.
46
+
47
+ ```ts
48
+ const host = createGovernedActionHost({
49
+ store,
50
+ authorization: {
51
+ checkEntitlement,
52
+ authorize: authorizeSubmission,
53
+ authorizeExecution: async ({ invocation, parameters, executionReason }) =>
54
+ admissionStore.authorize({
55
+ admissionId: invocation.authorizationBindingId,
56
+ parameters,
57
+ executionReason,
58
+ }),
59
+ },
60
+ });
61
+ ```
62
+
63
+ When `authorizeExecution` is absent, the Host reuses `authorize` at the execution boundary. A
64
+ completed idempotent replay returns its existing result without creating or executing a new
65
+ mutation.
66
+
67
+ ## Atomic mutation unit of work
68
+
69
+ Production stores can implement `AtomicMutationPlatformHostStore.transactionWithEvents`. The
70
+ transaction-scoped `db`, event sequence, event append, event listing, and invocation update methods
71
+ must all use the same database transaction.
72
+
73
+ `PostgresPlatformHostStore` enables this capability when constructed with a
74
+ `PostgresPlatformHostTransactionProvider`. The application owns that narrow binder because only the
75
+ application knows how its `TDb` is rebound to the transaction's SQL client.
76
+
77
+ - `before_adapters`: handler domain writes and declared domain events commit or roll back together.
78
+ - `after_adapters`: completion events and invocation completion commit together. A finalization
79
+ failure leaves the invocation recoverable; succeeded adapter checkpoints are not repeated.
80
+
81
+ Stores without this additive capability retain the legacy boundary for compatibility and must not
82
+ claim atomic domain-write/event persistence.
83
+
39
84
  ## Agent HITL
40
85
 
41
86
  Hosts can inject a vertical-owned `hitlEvaluator`. It runs only for `actorType: "agent"`, after schema
@@ -98,6 +143,22 @@ The built-in stores persist footprints, delegation, obligations, attestations, a
98
143
  observations. Provider-specific authentication and clients do not belong here. For Databricks, use
99
144
  the optional Platform integration exported by `@fabric-harness/databricks`.
100
145
 
146
+ Every newly submitted invocation also records `runtimeEvidence`. The host always supplies the
147
+ portable governance and host contract generations; applications should add the deployed host
148
+ package version, policy ruleset version, and provider bridge identity. This makes an audit record
149
+ explain which contract and provider adapter governed a mutation after dependencies have moved on.
150
+
151
+ ```ts
152
+ const host = createGovernedActionHost({
153
+ // ...
154
+ runtimeEvidence: {
155
+ hostPackageVersion: "0.7.0",
156
+ policyRulesetVersion: "gtm-rules.v8",
157
+ providerBridge: { name: "@fabric-harness/databricks", version: "1" },
158
+ },
159
+ });
160
+ ```
161
+
101
162
  `MemoryPlatformHostStore` is for tests and local demos. Production control planes use
102
163
  `PostgresPlatformHostStore` with Databricks Lakebase (or standard Postgres), call
103
164
  `ensureSchema()` at startup, and hydrate projections from `listEvents()`.
package/dist/index.cjs CHANGED
@@ -2,11 +2,19 @@
2
2
 
3
3
  var platform = require('@fabricorg/platform');
4
4
 
5
+ // src/host.ts
6
+
7
+ // src/types.ts
8
+ var PLATFORM_HOST_CONTRACT_VERSION = 2;
9
+
5
10
  // src/host.ts
6
11
  var DEFAULT_EXTRACT_EVENTS = (data) => {
7
12
  const value = data._events;
8
13
  return Array.isArray(value) ? value : [];
9
14
  };
15
+ var RecoverableFinalizationError = class extends Error {
16
+ name = "RecoverableFinalizationError";
17
+ };
10
18
  function createGovernedActionHost(options) {
11
19
  const adapters = new platform.AdapterRegistry();
12
20
  for (const adapter of options.adapters ?? []) adapters.register(adapter);
@@ -27,6 +35,12 @@ function createGovernedActionHost(options) {
27
35
  const actionInvocationId = platform.createFabricId("act");
28
36
  const correlationId = input.correlationId ?? platform.createFabricId("corr");
29
37
  const durableParameters = options.redactActionParameters ? options.redactActionParameters(input.actionId, input.parameters) : input.parameters;
38
+ const runtimeEvidence = {
39
+ governanceContractVersion: platform.FABRIC_GOVERNANCE_CONTRACT_VERSION,
40
+ hostContractVersion: PLATFORM_HOST_CONTRACT_VERSION,
41
+ ...options.runtimeEvidence
42
+ };
43
+ platform.assertGovernanceRuntimeEvidence(runtimeEvidence);
30
44
  const durableInvocation = await options.store.createActionInvocation({
31
45
  id: actionInvocationId,
32
46
  tenantId: input.tenantId,
@@ -38,9 +52,11 @@ function createGovernedActionHost(options) {
38
52
  status: "pending",
39
53
  parameters: durableParameters,
40
54
  result: {},
55
+ runtimeEvidence,
41
56
  correlationId,
42
57
  ...input.causationId ? { causationId: input.causationId } : {},
43
- ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
58
+ ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
59
+ ...input.authorizationBindingId ? { authorizationBindingId: input.authorizationBindingId } : {}
44
60
  });
45
61
  const durableWorkflowId = `action-invocation-${durableInvocation.id}`;
46
62
  if (durableInvocation.id !== actionInvocationId && isTerminal(durableInvocation.status)) {
@@ -88,7 +104,7 @@ function createGovernedActionHost(options) {
88
104
  );
89
105
  return { ...executed, workflowId: durableWorkflowId };
90
106
  }
91
- async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}) {
107
+ async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}, executionReasonOverride) {
92
108
  const loadedInvocation = await options.store.getActionInvocation(
93
109
  actionInvocationId,
94
110
  tenantId,
@@ -97,6 +113,7 @@ function createGovernedActionHost(options) {
97
113
  if (!loadedInvocation) {
98
114
  throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
99
115
  }
116
+ const resumingRunningInvocation = loadedInvocation.status === "running";
100
117
  let invocation = loadedInvocation;
101
118
  if (isTerminal(invocation.status) || invocation.status === "waiting_for_approval") {
102
119
  return actionResult(invocation);
@@ -198,6 +215,28 @@ function createGovernedActionHost(options) {
198
215
  }
199
216
  }
200
217
  const authorizationInput = toAuthorizationInput(action, invocation);
218
+ const executionReason = executionReasonOverride ?? (invocation.attemptCount > 1 || resumingRunningInvocation && invocation.attemptCount === 0 ? "recovery" : "initial");
219
+ if (!await options.authorization.checkEntitlement(authorizationInput)) {
220
+ return fail(
221
+ invocation,
222
+ "failed",
223
+ `Module "${action.namespace}" is no longer enabled for tenant ${tenantId}`
224
+ );
225
+ }
226
+ const executionAuthorized = options.authorization.authorizeExecution ? await options.authorization.authorizeExecution({
227
+ ...authorizationInput,
228
+ actionInvocationId,
229
+ parameters: parsed.data,
230
+ invocation,
231
+ executionReason
232
+ }) : await options.authorization.authorize(authorizationInput);
233
+ if (!executionAuthorized) {
234
+ return fail(
235
+ invocation,
236
+ "failed",
237
+ `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`
238
+ );
239
+ }
201
240
  const definitions = options.resolvePolicies ? await options.resolvePolicies({
202
241
  ...authorizationInput,
203
242
  declaredPolicyIds: action.policies ?? []
@@ -282,8 +321,8 @@ function createGovernedActionHost(options) {
282
321
  let data;
283
322
  let domainEvents = [];
284
323
  try {
285
- const handlerResult = await options.store.transaction(
286
- (db) => action.handler ? action.handler(
324
+ const runHandler = async (db, transaction) => {
325
+ const handlerResult = action.handler ? await action.handler(
287
326
  {
288
327
  actionInvocationId,
289
328
  tenantId,
@@ -296,28 +335,39 @@ function createGovernedActionHost(options) {
296
335
  services: options.services
297
336
  },
298
337
  parsed.data
299
- ) : Promise.resolve({ success: true, data: {} })
300
- );
301
- if (!handlerResult.success) {
302
- return fail(invocation, "failed", handlerResult.error ?? "Action handler failed");
303
- }
304
- data = handlerResult.data ?? {};
305
- domainEvents = extractEvents(data);
306
- const undeclaredEvent = domainEvents.find(
307
- (event) => !action.emitsEvents.includes(event.eventType)
308
- );
309
- if (undeclaredEvent) {
310
- return fail(
311
- invocation,
312
- "failed",
313
- `${action.actionId} emitted undeclared event type ${undeclaredEvent.eventType}`
338
+ ) : { success: true, data: {} };
339
+ if (!handlerResult.success) {
340
+ throw new Error(handlerResult.error ?? "Action handler failed");
341
+ }
342
+ const handlerData = handlerResult.data ?? {};
343
+ const events = extractEvents(handlerData);
344
+ const undeclaredEvent = events.find(
345
+ (event) => !action.emitsEvents.includes(event.eventType)
314
346
  );
315
- }
316
- if (action.eventPhase !== "after_adapters") {
317
- for (const [index, event] of domainEvents.entries()) {
318
- await appendEvent(invocation, event, `domain:${index}`, action.version);
347
+ if (undeclaredEvent) {
348
+ throw new Error(
349
+ `${action.actionId} emitted undeclared event type ${undeclaredEvent.eventType}`
350
+ );
319
351
  }
320
- }
352
+ if (action.eventPhase !== "after_adapters") {
353
+ for (const [index, event] of events.entries()) {
354
+ await appendEvent(
355
+ invocation,
356
+ event,
357
+ `domain:${index}`,
358
+ action.version,
359
+ transaction
360
+ );
361
+ }
362
+ }
363
+ return { data: handlerData, domainEvents: events };
364
+ };
365
+ const atomicStore = asAtomicMutationStore(options.store);
366
+ const executed = atomicStore ? await atomicStore.transactionWithEvents(
367
+ (transaction) => runHandler(transaction.db, transaction)
368
+ ) : await options.store.transaction((db) => runHandler(db));
369
+ data = executed.data;
370
+ domainEvents = executed.domainEvents;
321
371
  } catch (error) {
322
372
  return fail(invocation, "failed", errorMessage(error));
323
373
  }
@@ -448,11 +498,6 @@ function createGovernedActionHost(options) {
448
498
  return fail(invocation, "failed", message);
449
499
  }
450
500
  }
451
- if (action.eventPhase === "after_adapters") {
452
- for (const [index, event] of domainEvents.entries()) {
453
- await appendEvent(invocation, event, `domain:${index}`, action.version);
454
- }
455
- }
456
501
  const governanceStore = asGovernanceStore(options.store);
457
502
  if (governanceStore && options.resolvePolicyObligations) {
458
503
  const obligations = await governanceStore.listPolicyObligations(actionInvocationId, tenantId, spaceId);
@@ -462,10 +507,43 @@ function createGovernedActionHost(options) {
462
507
  }
463
508
  }
464
509
  const result = withoutPrivateHostFields(data, eventResultFields);
465
- await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
466
- status: "completed",
467
- result
468
- });
510
+ try {
511
+ const atomicStore = asAtomicMutationStore(options.store);
512
+ if (action.eventPhase === "after_adapters" && atomicStore) {
513
+ await atomicStore.transactionWithEvents(async (transaction) => {
514
+ for (const [index, event] of domainEvents.entries()) {
515
+ await appendEvent(
516
+ invocation,
517
+ event,
518
+ `domain:${index}`,
519
+ action.version,
520
+ transaction
521
+ );
522
+ }
523
+ await transaction.updateActionInvocation(
524
+ actionInvocationId,
525
+ tenantId,
526
+ spaceId,
527
+ { status: "completed", result }
528
+ );
529
+ });
530
+ } else {
531
+ if (action.eventPhase === "after_adapters") {
532
+ for (const [index, event] of domainEvents.entries()) {
533
+ await appendEvent(invocation, event, `domain:${index}`, action.version);
534
+ }
535
+ }
536
+ await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
537
+ status: "completed",
538
+ result
539
+ });
540
+ }
541
+ } catch (error) {
542
+ if (action.eventPhase === "after_adapters") {
543
+ throw new RecoverableFinalizationError(errorMessage(error));
544
+ }
545
+ throw error;
546
+ }
469
547
  return {
470
548
  actionInvocationId,
471
549
  status: "completed",
@@ -474,6 +552,7 @@ function createGovernedActionHost(options) {
474
552
  ...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {}
475
553
  };
476
554
  } catch (error) {
555
+ if (error instanceof RecoverableFinalizationError) throw error;
477
556
  return fail(invocation, "failed", errorMessage(error));
478
557
  }
479
558
  }
@@ -538,7 +617,13 @@ function createGovernedActionHost(options) {
538
617
  return actionResult(transitioned);
539
618
  }
540
619
  if (!decision.approved) return actionResult(transitioned);
541
- return executeInvocation(actionInvocationId, tenantId, spaceId, { leaseOwner });
620
+ return executeInvocation(
621
+ actionInvocationId,
622
+ tenantId,
623
+ spaceId,
624
+ { leaseOwner },
625
+ "approval_resume"
626
+ );
542
627
  }
543
628
  async function recordExecutionAttestation(actionInvocationId, tenantId, spaceId, attestation, adapterInvocationId) {
544
629
  const governanceStore = asGovernanceStore(options.store);
@@ -572,7 +657,7 @@ function createGovernedActionHost(options) {
572
657
  spaceId
573
658
  });
574
659
  }
575
- async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1) {
660
+ async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1, transaction) {
576
661
  const timestamp = now();
577
662
  const envelope = {
578
663
  id: lifecycleId("evt", invocation.id, deduplicationKey),
@@ -586,7 +671,7 @@ function createGovernedActionHost(options) {
586
671
  actorType: invocation.actorType,
587
672
  actionInvocationId: invocation.id,
588
673
  payload: event.payload,
589
- sequence: await options.store.nextEventSequence(
674
+ sequence: await (transaction ?? options.store).nextEventSequence(
590
675
  invocation.tenantId,
591
676
  invocation.spaceId
592
677
  ),
@@ -595,7 +680,7 @@ function createGovernedActionHost(options) {
595
680
  correlationId: invocation.correlationId,
596
681
  ...invocation.causationId ? { causationId: invocation.causationId } : {}
597
682
  };
598
- await options.store.appendEvent(envelope);
683
+ await (transaction ?? options.store).appendEvent(envelope);
599
684
  }
600
685
  async function fail(invocation, status, error) {
601
686
  await options.store.updateActionInvocation(
@@ -628,6 +713,10 @@ function asApprovalStore(store) {
628
713
  const candidate = store;
629
714
  return typeof candidate.recordHitlDecision === "function" && typeof candidate.beginApprovalDecision === "function" ? store : void 0;
630
715
  }
716
+ function asAtomicMutationStore(store) {
717
+ const candidate = store;
718
+ return typeof candidate.transactionWithEvents === "function" ? store : void 0;
719
+ }
631
720
  function asGovernanceStore(store) {
632
721
  const candidate = store;
633
722
  return typeof candidate.recordMutationGovernance === "function" && typeof candidate.appendPolicyObligations === "function" && typeof candidate.appendExecutionAttestation === "function" ? store : void 0;
@@ -854,13 +943,26 @@ var MemoryPlatformHostStore = class {
854
943
  };
855
944
 
856
945
  // src/postgres-store.ts
857
- var PostgresPlatformHostStore = class {
858
- constructor(db, sql) {
946
+ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
947
+ constructor(db, sql, transactionProvider) {
859
948
  this.db = db;
860
949
  this.sql = sql;
950
+ if (transactionProvider) {
951
+ this.transactionWithEvents = async (run) => transactionProvider.run(async ({ db: db2, sql: sql2 }) => {
952
+ const scoped = new _PostgresPlatformHostStore(db2, sql2);
953
+ return run({
954
+ db: db2,
955
+ appendEvent: (event) => scoped.appendEvent(event),
956
+ nextEventSequence: (tenantId, spaceId) => scoped.nextEventSequence(tenantId, spaceId),
957
+ listEvents: (tenantId, spaceId) => scoped.listEvents(tenantId, spaceId),
958
+ updateActionInvocation: (id, tenantId, spaceId, patch) => scoped.updateActionInvocation(id, tenantId, spaceId, patch)
959
+ });
960
+ });
961
+ }
861
962
  }
862
963
  db;
863
964
  sql;
965
+ transactionWithEvents;
864
966
  async ensureSchema() {
865
967
  await this.sql.query(`
866
968
  CREATE SCHEMA IF NOT EXISTS fabric_platform;
@@ -869,15 +971,17 @@ var PostgresPlatformHostStore = class {
869
971
  action_id text NOT NULL, action_version integer NOT NULL,
870
972
  actor_id text NOT NULL, actor_type text NOT NULL, status text NOT NULL,
871
973
  parameters jsonb NOT NULL, result jsonb NOT NULL DEFAULT '{}'::jsonb,
872
- correlation_id text NOT NULL, causation_id text, idempotency_key text, error text,
974
+ correlation_id text NOT NULL, causation_id text, idempotency_key text,
975
+ authorization_binding_id text, error text,
873
976
  attempt_count integer NOT NULL DEFAULT 0, lease_owner text,
874
977
  lease_expires_at timestamptz, hitl_route text, hitl_risk_tier text,
875
978
  hitl_reason text, hitl_policy_version text, approval_decision jsonb,
876
- mutation_footprint jsonb, execution_principal jsonb,
979
+ mutation_footprint jsonb, execution_principal jsonb, runtime_evidence jsonb,
877
980
  created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL
878
981
  );
879
982
  ALTER TABLE fabric_platform.action_invocations
880
983
  ADD COLUMN IF NOT EXISTS idempotency_key text,
984
+ ADD COLUMN IF NOT EXISTS authorization_binding_id text,
881
985
  ADD COLUMN IF NOT EXISTS attempt_count integer NOT NULL DEFAULT 0,
882
986
  ADD COLUMN IF NOT EXISTS lease_owner text,
883
987
  ADD COLUMN IF NOT EXISTS lease_expires_at timestamptz,
@@ -887,7 +991,8 @@ var PostgresPlatformHostStore = class {
887
991
  ADD COLUMN IF NOT EXISTS hitl_policy_version text,
888
992
  ADD COLUMN IF NOT EXISTS approval_decision jsonb,
889
993
  ADD COLUMN IF NOT EXISTS mutation_footprint jsonb,
890
- ADD COLUMN IF NOT EXISTS execution_principal jsonb;
994
+ ADD COLUMN IF NOT EXISTS execution_principal jsonb,
995
+ ADD COLUMN IF NOT EXISTS runtime_evidence jsonb;
891
996
  CREATE UNIQUE INDEX IF NOT EXISTS action_invocations_idempotency_idx
892
997
  ON fabric_platform.action_invocations
893
998
  (tenant_id, space_id, action_id, idempotency_key)
@@ -951,8 +1056,9 @@ var PostgresPlatformHostStore = class {
951
1056
  const result = await this.sql.query(
952
1057
  `INSERT INTO fabric_platform.action_invocations
953
1058
  (id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
954
- parameters,result,correlation_id,causation_id,idempotency_key,error,created_at,updated_at)
955
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$15)
1059
+ parameters,result,correlation_id,causation_id,idempotency_key,
1060
+ authorization_binding_id,error,runtime_evidence,created_at,updated_at)
1061
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16::jsonb,$17,$17)
956
1062
  ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
957
1063
  WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
958
1064
  RETURNING *`,
@@ -970,7 +1076,9 @@ var PostgresPlatformHostStore = class {
970
1076
  input.correlationId,
971
1077
  input.causationId ?? null,
972
1078
  input.idempotencyKey ?? null,
1079
+ input.authorizationBindingId ?? null,
973
1080
  input.error ?? null,
1081
+ input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
974
1082
  now
975
1083
  ]
976
1084
  );
@@ -1332,6 +1440,7 @@ function toActionRecord(row) {
1332
1440
  correlationId: String(row.correlation_id),
1333
1441
  ...row.causation_id ? { causationId: String(row.causation_id) } : {},
1334
1442
  ...row.idempotency_key ? { idempotencyKey: String(row.idempotency_key) } : {},
1443
+ ...row.authorization_binding_id ? { authorizationBindingId: String(row.authorization_binding_id) } : {},
1335
1444
  attemptCount: Number(row.attempt_count ?? 0),
1336
1445
  ...row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {},
1337
1446
  ...row.lease_expires_at ? { leaseExpiresAt: new Date(row.lease_expires_at) } : {},
@@ -1342,6 +1451,7 @@ function toActionRecord(row) {
1342
1451
  ...row.approval_decision ? { approvalDecision: toApprovalDecision(row.approval_decision) } : {},
1343
1452
  ...row.mutation_footprint ? { mutationFootprint: row.mutation_footprint } : {},
1344
1453
  ...row.execution_principal ? { executionPrincipal: row.execution_principal } : {},
1454
+ ...row.runtime_evidence ? { runtimeEvidence: row.runtime_evidence } : {},
1345
1455
  ...row.error ? { error: String(row.error) } : {},
1346
1456
  createdAt: new Date(row.created_at),
1347
1457
  updatedAt: new Date(row.updated_at)
@@ -1476,6 +1586,7 @@ async function abortableDelay(milliseconds, signal) {
1476
1586
  }
1477
1587
 
1478
1588
  exports.MemoryPlatformHostStore = MemoryPlatformHostStore;
1589
+ exports.PLATFORM_HOST_CONTRACT_VERSION = PLATFORM_HOST_CONTRACT_VERSION;
1479
1590
  exports.PostgresPlatformHostStore = PostgresPlatformHostStore;
1480
1591
  exports.createGovernedActionHost = createGovernedActionHost;
1481
1592
  exports.createStoreBackedActionDispatcher = createStoreBackedActionDispatcher;