@fabricorg/platform-host 0.7.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # @fabricorg/platform-host
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add the optional durable event outbox, PostgreSQL and memory adapters, and at-least-once relay.
8
+ - Add manifest v2 typed entity contracts, v1 reader normalization, optional action result schemas, and
9
+ Host enforcement of result validation before events and adapters.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+ - Updated dependencies
15
+ - @fabricorg/platform@0.11.0
16
+
17
+ ## Unreleased
18
+
19
+ - Validate optional action result schemas immediately after handler execution and before events or
20
+ adapters. Invalid results roll back the handler transaction and persist a sanitized failure.
21
+ - Add optional memory and PostgreSQL outbox capabilities with atomic canonical-event records,
22
+ lease-based at-least-once relay, retry, replay, and dead-letter state.
23
+
24
+ ## 1.0.0
25
+
26
+ ### Minor Changes
27
+
28
+ - fdd3e69: Add provider-neutral external mutation footprints, delegated principals, enforceable obligations,
29
+ execution attestations, and reconciliation evidence with durable host-store support.
30
+ - 714ad06: Add enforceable Fabric-family compatibility controls and durable runtime contract-version evidence.
31
+ - 82aa9c8: Add parameter-aware execution-time authorization, opaque durable admission bindings, and an
32
+ application-bound PostgreSQL unit of work for atomic domain/event persistence and recoverable
33
+ after-adapter finalization.
34
+
35
+ ### Patch Changes
36
+
37
+ - Updated dependencies [bc7d3ab]
38
+ - Updated dependencies [fdd3e69]
39
+ - Updated dependencies [714ad06]
40
+ - Updated dependencies [43c5770]
41
+ - Updated dependencies [986636b]
42
+ - @fabricorg/platform@0.10.0
43
+
3
44
  ## 0.7.0 — 2026-07-27
4
45
 
5
46
  - Add parameter-aware execution authorization immediately before policies and mutation execution,
package/README.md CHANGED
@@ -3,9 +3,23 @@
3
3
  The canonical host for the `@fabricorg/platform` mutation pipeline.
4
4
 
5
5
  ```bash
6
- pnpm add @fabricorg/platform@^0.9.0 @fabricorg/platform-host@^0.7.0
6
+ pnpm add @fabricorg/platform@^0.10.0 @fabricorg/platform-host@^1.0.0
7
7
  ```
8
8
 
