@fabricorg/platform-host 2.0.0 → 2.0.1

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,9 +1,28 @@
1
1
  # @fabricorg/platform-host
2
2
 
3
+ ## 2.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Close final uplift audit gaps: align Host 2.x documentation and migration evidence, surface every
8
+ compiler/generator API to LLM consumers, make memory outbox egress directly usable, sanitize relay
9
+ failures, validate complete archetype state maps, support recursive JSON Schema generation, reject
10
+ cross-platform path traversal, synchronize artifact version stamps, and include package licenses.
11
+ - Require explicit domain snapshot/restore support before memory stores advertise atomic outbox
12
+ transactions, buffer Host lifecycle commits until success, fail missing transaction capabilities
13
+ before handlers run, sanitize relay failures, and exclude sensitive Host lifecycle events from bus
14
+ egress by default.
15
+ - Prevalidate buffered memory transactions so failed commits cannot append partial event or outbox rows.
16
+ - Qualify result-validation rollback documentation by the configured domain transaction provider.
17
+ - Updated dependencies
18
+ - @fabricorg/platform@0.11.1
19
+
3
20
  ## 2.0.0
4
21
 
5
- ### Minor Changes
22
+ ### Major Changes
6
23
 
24
+ - Require `@fabricorg/platform@^0.11.0` and its manifest v2/result-schema contract. Applications
25
+ upgrading from Host 1.x must upgrade Platform in the same deployment and regenerate manifest consumers.
7
26
  - Add the optional durable event outbox, PostgreSQL and memory adapters, and at-least-once relay.
8
27
  - Add manifest v2 typed entity contracts, v1 reader normalization, optional action result schemas, and
9
28
  Host enforcement of result validation before events and adapters.
@@ -14,13 +33,6 @@
14
33
  - Updated dependencies
15
34
  - @fabricorg/platform@0.11.0
16
35
 
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
36
  ## 1.0.0
25
37
 
26
38
  ### Minor Changes
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.10.0 @fabricorg/platform-host@^1.0.0
6
+ pnpm add @fabricorg/platform@^0.11.0 @fabricorg/platform-host@^2.0.0
7
7
  ```
8
8
 
9
9
  ## AI agent integration boundary
@@ -15,7 +15,7 @@ registration, and submit stable idempotent commands. The gateway derives tenant
15
15
  calls `submitAction()`; an agent must never call handlers, adapters, workflow internals, or database
16
16
  writes directly.
17
17
 
18
- See the [Platform Host 1.0 agent integration
18
+ See the [Platform Host 2.x agent integration
19
19
  guide](https://platform.fabric.pro/docs/platform/reference/platform-host) for application wiring,
20
20
  execution-time authorization, the PostgreSQL transaction binder, recovery semantics, and the external
21
21
  gateway contract.
@@ -101,10 +101,11 @@ Stores without this additive capability retain the legacy boundary for compatibi
101
101
  claim atomic domain-write/event persistence.
102
102
 
103
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`.
104
+ the handler and before canonical event append or adapter execution. Invalid results fail the invocation,
105
+ emit no domain/completion event, invoke no adapter, and persist only a sanitized validation failure.
106
+ Domain writes roll back only when the configured store transaction provider includes those writes.
107
+ This validates the handler result contract; it does not observe arbitrary writes through an
108
+ application-owned `TDb`.
108
109
 
109
110
  ## Agent HITL
110
111
 