9
+ ## AI agent integration boundary
10
+
11
+ Application and vertical owners install this package. Hermes, Fabric Harness, and other external agent
12
+ runtimes do not import Platform Host merely to call a deployed application. They connect to the
13
+ application's authenticated REST or MCP gateway, discover only the actions allowed by their
14
+ registration, and submit stable idempotent commands. The gateway derives tenant and actor identity and
15
+ calls `submitAction()`; an agent must never call handlers, adapters, workflow internals, or database
16
+ writes directly.
17
+
18
+ See the [Platform Host 1.0 agent integration
19
+ guide](https://platform.fabric.pro/docs/platform/reference/platform-host) for application wiring,
20
+ execution-time authorization, the PostgreSQL transaction binder, recovery semantics, and the external
21
+ gateway contract.
22
+
9
23
  Vertical packages register `FabricModule` definitions. Host applications provide tenant authorization,
10
24
  module entitlements, persistence, projections, and an optional durable dispatcher. The package owns the
11
25
  ordering and lifecycle invariant:
@@ -64,6 +78,11 @@ When `authorizeExecution` is absent, the Host reuses `authorize` at the executio
64
78
  completed idempotent replay returns its existing result without creating or executing a new
65
79
  mutation.
66
80
 
81
+ External gateways should persist a compact admission record bound to the tenant, registration, actor,
82
+ action, canonical parameter hash, resource scope, and expiry, then pass its opaque non-secret identifier
83
+ as `authorizationBindingId` to `submitAction()`. Credentials and signed principal proofs do not belong
84
+ in action parameters or workflow history.
85
+
67
86
  ## Atomic mutation unit of work
68
87
 
69
88
  Production stores can implement `AtomicMutationPlatformHostStore.transactionWithEvents`. The
@@ -81,6 +100,12 @@ application knows how its `TDb` is rebound to the transaction's SQL client.
81
100
  Stores without this additive capability retain the legacy boundary for compatibility and must not
82
101
  claim atomic domain-write/event persistence.
83
102
 
103
+ Actions can optionally declare `resultSchema`. The Host validates public handler result data after
104
+ the handler and before canonical event append or adapter execution. Invalid results roll back the
105
+ handler transaction, emit no domain/completion event, invoke no adapter, and persist only a sanitized
106
+ validation failure. This validates the handler result contract; it does not observe arbitrary writes
107
+ through an application-owned `TDb`.
108
+
84
109
  ## Agent HITL
85
110
 
86
111
  Hosts can inject a vertical-owned `hitlEvaluator`. It runs only for `actorType: "agent"`, after schema
@@ -152,7 +177,7 @@ explain which contract and provider adapter governed a mutation after dependenci
152
177
  const host = createGovernedActionHost({
153
178
  // ...
154
179
  runtimeEvidence: {
155
- hostPackageVersion: "0.7.0",
180
+ hostPackageVersion: "1.0.0",
156
181
  policyRulesetVersion: "gtm-rules.v8",
157
182
  providerBridge: { name: "@fabric-harness/databricks", version: "1" },
158
183
  },
@@ -162,3 +187,12 @@ const host = createGovernedActionHost({
162
187
  `MemoryPlatformHostStore` is for tests and local demos. Production control planes use
163
188
  `PostgresPlatformHostStore` with Databricks Lakebase (or standard Postgres), call
164
189
  `ensureSchema()` at startup, and hydrate projections from `listEvents()`.
190
+
191
+ ## Durable outbox egress
192
+
193
+ Configure `outbox` on `createGovernedActionHost()` only with an `OutboxPlatformHostStore`.
194
+ The Host then uses `appendEventWithOutbox` so the canonical event and delivery record share
195
+ the proven event transaction. `runOutboxRelayCycle()` leases and publishes records with retry
196
+ and dead-letter handling. Delivery is at least once: consumers deduplicate on immutable
197
+ `eventId`. Payload classification is metadata, not a redaction mechanism; event payloads must
198
+ already be audit-safe.
package/dist/index.cjs CHANGED
@@ -16,6 +16,12 @@ var RecoverableFinalizationError = class extends Error {
16
16
  name = "RecoverableFinalizationError";
17
17
  };
18
18
  function createGovernedActionHost(options) {
19
+ if (options.outbox) {
20
+ const outboxStore = asOutboxStore(options.store);
21
+ if (!outboxStore || !asAtomicMutationStore(options.store) || outboxStore.transactionalOutbox !== true) {
22
+ throw new Error("Outbox egress requires a store with transactionalOutbox and transactionWithEvents capabilities.");
23
+ }
24
+ }
19
25
  const adapters = new platform.AdapterRegistry();
20
26
  for (const adapter of options.adapters ?? []) adapters.register(adapter);
21
27
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
@@ -340,6 +346,13 @@ function createGovernedActionHost(options) {
340
346
  throw new Error(handlerResult.error ?? "Action handler failed");
341
347
  }
342
348
  const handlerData = handlerResult.data ?? {};
349
+ if (action.resultSchema) {
350
+ const publicResult = withoutPrivateHostFields(handlerData, eventResultFields);
351
+ const parsedResult = action.resultSchema.safeParse(publicResult);
352
+ if (!parsedResult.success) {
353
+ throw new Error(`Action result validation failed at ${parsedResult.error.issues.map((issue) => issue.path.map(String).join(".") || "result").join(", ")}`);
354
+ }
355
+ }
343
356
  const events = extractEvents(handlerData);
344
357
  const undeclaredEvent = events.find(
345
358
  (event) => !action.emitsEvents.includes(event.eventType)
@@ -680,7 +693,24 @@ function createGovernedActionHost(options) {
680
693
  correlationId: invocation.correlationId,
681
694
  ...invocation.causationId ? { causationId: invocation.causationId } : {}
682
695
  };
683
- await (transaction ?? options.store).appendEvent(envelope);
696
+ if (options.outbox) {
697
+ const traceContext = options.outbox.traceContext?.(envelope);
698
+ const metadata = {
699
+ producerModuleVersion: options.outbox.producerModuleVersion(invocation.actionId, invocation.actionVersion),
700
+ payloadClassification: options.outbox.classifyPayload(envelope),
701
+ ...traceContext ? { traceContext } : {}
702
+ };
703
+ if (transaction) {
704
+ if (!transaction.appendEventWithOutbox) throw new Error("Outbox egress requires an atomic transaction with appendEventWithOutbox.");
705
+ await transaction.appendEventWithOutbox(envelope, metadata);
706
+ } else {
707
+ const outboxStore = asOutboxStore(options.store);
708
+ if (!outboxStore) throw new Error("Outbox egress requires an outbox-capable Platform Host store.");
709
+ await outboxStore.appendEventWithOutbox(envelope, metadata);
710
+ }
711
+ } else {
712
+ await (transaction ?? options.store).appendEvent(envelope);
713
+ }
684
714
  }
685
715
  async function fail(invocation, status, error) {
686
716
  await options.store.updateActionInvocation(
@@ -717,6 +747,10 @@ function asAtomicMutationStore(store) {
717
747
  const candidate = store;
718
748
  return typeof candidate.transactionWithEvents === "function" ? store : void 0;
719
749
  }
750
+ function asOutboxStore(store) {
751
+ const candidate = store;
752
+ return typeof candidate.appendEventWithOutbox === "function" && typeof candidate.claimOutbox === "function" ? store : void 0;
753
+ }
720
754
  function asGovernanceStore(store) {
721
755
  const candidate = store;
722
756
  return typeof candidate.recordMutationGovernance === "function" && typeof candidate.appendPolicyObligations === "function" && typeof candidate.appendExecutionAttestation === "function" ? store : void 0;
@@ -773,6 +807,73 @@ function lifecycleId(prefix, invocationId, key) {
773
807
  return `${prefix}_${invocationId}_${safeKey}`;
774
808
  }
775
809
 
810
+ // src/outbox.ts
811
+ function toEnterpriseEventEnvelope(event, metadata) {
812
+ return {
813
+ eventId: event.id,
814
+ eventType: event.eventType,
815
+ eventSchemaVersion: event.eventSchemaVersion,
816
+ tenantId: event.tenantId,
817
+ spaceId: event.spaceId,
818
+ subjectType: event.subjectType,
819
+ subjectId: event.subjectId,
820
+ sequence: event.sequence,
821
+ ...event.actionInvocationId ? { actionInvocationId: event.actionInvocationId } : {},
822
+ correlationId: event.correlationId,
823
+ ...event.causationId ? { causationId: event.causationId } : {},
824
+ occurredAt: event.occurredAt,
825
+ recordedAt: event.recordedAt,
826
+ producerModuleVersion: metadata.producerModuleVersion,
827
+ payloadClassification: metadata.payloadClassification,
828
+ ...metadata.traceContext ? { traceContext: { ...metadata.traceContext } } : {},
829
+ payload: event.payload
830
+ };
831
+ }
832
+ async function runOutboxRelayCycle(options) {
833
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
834
+ const maxAttempts = options.maxAttempts ?? 10;
835
+ const records = await options.store.claimOutbox({
836
+ workerId: options.workerId,
837
+ leaseDurationMs: options.leaseDurationMs ?? 3e4,
838
+ limit: options.batchSize ?? 100,
839
+ now: now()
840
+ });
841
+ const result = { claimed: records.length, published: 0, failed: 0, deadLettered: 0 };
842
+ for (const record of records) {
843
+ try {
844
+ await options.publisher.publish(record.event);
845
+ try {
846
+ await options.store.markOutboxPublished(record.id, options.workerId, now());
847
+ } catch {
848
+ result.failed += 1;
849
+ continue;
850
+ }
851
+ result.published += 1;
852
+ } catch (error) {
853
+ const deadLetter = record.attemptCount >= maxAttempts;
854
+ try {
855
+ await options.store.markOutboxFailed({
856
+ id: record.id,
857
+ workerId: options.workerId,
858
+ error: error instanceof Error ? error.message : String(error),
859
+ availableAt: new Date(now().getTime() + (options.retryDelayMs?.(record.attemptCount) ?? 1e3)),
860
+ deadLetter
861
+ });
862
+ } catch {
863
+ }
864
+ result.failed += 1;
865
+ if (deadLetter) result.deadLettered += 1;
866
+ }
867
+ }
868
+ return result;
869
+ }
870
+ function cloneOutboxRecord(record) {
871
+ return {
872
+ ...record,
873
+ event: { ...record.event, ...record.event.traceContext ? { traceContext: { ...record.event.traceContext } } : {} }
874
+ };
875
+ }
876
+
776
877
  // src/memory-store.ts
777
878
  var MemoryPlatformHostStore = class {
778
879
  constructor(db) {
@@ -786,6 +887,7 @@ var MemoryPlatformHostStore = class {
786
887
  policyObligations = [];
787
888
  executionAttestations = [];
788
889
  externalReconciliations = [];
890
+ outbox = [];
789
891
  async transaction(run) {
790
892
  return run(this.db);
791
893
  }
@@ -904,6 +1006,53 @@ var MemoryPlatformHostStore = class {
904
1006
  if (this.events.some((candidate) => candidate.id === event.id)) return;
905
1007
  this.events.push(event);
906
1008
  }
1009
+ async appendEventWithOutbox(event, metadata) {
1010
+ if (this.events.some((candidate) => candidate.id === event.id)) return;
1011
+ const createdAt = new Date(event.recordedAt);
1012
+ this.events.push(event);
1013
+ this.outbox.push({
1014
+ id: event.id,
1015
+ tenantId: event.tenantId,
1016
+ spaceId: event.spaceId,
1017
+ event: toEnterpriseEventEnvelope(event, metadata),
1018
+ status: "pending",
1019
+ attemptCount: 0,
1020
+ availableAt: createdAt,
1021
+ createdAt
1022
+ });
1023
+ }
1024
+ async claimOutbox(input) {
1025
+ const current = input.now ?? /* @__PURE__ */ new Date();
1026
+ return this.outbox.filter((record) => record.status === "pending" && record.availableAt <= current && (!record.leaseExpiresAt || record.leaseExpiresAt <= current) && (!input.tenantId || record.tenantId === input.tenantId) && (!input.spaceId || record.spaceId === input.spaceId)).sort((left, right) => left.createdAt.getTime() - right.createdAt.getTime() || left.id.localeCompare(right.id)).slice(0, Math.max(1, Math.min(input.limit ?? 100, 1e3))).map((record) => {
1027
+ record.leaseOwner = input.workerId;
1028
+ record.leaseExpiresAt = new Date(current.getTime() + input.leaseDurationMs);
1029
+ record.attemptCount += 1;
1030
+ return cloneOutboxRecord(record);
1031
+ });
1032
+ }
1033
+ async markOutboxPublished(id, workerId, publishedAt) {
1034
+ const record = this.requireLeasedOutbox(id, workerId);
1035
+ record.status = "published";
1036
+ record.publishedAt = publishedAt;
1037
+ delete record.leaseOwner;
1038
+ delete record.leaseExpiresAt;
1039
+ }
1040
+ async markOutboxFailed(input) {
1041
+ const record = this.requireLeasedOutbox(input.id, input.workerId);
1042
+ record.status = input.deadLetter ? "dead_letter" : "pending";
1043
+ record.lastError = input.error;
1044
+ record.availableAt = input.availableAt;
1045
+ delete record.leaseOwner;
1046
+ delete record.leaseExpiresAt;
1047
+ }
1048
+ async listOutbox(input = {}) {
1049
+ return this.outbox.filter((record) => (!input.tenantId || record.tenantId === input.tenantId) && (!input.spaceId || record.spaceId === input.spaceId) && (!input.statuses?.length || input.statuses.includes(record.status))).map(cloneOutboxRecord);
1050
+ }
1051
+ requireLeasedOutbox(id, workerId) {
1052
+ const record = this.outbox.find((candidate) => candidate.id === id);
1053
+ if (!record || record.leaseOwner !== workerId) throw new Error(`Outbox record ${id} is not leased by ${workerId}.`);
1054
+ return record;
1055
+ }
907
1056
  async nextEventSequence(tenantId, spaceId) {
908
1057
  return this.events.filter((event) => event.tenantId === tenantId && event.spaceId === spaceId).length + 1;
909
1058
  }
@@ -947,12 +1096,14 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
947
1096
  constructor(db, sql, transactionProvider) {
948
1097
  this.db = db;
949
1098
  this.sql = sql;
1099
+ this.transactionalOutbox = transactionProvider !== void 0;
950
1100
  if (transactionProvider) {
951
1101
  this.transactionWithEvents = async (run) => transactionProvider.run(async ({ db: db2, sql: sql2 }) => {
952
1102
  const scoped = new _PostgresPlatformHostStore(db2, sql2);
953
1103
  return run({
954
1104
  db: db2,
955
1105
  appendEvent: (event) => scoped.appendEvent(event),
1106
+ appendEventWithOutbox: (event, metadata) => scoped.appendEventWithOutbox(event, metadata),
956
1107
  nextEventSequence: (tenantId, spaceId) => scoped.nextEventSequence(tenantId, spaceId),
957
1108
  listEvents: (tenantId, spaceId) => scoped.listEvents(tenantId, spaceId),
958
1109
  updateActionInvocation: (id, tenantId, spaceId, patch) => scoped.updateActionInvocation(id, tenantId, spaceId, patch)
@@ -963,6 +1114,7 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
963
1114
  db;
964
1115
  sql;
965
1116
  transactionWithEvents;
1117
+ transactionalOutbox;
966
1118
  async ensureSchema() {
967
1119
  await this.sql.query(`
968
1120
  CREATE SCHEMA IF NOT EXISTS fabric_platform;
@@ -1046,6 +1198,15 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
1046
1198
  CREATE INDEX IF NOT EXISTS asset_events_subject_idx
1047
1199
  ON fabric_platform.asset_events
1048
1200
  (tenant_id, space_id, subject_type, subject_id, sequence);
1201
+ CREATE TABLE IF NOT EXISTS fabric_platform.event_outbox (
1202
+ id text PRIMARY KEY, tenant_id text NOT NULL, space_id text NOT NULL,
1203
+ event jsonb NOT NULL, status text NOT NULL DEFAULT 'pending',
1204
+ attempt_count integer NOT NULL DEFAULT 0, available_at timestamptz NOT NULL,
1205
+ lease_owner text, lease_expires_at timestamptz, last_error text,
1206
+ created_at timestamptz NOT NULL, published_at timestamptz
1207
+ );
1208
+ CREATE INDEX IF NOT EXISTS event_outbox_claim_idx
1209
+ ON fabric_platform.event_outbox (status, available_at, lease_expires_at, created_at);
1049
1210
  `);
1050
1211
  }
1051
1212
  async transaction(run) {
@@ -1328,6 +1489,86 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
1328
1489
  ]
1329
1490
  );
1330
1491
  }
1492
+ async appendEventWithOutbox(event, metadata) {
1493
+ const envelope = toEnterpriseEventEnvelope(event, metadata);
1494
+ await this.sql.query(
1495
+ `WITH inserted_event AS (
1496
+ INSERT INTO fabric_platform.asset_events
1497
+ (id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
1498
+ actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
1499
+ correlation_id,causation_id)
1500
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16)
1501
+ ON CONFLICT (id) DO NOTHING RETURNING id
1502
+ )
1503
+ INSERT INTO fabric_platform.event_outbox
1504
+ (id,tenant_id,space_id,event,status,attempt_count,available_at,created_at)
1505
+ SELECT $1,$2,$3,$17::jsonb,'pending',0,$14,$14 FROM inserted_event
1506
+ ON CONFLICT (id) DO NOTHING`,
1507
+ [
1508
+ event.id,
1509
+ event.tenantId,
1510
+ event.spaceId,
1511
+ event.eventType,
1512
+ event.eventSchemaVersion,
1513
+ event.subjectType,
1514
+ event.subjectId,
1515
+ event.actorId,
1516
+ event.actorType,
1517
+ event.actionInvocationId ?? null,
1518
+ JSON.stringify(event.payload),
1519
+ event.sequence,
1520
+ event.occurredAt,
1521
+ event.recordedAt,
1522
+ event.correlationId,
1523
+ event.causationId ?? null,
1524
+ JSON.stringify(envelope)
1525
+ ]
1526
+ );
1527
+ }
1528
+ async claimOutbox(input) {
1529
+ const current = input.now ?? /* @__PURE__ */ new Date();
1530
+ const leaseExpiresAt = new Date(current.getTime() + input.leaseDurationMs);
1531
+ const result = await this.sql.query(
1532
+ `WITH claimable AS (
1533
+ SELECT id FROM fabric_platform.event_outbox
1534
+ WHERE status='pending' AND available_at <= $1
1535
+ AND (lease_expires_at IS NULL OR lease_expires_at <= $1)
1536
+ AND ($2::text IS NULL OR tenant_id=$2)
1537
+ AND ($3::text IS NULL OR space_id=$3)
1538
+ ORDER BY created_at,id FOR UPDATE SKIP LOCKED LIMIT $4
1539
+ )
1540
+ UPDATE fabric_platform.event_outbox AS item
1541
+ SET lease_owner=$5, lease_expires_at=$6, attempt_count=attempt_count+1
1542
+ FROM claimable WHERE item.id=claimable.id RETURNING item.*`,
1543
+ [current, input.tenantId ?? null, input.spaceId ?? null, Math.max(1, Math.min(input.limit ?? 100, 1e3)), input.workerId, leaseExpiresAt]
1544
+ );
1545
+ return result.rows.map(toOutboxRecord);
1546
+ }
1547
+ async markOutboxPublished(id, workerId, publishedAt) {
1548
+ const result = await this.sql.query(
1549
+ `UPDATE fabric_platform.event_outbox SET status='published',published_at=$3,
1550
+ lease_owner=NULL,lease_expires_at=NULL WHERE id=$1 AND lease_owner=$2 RETURNING id`,
1551
+ [id, workerId, publishedAt]
1552
+ );
1553
+ if (result.rows.length === 0) throw new Error(`Outbox record ${id} is not leased by ${workerId}.`);
1554
+ }
1555
+ async markOutboxFailed(input) {
1556
+ const result = await this.sql.query(
1557
+ `UPDATE fabric_platform.event_outbox SET status=$3,last_error=$4,available_at=$5,
1558
+ lease_owner=NULL,lease_expires_at=NULL WHERE id=$1 AND lease_owner=$2 RETURNING id`,
1559
+ [input.id, input.workerId, input.deadLetter ? "dead_letter" : "pending", input.error, input.availableAt]
1560
+ );
1561
+ if (result.rows.length === 0) throw new Error(`Outbox record ${input.id} is not leased by ${input.workerId}.`);
1562
+ }
1563
+ async listOutbox(input = {}) {
1564
+ const result = await this.sql.query(
1565
+ `SELECT * FROM fabric_platform.event_outbox
1566
+ WHERE ($1::text IS NULL OR tenant_id=$1) AND ($2::text IS NULL OR space_id=$2)
1567
+ AND ($3::text[] IS NULL OR status=ANY($3::text[])) ORDER BY created_at,id`,
1568
+ [input.tenantId ?? null, input.spaceId ?? null, input.statuses?.length ? input.statuses : null]
1569
+ );
1570
+ return result.rows.map(toOutboxRecord);
1571
+ }
1331
1572
  async nextEventSequence(tenantId, spaceId) {
1332
1573
  const result = await this.sql.query(
1333
1574
  `INSERT INTO fabric_platform.event_sequences (tenant_id,space_id,next_sequence)
@@ -1516,6 +1757,41 @@ function toEventRecord(row) {
1516
1757
  ...row.causation_id ? { causationId: String(row.causation_id) } : {}
1517
1758
  };
1518
1759
  }
1760
+ function toOutboxRecord(row) {
1761
+ const event = row.event;
1762
+ return {
1763
+ id: String(row.id),
1764
+ tenantId: String(row.tenant_id),
1765
+ spaceId: String(row.space_id),
1766
+ event: {
1767
+ eventId: String(event.eventId),
1768
+ eventType: String(event.eventType),
1769
+ eventSchemaVersion: Number(event.eventSchemaVersion),
1770
+ tenantId: String(event.tenantId),
1771
+ spaceId: String(event.spaceId),
1772
+ subjectType: String(event.subjectType),
1773
+ subjectId: String(event.subjectId),
1774
+ sequence: Number(event.sequence),
1775
+ ...event.actionInvocationId ? { actionInvocationId: String(event.actionInvocationId) } : {},
1776
+ correlationId: String(event.correlationId),
1777
+ ...event.causationId ? { causationId: String(event.causationId) } : {},
1778
+ occurredAt: new Date(event.occurredAt),
1779
+ recordedAt: new Date(event.recordedAt),
1780
+ producerModuleVersion: String(event.producerModuleVersion),
1781
+ payloadClassification: String(event.payloadClassification),
1782
+ ...event.traceContext ? { traceContext: event.traceContext } : {},
1783
+ payload: event.payload
1784
+ },
1785
+ status: String(row.status),
1786
+ attemptCount: Number(row.attempt_count),
1787
+ availableAt: new Date(row.available_at),
1788
+ ...row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {},
1789
+ ...row.lease_expires_at ? { leaseExpiresAt: new Date(row.lease_expires_at) } : {},
1790
+ ...row.last_error ? { lastError: String(row.last_error) } : {},
1791
+ createdAt: new Date(row.created_at),
1792
+ ...row.published_at ? { publishedAt: new Date(row.published_at) } : {}
1793
+ };
1794
+ }
1519
1795
 
1520
1796
  // src/worker.ts
1521
1797
  var DEFAULT_BATCH_SIZE = 10;
@@ -1588,9 +1864,12 @@ async function abortableDelay(milliseconds, signal) {
1588
1864
  exports.MemoryPlatformHostStore = MemoryPlatformHostStore;
1589
1865
  exports.PLATFORM_HOST_CONTRACT_VERSION = PLATFORM_HOST_CONTRACT_VERSION;
1590
1866
  exports.PostgresPlatformHostStore = PostgresPlatformHostStore;
1867
+ exports.cloneOutboxRecord = cloneOutboxRecord;
1591
1868
  exports.createGovernedActionHost = createGovernedActionHost;
1592
1869
  exports.createStoreBackedActionDispatcher = createStoreBackedActionDispatcher;
1870
+ exports.runOutboxRelayCycle = runOutboxRelayCycle;
1593
1871
  exports.runPlatformActionWorker = runPlatformActionWorker;
1594
1872
  exports.runPlatformActionWorkerCycle = runPlatformActionWorkerCycle;
1873
+ exports.toEnterpriseEventEnvelope = toEnterpriseEventEnvelope;
1595
1874
  //# sourceMappingURL=index.cjs.map
1596
1875
  //# sourceMappingURL=index.cjs.map