@@ -177,7 +178,7 @@ explain which contract and provider adapter governed a mutation after dependenci
177
178
  const host = createGovernedActionHost({
178
179
  // ...
179
180
  runtimeEvidence: {
180
- hostPackageVersion: "1.0.0",
181
+ hostPackageVersion: "2.0.1",
181
182
  policyRulesetVersion: "gtm-rules.v8",
182
183
  providerBridge: { name: "@fabric-harness/databricks", version: "1" },
183
184
  },
@@ -196,3 +197,11 @@ the proven event transaction. `runOutboxRelayCycle()` leases and publishes recor
196
197
  and dead-letter handling. Delivery is at least once: consumers deduplicate on immutable
197
198
  `eventId`. Payload classification is metadata, not a redaction mechanism; event payloads must
198
199
  already be audit-safe.
200
+
201
+ Adapter and compliance lifecycle events remain canonical but are excluded from bus egress by default.
202
+ Use `shouldPublish` to opt them in only when the application has made their payloads safe for that bus.
203
+
204
+ For deterministic local tests, `MemoryPlatformHostStore` enables governed Host outbox egress only
205
+ when its constructor receives a `MemoryPlatformHostTransactionProvider` with domain `snapshot` and
206
+ `restore` functions. Without that explicit rollback seam, `transactionalOutbox` is false and Host
207
+ construction rejects outbox configuration. PostgreSQL remains the production implementation.
package/dist/index.cjs CHANGED
@@ -328,6 +328,9 @@ function createGovernedActionHost(options) {
328
328
  let domainEvents = [];
329
329
  try {
330
330
  const runHandler = async (db, transaction) => {
331
+ if (options.outbox && !transaction?.appendEventWithOutbox) {
332
+ throw new Error("Outbox egress requires transactionWithEvents to provide appendEventWithOutbox before handler execution.");
333
+ }
331
334
  const handlerResult = action.handler ? await action.handler(
332
335
  {
333
336
  actionInvocationId,
@@ -694,6 +697,12 @@ function createGovernedActionHost(options) {
694
697
  ...invocation.causationId ? { causationId: invocation.causationId } : {}
695
698
  };
696
699
  if (options.outbox) {
700
+ const hostLifecycleEvent = (/* @__PURE__ */ new Set(["AdapterInvocationStarted", "AdapterInvocationSucceeded", "AdapterInvocationFailed", "ComplianceBlocked"])).has(envelope.eventType);
701
+ const shouldPublish = options.outbox.shouldPublish?.(envelope) ?? !hostLifecycleEvent;
702
+ if (!shouldPublish) {
703
+ await (transaction ?? options.store).appendEvent(envelope);
704
+ return;
705
+ }
697
706
  const traceContext = options.outbox.traceContext?.(envelope);
698
707
  const metadata = {
699
708
  producerModuleVersion: options.outbox.producerModuleVersion(invocation.actionId, invocation.actionVersion),
@@ -849,13 +858,13 @@ async function runOutboxRelayCycle(options) {
849
858
  continue;
850
859
  }
851
860
  result.published += 1;
852
- } catch (error) {
861
+ } catch {
853
862
  const deadLetter = record.attemptCount >= maxAttempts;
854
863
  try {
855
864
  await options.store.markOutboxFailed({
856
865
  id: record.id,
857
866
  workerId: options.workerId,
858
- error: error instanceof Error ? error.message : String(error),
867
+ error: "Event publisher failed",
859
868
  availableAt: new Date(now().getTime() + (options.retryDelayMs?.(record.attemptCount) ?? 1e3)),
860
869
  deadLetter
861
870
  });
@@ -876,10 +885,14 @@ function cloneOutboxRecord(record) {
876
885
 
877
886
  // src/memory-store.ts
878
887
  var MemoryPlatformHostStore = class {
879
- constructor(db) {
888
+ constructor(db, transactionProvider) {
880
889
  this.db = db;
890
+ this.transactionalOutbox = transactionProvider !== void 0;
891
+ if (transactionProvider) this.transactionWithEvents = (run) => this.runTransactionWithEvents(run, transactionProvider);
881
892
  }
882
893
  db;
894
+ transactionalOutbox;
895
+ transactionTail = Promise.resolve();
883
896
  invocations = [];
884
897
  policyEvaluations = [];
885
898
  adapterInvocations = [];
@@ -891,6 +904,50 @@ var MemoryPlatformHostStore = class {
891
904
  async transaction(run) {
892
905
  return run(this.db);
893
906
  }
907
+ async runTransactionWithEvents(run, transactionProvider) {
908
+ let release;
909
+ const previous = this.transactionTail;
910
+ this.transactionTail = new Promise((resolve) => {
911
+ release = resolve;
912
+ });
913
+ await previous;
914
+ let domainSnapshot;
915
+ let snapshotCreated = false;
916
+ try {
917
+ domainSnapshot = transactionProvider.snapshot(this.db);
918
+ snapshotCreated = true;
919
+ const pendingEvents = [];
920
+ const pendingUpdates = [];
921
+ const appendPending = async (event, metadata) => {
922
+ if (this.events.some((candidate) => candidate.id === event.id) || pendingEvents.some((candidate) => candidate.event.id === event.id)) return;
923
+ pendingEvents.push({ event, ...metadata ? { metadata } : {} });
924
+ };
925
+ const result = await run({
926
+ db: this.db,
927
+ appendEvent: (event) => appendPending(event),
928
+ appendEventWithOutbox: (event, metadata) => appendPending(event, metadata),
929
+ nextEventSequence: async (tenantId, spaceId) => (await this.listEvents(tenantId, spaceId)).length + pendingEvents.filter((candidate) => candidate.event.tenantId === tenantId && candidate.event.spaceId === spaceId).length + 1,
930
+ listEvents: async (tenantId, spaceId) => [...await this.listEvents(tenantId, spaceId), ...pendingEvents.map((candidate) => candidate.event).filter((event) => event.tenantId === tenantId && event.spaceId === spaceId)],
931
+ updateActionInvocation: async (id, tenantId, spaceId, patch) => {
932
+ pendingUpdates.push({ id, tenantId, spaceId, patch });
933
+ }
934
+ });
935
+ for (const update of pendingUpdates) {
936
+ if (!await this.getActionInvocation(update.id, update.tenantId, update.spaceId)) throw new Error(`ActionInvocation not found: ${update.id}`);
937
+ }
938
+ for (const pending of pendingEvents) {
939
+ if (pending.metadata) await this.appendEventWithOutbox(pending.event, pending.metadata);
940
+ else await this.appendEvent(pending.event);
941
+ }
942
+ for (const update of pendingUpdates) await this.updateActionInvocation(update.id, update.tenantId, update.spaceId, update.patch);
943
+ return result;
944
+ } catch (error) {
945
+ if (snapshotCreated) transactionProvider.restore(this.db, domainSnapshot);
946
+ throw error;
947
+ } finally {
948
+ release();
949
+ }
950
+ }
894
951
  async createActionInvocation(input) {
895
952
  if (input.idempotencyKey) {
896
953
  const existing = this.invocations.find(