@fabricorg/platform-host 7.2.3 → 7.4.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.cjs CHANGED
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
3
  var platform = require('@fabricorg/platform');
4
+ var perf_hooks = require('perf_hooks');
4
5
  var assembly = require('@fabricorg/assembly');
5
6
  var crypto = require('crypto');
7
+ var util = require('util');
6
8
 
7
9
  // src/host.ts
8
10
 
@@ -17,6 +19,19 @@ var IdempotencyConflictError = class extends Error {
17
19
  conflict;
18
20
  code = "IDEMPOTENCY_CONFLICT";
19
21
  };
22
+ var ActionAdmissionError = class extends Error {
23
+ constructor(actionId, refusal, refusalCode) {
24
+ super(`Action ${actionId} submission was not admitted (${refusal}${refusalCode ? `: ${refusalCode}` : ""}); nothing was recorded.`);
25
+ this.actionId = actionId;
26
+ this.refusal = refusal;
27
+ this.refusalCode = refusalCode;
28
+ this.name = "ActionAdmissionError";
29
+ }
30
+ actionId;
31
+ refusal;
32
+ refusalCode;
33
+ code = "ACTION_ADMISSION_REFUSED";
34
+ };
20
35
  var ExternalCompletionError = class extends Error {
21
36
  constructor(refusal, message) {
22
37
  super(message);
@@ -30,18 +45,8 @@ var ExternalCompletionError = class extends Error {
30
45
  }
31
46
  };
32
47
  var PARAMETER_DIGEST_ALGORITHM = "fabric-canonical-json-sha256-v1";
33
- function canonicalJson(value) {
34
- return JSON.stringify(sort(value));
35
- }
36
- function sort(value) {
37
- if (Array.isArray(value)) return value.map(sort);
38
- if (value && typeof value === "object") {
39
- return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, sort(child)]));
40
- }
41
- return value;
42
- }
43
48
  function digestParameters(parameters) {
44
- return crypto.createHash("sha256").update(canonicalJson(parameters)).digest("hex");
49
+ return crypto.createHash("sha256").update(platform.canonicalJson(parameters)).digest("hex");
45
50
  }
46
51
  var ATTRIBUTE_NAME = /^[a-z][a-z0-9-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)+$/;
47
52
  var INVOCATION_SOURCE = /^(?:ui|sdui|api|agent|worker|system|[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)+)$/;
@@ -84,6 +89,7 @@ var PLATFORM_HOST_METRIC_NAMES = {
84
89
  workerCyclesCompleted: "fabric.platform.worker.cycles.completed",
85
90
  workerCyclesFailed: "fabric.platform.worker.cycles.failed",
86
91
  invocationSubmitted: "fabric.platform.invocation.submitted",
92
+ invocationAdmissionRefused: "fabric.platform.invocation.admission_refused",
87
93
  invocationExecutionStarted: "fabric.platform.invocation.execution_started",
88
94
  invocationLeaseClaimed: "fabric.platform.invocation.lease_claimed",
89
95
  invocationCompleted: "fabric.platform.invocation.completed",
@@ -99,6 +105,8 @@ var PLATFORM_HOST_METRIC_NAMES = {
99
105
  outboxPublished: "fabric.platform.outbox.published",
100
106
  outboxFailed: "fabric.platform.outbox.failed",
101
107
  outboxDeadLettered: "fabric.platform.outbox.dead_lettered",
108
+ outboxRequeued: "fabric.platform.outbox.requeued",
109
+ outboxRequeueRefused: "fabric.platform.outbox.requeue_refused",
102
110
  invocationBacklog: "fabric.platform.invocation.backlog",
103
111
  invocationRunning: "fabric.platform.invocation.running",
104
112
  invocationExpiredLeases: "fabric.platform.invocation.expired_leases",
@@ -439,7 +447,7 @@ function createExternalCompletionLifecycle(deps) {
439
447
  return withConsistency(actionResult2({ ...invocation, ...patch }), invocation.actionId);
440
448
  }
441
449
  function completionDigest(completion, provider) {
442
- return crypto.createHash("sha256").update(canonicalJson({
450
+ return crypto.createHash("sha256").update(platform.canonicalJson({
443
451
  provider: provider ?? null,
444
452
  externalReference: completion.externalReference,
445
453
  outcome: completion.outcome,
@@ -498,6 +506,340 @@ function createExternalCompletionLifecycle(deps) {
498
506
  }
499
507
  return { completeExternalInvocation, reconcileOverdueCompletions, recordCompletionReconciliation };
500
508
  }
509
+ var SubmissionUnreadableError = class extends Error {
510
+ name = "SubmissionUnreadableError";
511
+ constructor() {
512
+ super("Platform Host submission could not be read as plain data; nothing was recorded.");
513
+ }
514
+ };
515
+ var NotJsonData = class extends Error {
516
+ };
517
+ function copyJsonData(value) {
518
+ try {
519
+ return { ok: true, value: copy(value, /* @__PURE__ */ new Set()) };
520
+ } catch {
521
+ return { ok: false };
522
+ }
523
+ }
524
+ function copy(value, ancestors) {
525
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
526
+ if (typeof value === "number") {
527
+ if (!Number.isFinite(value)) throw new NotJsonData();
528
+ return value === 0 ? 0 : value;
529
+ }
530
+ if (typeof value !== "object") throw new NotJsonData();
531
+ if (util.types.isProxy(value)) throw new NotJsonData();
532
+ if (ancestors.has(value)) throw new NotJsonData();
533
+ const prototype = Reflect.getPrototypeOf(value);
534
+ ancestors.add(value);
535
+ try {
536
+ if (Array.isArray(value)) {
537
+ if (prototype !== Array.prototype) throw new NotJsonData();
538
+ const length = value.length;
539
+ const out2 = [];
540
+ for (let index = 0; index < length; index += 1) {
541
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, String(index));
542
+ if (descriptor === void 0) throw new NotJsonData();
543
+ const item = readOnce(value, descriptor);
544
+ if (item === void 0) throw new NotJsonData();
545
+ out2.push(copy(item, ancestors));
546
+ }
547
+ return out2;
548
+ }
549
+ if (prototype !== Object.prototype && prototype !== null) throw new NotJsonData();
550
+ const out = {};
551
+ for (const key of Reflect.ownKeys(value)) {
552
+ if (typeof key !== "string") continue;
553
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
554
+ if (descriptor === void 0 || descriptor.enumerable !== true) continue;
555
+ const item = readOnce(value, descriptor);
556
+ if (item === void 0) continue;
557
+ Reflect.defineProperty(out, key, { value: copy(item, ancestors), enumerable: true, writable: true, configurable: true });
558
+ }
559
+ return out;
560
+ } finally {
561
+ ancestors.delete(value);
562
+ }
563
+ }
564
+ function readOnce(target, descriptor) {
565
+ if ("value" in descriptor) return descriptor.value;
566
+ return descriptor.get ? Reflect.apply(descriptor.get, target, []) : void 0;
567
+ }
568
+ function snapshotSubmission(input) {
569
+ if (input === null || typeof input !== "object") throw new SubmissionUnreadableError();
570
+ const scalar = (key) => primitiveOrAbsent(readField(input, key));
571
+ const actionId = scalar("actionId");
572
+ const tenantId = scalar("tenantId");
573
+ const spaceId = scalar("spaceId");
574
+ const actorId = scalar("actorId");
575
+ const actorType = scalar("actorType");
576
+ const correlationId = scalar("correlationId");
577
+ const causationId = scalar("causationId");
578
+ const executionReason = scalar("executionReason");
579
+ const idempotencyKey = scalar("idempotencyKey");
580
+ const authorizationBindingId = scalar("authorizationBindingId");
581
+ const scalars = {
582
+ actionId,
583
+ tenantId,
584
+ spaceId,
585
+ actorId,
586
+ actorType,
587
+ ...correlationId != null ? { correlationId } : {},
588
+ ...causationId != null ? { causationId } : {},
589
+ ...executionReason != null ? { executionReason } : {},
590
+ ...idempotencyKey != null ? { idempotencyKey } : {},
591
+ ...authorizationBindingId != null ? { authorizationBindingId } : {}
592
+ };
593
+ const live = { ...scalars, parametersArePrivate: false };
594
+ Object.defineProperties(live, {
595
+ parameters: { get: () => input.parameters, enumerable: true },
596
+ provenance: { get: () => input.provenance, enumerable: true },
597
+ authorizationBinding: { get: () => input.authorizationBinding, enumerable: true }
598
+ });
599
+ return Object.freeze(live);
600
+ }
601
+ var PROVENANCE_SCALARS = ["source", "correlationId", "causationId"];
602
+ var PROVENANCE_ATTRIBUTES = ["traceAttributes", "auditAttributes"];
603
+ var BINDING_FIELDS = ["id", "tenantId", "actorId", "actionId", "parameterDigest", "resourceScope", "capturedAt", "expiresAt", "governingMoment"];
604
+ function privateSubmission(snapshot) {
605
+ const parameters = readField(snapshot, "parameters");
606
+ const provenanceRead = readField(snapshot, "provenance");
607
+ const bindingRead = readField(snapshot, "authorizationBinding");
608
+ const copied = copyJsonData(parameters);
609
+ const provenance = provenanceRead ? copyProvenance(provenanceRead) : void 0;
610
+ const authorizationBinding = bindingRead ? copyBinding(bindingRead) : void 0;
611
+ return Object.freeze({
612
+ actionId: snapshot.actionId,
613
+ tenantId: snapshot.tenantId,
614
+ spaceId: snapshot.spaceId,
615
+ actorId: snapshot.actorId,
616
+ actorType: snapshot.actorType,
617
+ ...snapshot.correlationId !== void 0 ? { correlationId: snapshot.correlationId } : {},
618
+ ...snapshot.causationId !== void 0 ? { causationId: snapshot.causationId } : {},
619
+ ...snapshot.executionReason !== void 0 ? { executionReason: snapshot.executionReason } : {},
620
+ ...snapshot.idempotencyKey !== void 0 ? { idempotencyKey: snapshot.idempotencyKey } : {},
621
+ ...snapshot.authorizationBindingId !== void 0 ? { authorizationBindingId: snapshot.authorizationBindingId } : {},
622
+ ...provenance ? { provenance } : {},
623
+ ...authorizationBinding ? { authorizationBinding } : {},
624
+ parameters: copied.ok ? copied.value : parameters,
625
+ parametersArePrivate: copied.ok
626
+ });
627
+ }
628
+ function readField(target, key) {
629
+ try {
630
+ return target[key];
631
+ } catch {
632
+ throw new SubmissionUnreadableError();
633
+ }
634
+ }
635
+ function primitiveOrAbsent(value) {
636
+ if (value !== null && (typeof value === "object" || typeof value === "function" || typeof value === "symbol")) {
637
+ throw new SubmissionUnreadableError();
638
+ }
639
+ return value;
640
+ }
641
+ function objectOf(value) {
642
+ if (value === null || typeof value !== "object") throw new SubmissionUnreadableError();
643
+ return value;
644
+ }
645
+ function copyFields(source, fields) {
646
+ const out = {};
647
+ for (const field of fields) {
648
+ const value = primitiveOrAbsent(readField(source, field));
649
+ if (value !== void 0) out[field] = value;
650
+ }
651
+ return out;
652
+ }
653
+ function copyAttributes(source, field) {
654
+ const value = readField(source, field);
655
+ if (value === void 0 || value === null) return void 0;
656
+ const map = objectOf(value);
657
+ let entries;
658
+ try {
659
+ entries = Object.entries(map);
660
+ } catch {
661
+ throw new SubmissionUnreadableError();
662
+ }
663
+ const out = {};
664
+ for (const [key, item] of entries) {
665
+ Reflect.defineProperty(out, key, { value: primitiveOrAbsent(item), enumerable: true, writable: true, configurable: true });
666
+ }
667
+ return out;
668
+ }
669
+ function copyProvenance(value) {
670
+ const source = objectOf(value);
671
+ const out = copyFields(source, PROVENANCE_SCALARS);
672
+ for (const field of PROVENANCE_ATTRIBUTES) {
673
+ const attributes = copyAttributes(source, field);
674
+ if (attributes !== void 0) out[field] = attributes;
675
+ }
676
+ return out;
677
+ }
678
+ function copyBinding(value) {
679
+ return copyFields(objectOf(value), BINDING_FIELDS);
680
+ }
681
+ function parametersCopy(snapshot) {
682
+ if (!snapshot.parametersArePrivate) throw new SubmissionUnreadableError();
683
+ const copied = copyJsonData(snapshot.parameters);
684
+ if (!copied.ok) throw new SubmissionUnreadableError();
685
+ return copied.value;
686
+ }
687
+ function liveSubmissionView(snapshot, input) {
688
+ const view = {
689
+ actionId: snapshot.actionId,
690
+ tenantId: snapshot.tenantId,
691
+ spaceId: snapshot.spaceId,
692
+ actorId: snapshot.actorId,
693
+ actorType: snapshot.actorType,
694
+ ...snapshot.correlationId !== void 0 ? { correlationId: snapshot.correlationId } : {},
695
+ ...snapshot.causationId !== void 0 ? { causationId: snapshot.causationId } : {},
696
+ ...snapshot.executionReason !== void 0 ? { executionReason: snapshot.executionReason } : {},
697
+ ...snapshot.idempotencyKey !== void 0 ? { idempotencyKey: snapshot.idempotencyKey } : {},
698
+ ...snapshot.authorizationBindingId !== void 0 ? { authorizationBindingId: snapshot.authorizationBindingId } : {}
699
+ };
700
+ const caller = input;
701
+ for (const field of ["parameters", "provenance", "authorizationBinding"]) {
702
+ Object.defineProperty(view, field, {
703
+ get: () => caller[field],
704
+ set: (value) => {
705
+ caller[field] = value;
706
+ },
707
+ enumerable: true,
708
+ configurable: true
709
+ });
710
+ }
711
+ return view;
712
+ }
713
+ function submissionView(snapshot) {
714
+ const fresh = (value) => {
715
+ if (value === void 0) return void 0;
716
+ const copied = copyJsonData(value);
717
+ if (!copied.ok) throw new SubmissionUnreadableError();
718
+ return copied.value;
719
+ };
720
+ const provenance = fresh(snapshot.provenance);
721
+ const authorizationBinding = fresh(snapshot.authorizationBinding);
722
+ return {
723
+ actionId: snapshot.actionId,
724
+ parameters: parametersCopy(snapshot),
725
+ tenantId: snapshot.tenantId,
726
+ spaceId: snapshot.spaceId,
727
+ actorId: snapshot.actorId,
728
+ actorType: snapshot.actorType,
729
+ ...snapshot.correlationId !== void 0 ? { correlationId: snapshot.correlationId } : {},
730
+ ...snapshot.causationId !== void 0 ? { causationId: snapshot.causationId } : {},
731
+ ...provenance ? { provenance } : {},
732
+ ...snapshot.executionReason !== void 0 ? { executionReason: snapshot.executionReason } : {},
733
+ ...snapshot.idempotencyKey !== void 0 ? { idempotencyKey: snapshot.idempotencyKey } : {},
734
+ ...snapshot.authorizationBindingId !== void 0 ? { authorizationBindingId: snapshot.authorizationBindingId } : {},
735
+ ...authorizationBinding ? { authorizationBinding } : {}
736
+ };
737
+ }
738
+ var MAX_REASON_LENGTH = 1e3;
739
+ function asOutboxRequeueStore(store) {
740
+ const candidate = store;
741
+ return typeof candidate.requeueOutboxDeadLetter === "function" && typeof candidate.getOutboxRecord === "function" && typeof candidate.listOutboxRequeues === "function" ? store : void 0;
742
+ }
743
+ function createOutboxRequeue(deps) {
744
+ return async function requeueOutboxDeadLetter(input) {
745
+ for (const field of ["outboxId", "tenantId", "spaceId", "actorId", "actorType"]) {
746
+ if (typeof input[field] !== "string" || input[field].trim() === "") {
747
+ throw new Error(`Outbox requeue requires ${field}.`);
748
+ }
749
+ }
750
+ if (typeof input.reason !== "string" || input.reason.trim() === "") {
751
+ throw new Error("Outbox requeue requires a reason.");
752
+ }
753
+ if (input.reason.length > MAX_REASON_LENGTH) {
754
+ throw new Error(`Outbox requeue reason must be at most ${MAX_REASON_LENGTH} characters.`);
755
+ }
756
+ if (input.availableAt !== void 0 && !(input.availableAt instanceof Date && Number.isFinite(input.availableAt.getTime()))) {
757
+ throw new Error("Outbox requeue availableAt must be a valid Date.");
758
+ }
759
+ const store = asOutboxRequeueStore(deps.store);
760
+ if (!store) throw new Error("Outbox requeue requires a store with outbox requeue support.");
761
+ const refuse = (refusal, message, currentStatus) => {
762
+ emitPlatformHostTelemetry(deps.telemetry, {
763
+ kind: "event",
764
+ name: "outbox.requeue_refused",
765
+ metricName: PLATFORM_HOST_METRIC_NAMES.outboxRequeueRefused,
766
+ occurredAt: deps.now(),
767
+ tenantId: input.tenantId,
768
+ spaceId: input.spaceId,
769
+ attributes: { refusal }
770
+ });
771
+ return { status: "refused", refusal, message, ...currentStatus ? { currentStatus } : {} };
772
+ };
773
+ const record = await store.getOutboxRecord(input.outboxId, input.tenantId, input.spaceId);
774
+ const authorization = deps.authorization;
775
+ const hasAuthorizer = typeof authorization.authorizeOutboxRequeue === "function";
776
+ const authorized = hasAuthorizer ? await authorization.authorizeOutboxRequeue({
777
+ tenantId: input.tenantId,
778
+ spaceId: input.spaceId,
779
+ actorId: input.actorId,
780
+ actorType: input.actorType,
781
+ outboxId: input.outboxId,
782
+ reason: input.reason,
783
+ ...record ? {
784
+ record: {
785
+ eventId: record.event.eventId,
786
+ eventType: record.event.eventType,
787
+ payloadClassification: record.event.payloadClassification,
788
+ status: record.status,
789
+ attemptCount: record.attemptCount
790
+ }
791
+ } : {}
792
+ }) : false;
793
+ if (authorized !== true) {
794
+ return refuse(
795
+ "not_authorized",
796
+ hasAuthorizer ? `Actor ${input.actorId} is not authorized to requeue outbox records in this scope.` : "Outbox requeue is refused because no authorizeOutboxRequeue is configured."
797
+ );
798
+ }
799
+ if (!record) return refuse("not_found", `Outbox record not found: ${input.outboxId}`);
800
+ if (record.status !== "dead_letter") {
801
+ return refuse("not_dead_letter", `Outbox record ${input.outboxId} is ${record.status}, not dead_letter.`, record.status);
802
+ }
803
+ const requeuedAt = deps.now();
804
+ const result = await store.requeueOutboxDeadLetter({
805
+ id: input.outboxId,
806
+ tenantId: input.tenantId,
807
+ spaceId: input.spaceId,
808
+ requeueId: platform.createFabricId("orq"),
809
+ actorId: input.actorId,
810
+ actorType: input.actorType,
811
+ reason: input.reason,
812
+ requeuedAt,
813
+ availableAt: input.availableAt ?? requeuedAt,
814
+ expectedLeaseToken: record.leaseToken ?? 0
815
+ });
816
+ if (!result.applied) {
817
+ if (result.refusal === "not_found") return refuse("not_found", `Outbox record not found: ${input.outboxId}`);
818
+ if (result.refusal === "stale") {
819
+ return refuse(
820
+ "stale",
821
+ `Outbox record ${input.outboxId} changed after it was authorized; read and authorize it again.`,
822
+ result.currentStatus
823
+ );
824
+ }
825
+ return refuse(
826
+ "not_dead_letter",
827
+ `Outbox record ${input.outboxId} is ${result.currentStatus ?? "no longer dead_letter"}, not dead_letter.`,
828
+ result.currentStatus
829
+ );
830
+ }
831
+ emitPlatformHostTelemetry(deps.telemetry, {
832
+ kind: "event",
833
+ name: "outbox.requeued",
834
+ metricName: PLATFORM_HOST_METRIC_NAMES.outboxRequeued,
835
+ occurredAt: requeuedAt,
836
+ tenantId: input.tenantId,
837
+ spaceId: input.spaceId,
838
+ attributes: { priorAttempt: result.requeue.priorAttemptCount }
839
+ });
840
+ return { status: "requeued", record: result.record, requeue: result.requeue };
841
+ };
842
+ }
501
843
 
502
844
  // src/host.ts
503
845
  var DEFAULT_EXTRACT_EVENTS = (data) => {
@@ -507,6 +849,12 @@ var DEFAULT_EXTRACT_EVENTS = (data) => {
507
849
  var RecoverableFinalizationError = class extends Error {
508
850
  name = "RecoverableFinalizationError";
509
851
  };
852
+ var DEFAULT_ADMISSION_TIMEOUT_MS = 1e4;
853
+ var MAX_TIMER_MS = 2147483647;
854
+ var monotonicNow = perf_hooks.performance.now.bind(perf_hooks.performance);
855
+ var ADMISSION_FAILED = Object.freeze({ kind: "failed" });
856
+ var ADMISSION_TIMED_OUT = Object.freeze({ kind: "timed_out" });
857
+ var ADMISSION_REFUSAL_CODE = /^[a-z][A-Za-z0-9_.:-]{0,127}$/;
510
858
  function createGovernedActionHost(options) {
511
859
  if (options.composition) {
512
860
  if (!options.registry?.orderedModules) {
@@ -528,45 +876,63 @@ function createGovernedActionHost(options) {
528
876
  }
529
877
  }
530
878
  const adapters = new platform.AdapterRegistry();
531
- for (const adapter of options.adapters ?? []) adapters.register(adapter);
879
+ for (const adapter of options.adapters ?? []) {
880
+ if (adapter.circuitBreaker) platform.assertAdapterCircuitBreakerConfig(adapter.circuitBreaker);
881
+ adapters.register(adapter);
882
+ }
532
883
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
884
+ const circuitBreakers = options.adapterCircuitBreakers ?? new platform.AdapterCircuitBreakerRegistry({ now: () => now().getTime() });
533
885
  const extractEvents = options.extractEvents ?? DEFAULT_EXTRACT_EVENTS;
534
886
  const configuredActionResolver = options.resolveAction ?? options.registry?.resolveAction;
535
887
  if (!configuredActionResolver) throw new Error("Platform Host requires an explicit module registry or resolveAction function.");
536
888
  const actionResolver = configuredActionResolver;
537
889
  const stateMachineResolver = options.registry?.resolveStateMachine ?? (() => void 0);
538
890
  const eventResultFields = options.eventResultFields ?? ["_events"];
891
+ const admissionTimeoutMs = options.admissionTimeoutMs ?? DEFAULT_ADMISSION_TIMEOUT_MS;
892
+ if (typeof admissionTimeoutMs !== "number" || !Number.isInteger(admissionTimeoutMs) || admissionTimeoutMs < 1 || admissionTimeoutMs > MAX_TIMER_MS) {
893
+ throw new Error(`admissionTimeoutMs must be a whole number of milliseconds from 1 to ${MAX_TIMER_MS}.`);
894
+ }
539
895
  async function submitAction(input) {
540
- if (input.executionReason && !["initial", "offline_replay"].includes(input.executionReason)) {
541
- throw new Error(`Unsupported submitted execution reason: ${input.executionReason}`);
896
+ const read = snapshotSubmission(input);
897
+ if (read.executionReason && !["initial", "offline_replay"].includes(read.executionReason)) {
898
+ throw new Error(`Unsupported submitted execution reason: ${read.executionReason}`);
542
899
  }
543
- const action = actionResolver(input.actionId);
544
- if (!action) throw new Error(`Unknown action: ${input.actionId}`);
545
- if (action.execution?.connectivity === "online-required" && input.executionReason === "offline_replay") {
546
- throw new Error(`Action ${input.actionId} is online-required and cannot be submitted as an offline replay.`);
900
+ const action = actionResolver(read.actionId);
901
+ if (!action) throw new Error(`Unknown action: ${read.actionId}`);
902
+ const declaresAdmission = action.admitSubmission !== void 0;
903
+ const submission = declaresAdmission ? privateSubmission(read) : read;
904
+ if (action.execution?.connectivity === "online-required" && submission.executionReason === "offline_replay") {
905
+ throw new Error(`Action ${submission.actionId} is online-required and cannot be submitted as an offline replay.`);
547
906
  }
548
- if (action.execution?.sensitivity === "restricted" && Object.keys(input.provenance?.auditAttributes ?? {}).length > 0) {
549
- throw new Error(`Action ${input.actionId} declares restricted sensitivity; provenance audit attributes must not be durably recorded.`);
907
+ if (action.execution?.sensitivity === "restricted" && Object.keys(submission.provenance?.auditAttributes ?? {}).length > 0) {
908
+ throw new Error(`Action ${submission.actionId} declares restricted sensitivity; provenance audit attributes must not be durably recorded.`);
550
909
  }
551
- const authorizationInput = toAuthorizationInput(action, input);
910
+ const authorizationInput = toAuthorizationInput(action, submission);
552
911
  if (!await options.authorization.checkEntitlement(authorizationInput)) {
553
- throw new Error(`Module "${action.namespace}" is not enabled for tenant ${input.tenantId}`);
912
+ throw new Error(`Module "${action.namespace}" is not enabled for tenant ${submission.tenantId}`);
554
913
  }
555
914
  if (!await options.authorization.authorize(authorizationInput)) {
556
- throw new Error(`Actor ${input.actorId} is not authorized for action ${input.actionId}`);
915
+ throw new Error(`Actor ${submission.actorId} is not authorized for action ${submission.actionId}`);
916
+ }
917
+ if (declaresAdmission && (!submission.parametersArePrivate || !isRecord(submission.parameters))) {
918
+ refuseAdmission(action, submission, "invalid_parameters");
557
919
  }
558
920
  const actionInvocationId = platform.createFabricId("act");
559
- if (input.correlationId && input.provenance?.correlationId && input.correlationId !== input.provenance.correlationId) throw new Error("Invocation provenance correlationId does not match submission correlationId.");
560
- if (input.causationId && input.provenance?.causationId && input.causationId !== input.provenance.causationId) throw new Error("Invocation provenance causationId does not match submission causationId.");
561
- const correlationId = input.correlationId ?? input.provenance?.correlationId ?? platform.createFabricId("corr");
562
- const durableParameters = options.redactActionParameters ? options.redactActionParameters(input.actionId, input.parameters) : input.parameters;
563
- const parameterDigest = digestParameters(input.parameters);
564
- if (input.authorizationBinding) validateAuthorizationBinding(input, parameterDigest, action.execution?.authorityMoment, now());
921
+ if (submission.correlationId && submission.provenance?.correlationId && submission.correlationId !== submission.provenance.correlationId) throw new Error("Invocation provenance correlationId does not match submission correlationId.");
922
+ if (submission.causationId && submission.provenance?.causationId && submission.causationId !== submission.provenance.causationId) throw new Error("Invocation provenance causationId does not match submission causationId.");
923
+ const correlationId = submission.correlationId ?? submission.provenance?.correlationId ?? platform.createFabricId("corr");
924
+ const prepare = () => {
925
+ const durableParameters2 = options.redactActionParameters ? options.redactActionParameters(submission.actionId, submission.parameters) : submission.parameters;
926
+ const parameterDigest2 = digestParameters(submission.parameters);
927
+ if (submission.authorizationBinding) validateAuthorizationBinding(submission, parameterDigest2, action.execution?.authorityMoment, now());
928
+ return { durableParameters: durableParameters2, parameterDigest: parameterDigest2 };
929
+ };
930
+ const early = declaresAdmission ? void 0 : prepare();
565
931
  const durableProvenance = normalizeProvenance(
566
- input.provenance,
932
+ submission.provenance,
567
933
  options.provenance
568
934
  );
569
- const initiatingReleaseDigest = await options.composition?.resolveInitiatingReleaseDigest?.(input);
935
+ const initiatingReleaseDigest = await options.composition?.resolveInitiatingReleaseDigest?.(declaresAdmission ? submissionView(submission) : liveSubmissionView(submission, input));
570
936
  const runtimeEvidence = {
571
937
  governanceContractVersion: platform.FABRIC_GOVERNANCE_CONTRACT_VERSION,
572
938
  hostContractVersion: PLATFORM_HOST_CONTRACT_VERSION,
@@ -575,48 +941,51 @@ function createGovernedActionHost(options) {
575
941
  ...initiatingReleaseDigest ? { initiatingReleaseDigest } : {}
576
942
  };
577
943
  platform.assertGovernanceRuntimeEvidence(runtimeEvidence);
944
+ const admission = declaresAdmission ? await admitSubmission(action, submission) : void 0;
945
+ const { durableParameters, parameterDigest } = early ?? prepare();
578
946
  const durableInvocation = await options.store.createActionInvocation({
579
947
  id: actionInvocationId,
580
- tenantId: input.tenantId,
581
- spaceId: input.spaceId,
582
- actionId: input.actionId,
948
+ tenantId: submission.tenantId,
949
+ spaceId: submission.spaceId,
950
+ actionId: submission.actionId,
583
951
  actionVersion: action.version,
584
- actorId: input.actorId,
585
- actorType: input.actorType,
952
+ actorId: submission.actorId,
953
+ actorType: submission.actorType,
586
954
  status: "pending",
587
955
  parameters: durableParameters,
588
956
  result: {},
589
957
  runtimeEvidence,
590
958
  correlationId,
591
- ...input.causationId ?? input.provenance?.causationId ? { causationId: input.causationId ?? input.provenance?.causationId } : {},
592
- ...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
593
- ...input.idempotencyKey ? {
959
+ ...submission.causationId ?? submission.provenance?.causationId ? { causationId: submission.causationId ?? submission.provenance?.causationId } : {},
960
+ ...submission.idempotencyKey ? { idempotencyKey: submission.idempotencyKey } : {},
961
+ ...submission.idempotencyKey ? {
594
962
  parameterDigest,
595
963
  parameterDigestAlgorithm: PARAMETER_DIGEST_ALGORITHM,
596
- idempotencyActorId: input.actorId,
597
- ...input.authorizationBindingId ? { idempotencyAuthorizationBindingId: input.authorizationBindingId } : {}
964
+ idempotencyActorId: submission.actorId,
965
+ ...submission.authorizationBindingId ? { idempotencyAuthorizationBindingId: submission.authorizationBindingId } : {}
598
966
  } : {},
599
967
  ...durableProvenance ? { provenance: durableProvenance } : {},
600
- ...input.authorizationBinding ? { authorizationBinding: input.authorizationBinding } : {},
601
- ...input.executionReason ? { executionReason: input.executionReason } : {},
602
- ...input.authorizationBindingId ? { authorizationBindingId: input.authorizationBindingId } : {}
968
+ ...submission.authorizationBinding ? { authorizationBinding: submission.authorizationBinding } : {},
969
+ ...submission.executionReason ? { executionReason: submission.executionReason } : {},
970
+ ...submission.authorizationBindingId ? { authorizationBindingId: submission.authorizationBindingId } : {},
971
+ ...admission ? { admittedAt: admission.admittedAt } : {}
603
972
  });
604
973
  emitPlatformHostTelemetry(options.telemetry, {
605
974
  kind: "event",
606
975
  name: "invocation.submitted",
607
976
  metricName: PLATFORM_HOST_METRIC_NAMES.invocationSubmitted,
608
977
  occurredAt: now(),
609
- tenantId: input.tenantId,
610
- spaceId: input.spaceId,
978
+ tenantId: submission.tenantId,
979
+ spaceId: submission.spaceId,
611
980
  attributes: { actionVersion: action.version }
612
981
  });
613
- if (durableInvocation.id !== actionInvocationId && input.idempotencyKey) {
982
+ if (durableInvocation.id !== actionInvocationId && submission.idempotencyKey) {
614
983
  const conflict = idempotencyConflict(durableInvocation, {
615
- actorId: input.actorId,
616
- authorizationBindingId: input.authorizationBindingId,
984
+ actorId: submission.actorId,
985
+ authorizationBindingId: submission.authorizationBindingId,
617
986
  actionVersion: action.version,
618
987
  parameterDigest,
619
- idempotencyKey: input.idempotencyKey
988
+ idempotencyKey: submission.idempotencyKey
620
989
  });
621
990
  if (!durableInvocation.parameterDigest) {
622
991
  options.idempotency?.onLegacyRecord?.(durableInvocation);
@@ -636,8 +1005,8 @@ function createGovernedActionHost(options) {
636
1005
  const dispatched = await options.dispatcher.dispatch({
637
1006
  actionInvocationId: durableInvocation.id,
638
1007
  actionId: durableInvocation.actionId,
639
- tenantId: input.tenantId,
640
- spaceId: input.spaceId,
1008
+ tenantId: submission.tenantId,
1009
+ spaceId: submission.spaceId,
641
1010
  workflowId: durableWorkflowId
642
1011
  });
643
1012
  return withConsistency({
@@ -647,7 +1016,7 @@ function createGovernedActionHost(options) {
647
1016
  ...dispatched.runId ? { runId: dispatched.runId } : {},
648
1017
  ...durableInvocation.hitlRoute ? { hitlRoute: durableInvocation.hitlRoute } : {},
649
1018
  ...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {}
650
- }, input.actionId);
1019
+ }, submission.actionId);
651
1020
  }
652
1021
  return withConsistency({
653
1022
  actionInvocationId: durableInvocation.id,
@@ -660,15 +1029,15 @@ function createGovernedActionHost(options) {
660
1029
  ...durableInvocation.authorizationReconciliation ? { reconciliation: durableInvocation.authorizationReconciliation } : {},
661
1030
  ...durableInvocation.adapterReconciliation ? { adapterReconciliation: durableInvocation.adapterReconciliation } : {},
662
1031
  ...durableInvocation.pendingCompletion ? { pendingCompletion: durableInvocation.pendingCompletion } : {}
663
- }, input.actionId);
1032
+ }, submission.actionId);
664
1033
  }
665
1034
  if (options.dispatcher) {
666
1035
  try {
667
1036
  const dispatched = await options.dispatcher.dispatch({
668
1037
  actionInvocationId: durableInvocation.id,
669
1038
  actionId: durableInvocation.actionId,
670
- tenantId: input.tenantId,
671
- spaceId: input.spaceId,
1039
+ tenantId: submission.tenantId,
1040
+ spaceId: submission.spaceId,
672
1041
  workflowId: durableWorkflowId
673
1042
  });
674
1043
  return withConsistency({
@@ -678,17 +1047,78 @@ function createGovernedActionHost(options) {
678
1047
  ...dispatched.runId ? { runId: dispatched.runId } : {},
679
1048
  ...durableInvocation.hitlRoute ? { hitlRoute: durableInvocation.hitlRoute } : {},
680
1049
  ...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {}
681
- }, input.actionId);
1050
+ }, submission.actionId);
682
1051
  } catch (error) {
683
1052
  throw error;
684
1053
  }
685
1054
  }
686
1055
  const executed = await executeInvocation(
687
1056
  durableInvocation.id,
688
- input.tenantId,
689
- input.spaceId
1057
+ submission.tenantId,
1058
+ submission.spaceId
690
1059
  );
691
- return { ...withConsistency(executed, input.actionId), workflowId: durableWorkflowId };
1060
+ return { ...withConsistency(executed, submission.actionId), workflowId: durableWorkflowId };
1061
+ }
1062
+ async function admitSubmission(action, submission) {
1063
+ const refuse = (refusal, refusalCode) => refuseAdmission(action, submission, refusal, refusalCode);
1064
+ const check = action.admitSubmission;
1065
+ if (typeof check !== "function") return refuse("failed");
1066
+ const checked = parametersCopy(submission);
1067
+ const controller = new AbortController();
1068
+ let timer;
1069
+ const deadline = new Promise((resolve) => {
1070
+ timer = setTimeout(() => {
1071
+ controller.abort();
1072
+ resolve(ADMISSION_TIMED_OUT);
1073
+ }, admissionTimeoutMs);
1074
+ });
1075
+ const startedAt = monotonicNow();
1076
+ const settledInTime = () => monotonicNow() - startedAt < admissionTimeoutMs;
1077
+ let answered;
1078
+ try {
1079
+ answered = Promise.resolve(check(Object.freeze({
1080
+ actionId: action.actionId,
1081
+ tenantId: submission.tenantId,
1082
+ spaceId: submission.spaceId,
1083
+ actorId: submission.actorId,
1084
+ actorType: submission.actorType,
1085
+ parameters: checked,
1086
+ signal: controller.signal
1087
+ }))).then(
1088
+ (value) => settledInTime() ? { kind: "answered", value } : ADMISSION_TIMED_OUT,
1089
+ () => settledInTime() ? ADMISSION_FAILED : ADMISSION_TIMED_OUT
1090
+ );
1091
+ } catch {
1092
+ answered = Promise.resolve(settledInTime() ? ADMISSION_FAILED : ADMISSION_TIMED_OUT);
1093
+ }
1094
+ let outcome;
1095
+ try {
1096
+ outcome = await Promise.race([answered, deadline]);
1097
+ } finally {
1098
+ clearTimeout(timer);
1099
+ }
1100
+ if (outcome.kind === "timed_out") {
1101
+ controller.abort();
1102
+ return refuse("timed_out");
1103
+ }
1104
+ if (outcome.kind === "failed") return refuse("failed");
1105
+ const admitted = ownDataProperty(outcome.value, "admitted");
1106
+ if (admitted === true) return { admittedAt: now() };
1107
+ const code = ownDataProperty(outcome.value, "code");
1108
+ if (admitted === false && typeof code === "string" && ADMISSION_REFUSAL_CODE.test(code)) return refuse("refused", code);
1109
+ return refuse("invalid_decision");
1110
+ }
1111
+ function refuseAdmission(action, submission, refusal, refusalCode) {
1112
+ emitPlatformHostTelemetry(options.telemetry, {
1113
+ kind: "event",
1114
+ name: "invocation.admission_refused",
1115
+ metricName: PLATFORM_HOST_METRIC_NAMES.invocationAdmissionRefused,
1116
+ occurredAt: now(),
1117
+ tenantId: submission.tenantId,
1118
+ spaceId: submission.spaceId,
1119
+ attributes: { actionVersion: action.version, refusal }
1120
+ });
1121
+ throw new ActionAdmissionError(action.actionId, refusal, refusalCode);
692
1122
  }
693
1123
  async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}, executionReasonOverride) {
694
1124
  const loadedInvocation = await options.store.getActionInvocation(
@@ -717,6 +1147,13 @@ function createGovernedActionHost(options) {
717
1147
  if (!action) {
718
1148
  return fail2(invocation, "failed", `Unknown action: ${invocation.actionId}`);
719
1149
  }
1150
+ if (action.admitSubmission !== void 0 && !isAdmissionTime(invocation.admittedAt)) {
1151
+ return fail2(
1152
+ invocation,
1153
+ "failed",
1154
+ `Invocation of ${action.actionId} was not admitted at submission; an action that declares admitSubmission runs only on invocations its check admitted.`
1155
+ );
1156
+ }
720
1157
  if (action.kind === "saga") {
721
1158
  return fail2(
722
1159
  invocation,
@@ -884,6 +1321,10 @@ function createGovernedActionHost(options) {
884
1321
  }
885
1322
  const definitions = options.resolvePolicies ? await options.resolvePolicies({
886
1323
  ...authorizationInput,
1324
+ actionInvocationId,
1325
+ parameters: parsed.data,
1326
+ invocation,
1327
+ executionReason,
887
1328
  declaredPolicyIds: action.policies ?? []
888
1329
  }) : declaredPolicies(action.policies ?? []);
889
1330
  const outcomes = await platform.evaluatePolicyDefinitions(definitions, {
@@ -994,6 +1435,7 @@ function createGovernedActionHost(options) {
994
1435
  correlationId: invocation.correlationId,
995
1436
  ...invocation.causationId ? { causationId: invocation.causationId } : {},
996
1437
  ...invocation.provenance ? { provenance: invocation.provenance } : {},
1438
+ ...isAdmissionTime(invocation.admittedAt) ? { admittedAt: invocation.admittedAt } : {},
997
1439
  db,
998
1440
  services: options.services
999
1441
  },
@@ -1121,14 +1563,20 @@ function createGovernedActionHost(options) {
1121
1563
  );
1122
1564
  adapterDeadlineTimer.unref?.();
1123
1565
  }
1566
+ const circuitBreaker = circuitBreakers.forAdapter(adapter);
1124
1567
  const result2 = await platform.executeWithAdapterRetry({
1568
+ ...circuitBreaker ? { circuitBreaker } : {},
1125
1569
  policy: step.retryPolicy ?? adapter.retryPolicy,
1126
1570
  defaultIdempotent: adapter.idempotent,
1127
- execute: async (attempt, maxAttempts) => {
1571
+ // Every await before the call happens here, before breaker
1572
+ // admission, so nothing awaits between admission and the adapter.
1573
+ prepareAttempt: async (attempt) => {
1128
1574
  await options.store.updateAdapterInvocation(adapterInvocationId, {
1129
1575
  attempt,
1130
1576
  updatedAt: now()
1131
1577
  });
1578
+ },
1579
+ execute: (attempt, maxAttempts) => {
1132
1580
  return adapter.execute(input, {
1133
1581
  tenantId,
1134
1582
  spaceId,
@@ -1146,6 +1594,7 @@ function createGovernedActionHost(options) {
1146
1594
  getError: (result3) => result3.error,
1147
1595
  classifyOutcome: (result3) => result3.outcome ?? "transient_failure",
1148
1596
  classifyThrownError: (error) => {
1597
+ if (platform.isAdapterCircuitOpenError(error)) return "transient_failure";
1149
1598
  if (adapterController?.signal.aborted) return "timeout";
1150
1599
  const message = error instanceof Error ? error.message : String(error);
1151
1600
  if (/timeout|deadline|timed?\s*out/i.test(message)) return "timeout";
@@ -1323,6 +1772,19 @@ function createGovernedActionHost(options) {
1323
1772
  error: message,
1324
1773
  updatedAt: now()
1325
1774
  });
1775
+ if (platform.isAdapterCircuitOpenError(error)) {
1776
+ await appendEvent(invocation, {
1777
+ eventType: "AdapterInvocationFailed",
1778
+ subjectType: adapterEventSubject.subjectType,
1779
+ subjectId: adapterEventSubject.subjectId,
1780
+ payload: {
1781
+ adapterType: step.adapterType,
1782
+ operation: step.operation,
1783
+ reason: "circuit_open",
1784
+ ...error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
1785
+ }
1786
+ }, `adapter:${stepIndex}:failed`);
1787
+ }
1326
1788
  return fail2(invocation, "failed", message);
1327
1789
  } finally {
1328
1790
  if (adapterDeadlineTimer) clearTimeout(adapterDeadlineTimer);
@@ -1576,6 +2038,8 @@ function createGovernedActionHost(options) {
1576
2038
  producerModuleVersion: options.outbox.producerModuleVersion(invocation.actionId, invocation.actionVersion),
1577
2039
  payloadClassification,
1578
2040
  includeProvenance: options.outbox.includeProvenance?.(envelope, payloadClassification) ?? payloadClassification !== "restricted",
2041
+ // Actor identity is excluded for every classification unless the application opts in.
2042
+ includeActor: options.outbox.includeActor?.(envelope, payloadClassification) === true,
1579
2043
  ...traceContext ? { traceContext } : {}
1580
2044
  };
1581
2045
  if (transaction) {
@@ -1645,7 +2109,13 @@ function createGovernedActionHost(options) {
1645
2109
  recordExecutionAttestation,
1646
2110
  recordExternalReconciliation,
1647
2111
  completeExternalInvocation: externalCompletion.completeExternalInvocation,
1648
- reconcileOverdueCompletions: externalCompletion.reconcileOverdueCompletions
2112
+ reconcileOverdueCompletions: externalCompletion.reconcileOverdueCompletions,
2113
+ requeueOutboxDeadLetter: createOutboxRequeue({
2114
+ store: options.store,
2115
+ authorization: options.authorization,
2116
+ telemetry: options.telemetry,
2117
+ now
2118
+ })
1649
2119
  };
1650
2120
  }
1651
2121
  function actionResult(invocation) {
@@ -1707,6 +2177,18 @@ function isTerminal(status) {
1707
2177
  function isRecord(value) {
1708
2178
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
1709
2179
  }
2180
+ function isAdmissionTime(value) {
2181
+ return value instanceof Date && Number.isFinite(value.getTime());
2182
+ }
2183
+ function ownDataProperty(value, key) {
2184
+ if (value === null || typeof value !== "object") return void 0;
2185
+ try {
2186
+ const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
2187
+ return descriptor && "value" in descriptor ? descriptor.value : void 0;
2188
+ } catch {
2189
+ return void 0;
2190
+ }
2191
+ }
1710
2192
  function validateAuthorizationBinding(input, parameterDigest, authorityMoment, currentTime = /* @__PURE__ */ new Date()) {
1711
2193
  const binding = input.authorizationBinding;
1712
2194
  if (binding.id !== input.authorizationBindingId && input.authorizationBindingId) throw new Error("Authorization binding ID does not match authorizationBindingId.");
@@ -1746,6 +2228,9 @@ function toEnterpriseEventEnvelope(event, metadata) {
1746
2228
  ...event.actionInvocationId ? { actionInvocationId: event.actionInvocationId } : {},
1747
2229
  correlationId: event.correlationId,
1748
2230
  ...event.causationId ? { causationId: event.causationId } : {},
2231
+ // Opt-in only: without it the keys are absent, so existing publishers see an unchanged envelope.
2232
+ ...includesActor(metadata) && event.actorId ? { actorId: event.actorId } : {},
2233
+ ...includesActor(metadata) && event.actorType ? { actorType: event.actorType } : {},
1749
2234
  occurredAt: event.occurredAt,
1750
2235
  recordedAt: event.recordedAt,
1751
2236
  producerModuleVersion: metadata.producerModuleVersion,
@@ -1757,6 +2242,12 @@ function toEnterpriseEventEnvelope(event, metadata) {
1757
2242
  payload: event.payload
1758
2243
  };
1759
2244
  }
2245
+ function outboxActorDisclosure(metadata) {
2246
+ return includesActor(metadata) ? "included" : "suppressed";
2247
+ }
2248
+ function includesActor(metadata) {
2249
+ return metadata.includeActor === true;
2250
+ }
1760
2251
  async function runOutboxRelayCycle(options) {
1761
2252
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
1762
2253
  const maxAttempts = options.maxAttempts ?? 10;
@@ -1791,7 +2282,7 @@ async function runOutboxRelayCycle(options) {
1791
2282
  try {
1792
2283
  await publishWithTimeout(
1793
2284
  options.publisher,
1794
- record.event,
2285
+ record,
1795
2286
  options.publishTimeoutMs ?? 1e4
1796
2287
  );
1797
2288
  await stopHeartbeat();
@@ -1853,7 +2344,7 @@ async function runOutboxRelayCycle(options) {
1853
2344
  });
1854
2345
  return result;
1855
2346
  }
1856
- async function publishWithTimeout(publisher, event, timeoutMs) {
2347
+ async function publishWithTimeout(publisher, record, timeoutMs) {
1857
2348
  if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
1858
2349
  throw new Error("publishTimeoutMs must be a positive finite number");
1859
2350
  }
@@ -1867,7 +2358,11 @@ async function publishWithTimeout(publisher, event, timeoutMs) {
1867
2358
  }, timeoutMs);
1868
2359
  });
1869
2360
  try {
1870
- await Promise.race([publisher.publish(event, { signal: controller.signal }), timeout]);
2361
+ const context = {
2362
+ signal: controller.signal,
2363
+ actorDisclosure: record.actorDisclosure ?? "legacy"
2364
+ };
2365
+ await Promise.race([publisher.publish(record.event, context), timeout]);
1871
2366
  } finally {
1872
2367
  if (timer) clearTimeout(timer);
1873
2368
  }
@@ -1897,9 +2392,23 @@ function startOutboxHeartbeat(options, record, leaseDurationMs) {
1897
2392
  };
1898
2393
  }
1899
2394
  function cloneOutboxRecord(record) {
2395
+ const event = {
2396
+ ...record.event,
2397
+ occurredAt: new Date(record.event.occurredAt),
2398
+ recordedAt: new Date(record.event.recordedAt),
2399
+ ...record.event.traceContext ? { traceContext: { ...record.event.traceContext } } : {}
2400
+ };
2401
+ if (record.actorDisclosure !== "included") {
2402
+ delete event.actorId;
2403
+ delete event.actorType;
2404
+ }
1900
2405
  return {
1901
2406
  ...record,
1902
- event: { ...record.event, ...record.event.traceContext ? { traceContext: { ...record.event.traceContext } } : {} }
2407
+ availableAt: new Date(record.availableAt),
2408
+ createdAt: new Date(record.createdAt),
2409
+ ...record.leaseExpiresAt ? { leaseExpiresAt: new Date(record.leaseExpiresAt) } : {},
2410
+ ...record.publishedAt ? { publishedAt: new Date(record.publishedAt) } : {},
2411
+ event
1903
2412
  };
1904
2413
  }
1905
2414
  function emitRelayTelemetry(telemetry, name, occurredAt, workerId, attributes) {
@@ -1931,6 +2440,13 @@ var MemoryPlatformHostStore = class {
1931
2440
  executionAttestations = [];
1932
2441
  externalReconciliations = [];
1933
2442
  outbox = [];
2443
+ /**
2444
+ * Requeue evidence is append-only audit history, so unlike the ledger
2445
+ * arrays above, which tests have always inspected and seeded directly, it
2446
+ * is not public: `listOutboxRequeues` returns copies with their own Dates. TypeScript
2447
+ * `private`, as for the store's other internals, is a compile-time guard.
2448
+ */
2449
+ outboxRequeues = [];
1934
2450
  async transaction(run) {
1935
2451
  return run(this.db);
1936
2452
  }
@@ -2131,6 +2647,7 @@ var MemoryPlatformHostStore = class {
2131
2647
  tenantId: event.tenantId,
2132
2648
  spaceId: event.spaceId,
2133
2649
  event: toEnterpriseEventEnvelope(event, metadata),
2650
+ actorDisclosure: outboxActorDisclosure(metadata),
2134
2651
  status: "pending",
2135
2652
  attemptCount: 0,
2136
2653
  availableAt: createdAt,
@@ -2172,6 +2689,42 @@ var MemoryPlatformHostStore = class {
2172
2689
  async listOutbox(input = {}) {
2173
2690
  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);
2174
2691
  }
2692
+ async getOutboxRecord(id, tenantId, spaceId) {
2693
+ const record = this.outbox.find((candidate) => candidate.id === id && candidate.tenantId === tenantId && candidate.spaceId === spaceId);
2694
+ return record ? cloneOutboxRecord(record) : void 0;
2695
+ }
2696
+ async requeueOutboxDeadLetter(input) {
2697
+ const record = this.outbox.find((candidate) => candidate.id === input.id && candidate.tenantId === input.tenantId && candidate.spaceId === input.spaceId);
2698
+ if (!record) return { applied: false, refusal: "not_found" };
2699
+ if (record.status !== "dead_letter") return { applied: false, refusal: "not_dead_letter", currentStatus: record.status };
2700
+ if ((record.leaseToken ?? 0) !== input.expectedLeaseToken) return { applied: false, refusal: "stale", currentStatus: record.status };
2701
+ const requeue = {
2702
+ id: input.requeueId,
2703
+ outboxId: record.id,
2704
+ tenantId: record.tenantId,
2705
+ spaceId: record.spaceId,
2706
+ eventId: record.event.eventId,
2707
+ actorId: input.actorId,
2708
+ actorType: input.actorType,
2709
+ reason: input.reason,
2710
+ requeuedAt: new Date(input.requeuedAt),
2711
+ availableAt: new Date(input.availableAt),
2712
+ priorAttemptCount: record.attemptCount,
2713
+ ...record.lastError !== void 0 ? { priorLastError: record.lastError } : {},
2714
+ priorAvailableAt: new Date(record.availableAt),
2715
+ priorLeaseToken: record.leaseToken ?? 0
2716
+ };
2717
+ record.status = "pending";
2718
+ record.attemptCount = 0;
2719
+ record.availableAt = new Date(input.availableAt);
2720
+ delete record.leaseOwner;
2721
+ delete record.leaseExpiresAt;
2722
+ this.outboxRequeues.push(requeue);
2723
+ return { applied: true, record: cloneOutboxRecord(record), requeue: cloneOutboxRequeue(requeue) };
2724
+ }
2725
+ async listOutboxRequeues(input) {
2726
+ return this.outboxRequeues.filter((requeue) => requeue.tenantId === input.tenantId && requeue.spaceId === input.spaceId && (input.outboxId === void 0 || requeue.outboxId === input.outboxId)).sort((left, right) => left.requeuedAt.getTime() - right.requeuedAt.getTime() || left.id.localeCompare(right.id)).map(cloneOutboxRequeue);
2727
+ }
2175
2728
  requireLeasedOutbox(id, workerId, leaseToken) {
2176
2729
  const record = this.outbox.find((candidate) => candidate.id === id);
2177
2730
  if (!record || record.leaseOwner !== workerId || (record.leaseToken ?? 0) !== (leaseToken ?? 0)) {
@@ -2253,6 +2806,14 @@ var MemoryPlatformHostStore = class {
2253
2806
  return this.events.filter((event) => event.tenantId === tenantId && event.spaceId === spaceId).sort((left, right) => left.sequence - right.sequence);
2254
2807
  }
2255
2808
  };
2809
+ function cloneOutboxRequeue(requeue) {
2810
+ return {
2811
+ ...requeue,
2812
+ requeuedAt: new Date(requeue.requeuedAt),
2813
+ availableAt: new Date(requeue.availableAt),
2814
+ priorAvailableAt: new Date(requeue.priorAvailableAt)
2815
+ };
2816
+ }
2256
2817
  async function applyPostgresMigrations(client, migrations) {
2257
2818
  const ordered = validateMigrations(migrations);
2258
2819
  const statements = ordered.map((migration) => migrationStatement(migration));
@@ -2494,6 +3055,43 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
2494
3055
  ON fabric_platform.action_invocations (completion_due_at)
2495
3056
  WHERE completion_due_at IS NOT NULL;
2496
3057
  `
3058
+ }, {
3059
+ version: 4,
3060
+ name: "outbox_actor_disclosure",
3061
+ // Nullable with no default, so rows written before this migration stay NULL and read back
3062
+ // as legacy. NOT VALID skips scanning existing rows; the check still applies to every
3063
+ // insert and update from here on.
3064
+ sql: `
3065
+ ALTER TABLE fabric_platform.event_outbox
3066
+ ADD COLUMN actor_disclosure text;
3067
+ ALTER TABLE fabric_platform.event_outbox
3068
+ ADD CONSTRAINT event_outbox_actor_disclosure_check
3069
+ CHECK (actor_disclosure IN ('included', 'suppressed')) NOT VALID;
3070
+ `
3071
+ }, {
3072
+ version: 5,
3073
+ name: "outbox_requeue",
3074
+ sql: `
3075
+ CREATE TABLE fabric_platform.event_outbox_requeues (
3076
+ id text PRIMARY KEY, outbox_id text NOT NULL,
3077
+ tenant_id text NOT NULL, space_id text NOT NULL, event_id text NOT NULL,
3078
+ actor_id text NOT NULL, actor_type text NOT NULL, reason text NOT NULL,
3079
+ requeued_at timestamptz NOT NULL, available_at timestamptz NOT NULL,
3080
+ prior_attempt_count integer NOT NULL, prior_last_error text,
3081
+ prior_available_at timestamptz NOT NULL, prior_lease_token bigint NOT NULL
3082
+ );
3083
+ CREATE INDEX event_outbox_requeues_record_idx
3084
+ ON fabric_platform.event_outbox_requeues (tenant_id, space_id, outbox_id, requeued_at);
3085
+ `
3086
+ }, {
3087
+ version: 6,
3088
+ name: "invocation_admission",
3089
+ // Nullable with no default: rows written before this migration, and
3090
+ // rows of actions that declare no admission check, stay NULL.
3091
+ sql: `
3092
+ ALTER TABLE fabric_platform.action_invocations
3093
+ ADD COLUMN admitted_at timestamptz;
3094
+ `
2497
3095
  }]);
2498
3096
  }
2499
3097
  async transaction(run) {
@@ -2501,44 +3099,59 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
2501
3099
  }
2502
3100
  async createActionInvocation(input) {
2503
3101
  const now = /* @__PURE__ */ new Date();
2504
- const result = await this.sql.query(
2505
- `INSERT INTO fabric_platform.action_invocations
2506
- (id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
2507
- parameters,result,correlation_id,causation_id,idempotency_key,
2508
- parameter_digest,parameter_digest_algorithm,idempotency_actor_id,
2509
- idempotency_authorization_binding_id,invocation_provenance,authorization_binding,execution_reason,
2510
- authorization_binding_id,error,runtime_evidence,created_at,updated_at)
2511
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16,$17,$18::jsonb,$19::jsonb,$20,$21,$22,$23::jsonb,$24,$24)
2512
- ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
2513
- WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
2514
- RETURNING *`,
2515
- [
2516
- input.id,
2517
- input.tenantId,
2518
- input.spaceId,
2519
- input.actionId,
2520
- input.actionVersion,
2521
- input.actorId,
2522
- input.actorType,
2523
- input.status,
2524
- JSON.stringify(input.parameters),
2525
- JSON.stringify(input.result),
2526
- input.correlationId,
2527
- input.causationId ?? null,
2528
- input.idempotencyKey ?? null,
2529
- input.parameterDigest ?? null,
2530
- input.parameterDigestAlgorithm ?? null,
2531
- input.idempotencyActorId ?? null,
2532
- input.idempotencyAuthorizationBindingId ?? null,
2533
- input.provenance ? JSON.stringify(input.provenance) : null,
2534
- input.authorizationBinding ? JSON.stringify(input.authorizationBinding) : null,
2535
- input.executionReason ?? null,
2536
- input.authorizationBindingId ?? null,
2537
- input.error ?? null,
2538
- input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
2539
- now
2540
- ]
2541
- );
3102
+ const admitted = input.admittedAt !== void 0;
3103
+ const admittedColumn = admitted ? "admitted_at," : "";
3104
+ const admittedValue = admitted ? "$25," : "";
3105
+ let result;
3106
+ try {
3107
+ result = await this.sql.query(
3108
+ `INSERT INTO fabric_platform.action_invocations
3109
+ (id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
3110
+ parameters,result,correlation_id,causation_id,idempotency_key,
3111
+ parameter_digest,parameter_digest_algorithm,idempotency_actor_id,
3112
+ idempotency_authorization_binding_id,invocation_provenance,authorization_binding,execution_reason,
3113
+ authorization_binding_id,error,runtime_evidence,${admittedColumn}created_at,updated_at)
3114
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$16,$17,$18::jsonb,$19::jsonb,$20,$21,$22,$23::jsonb,${admittedValue}$24,$24)
3115
+ ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
3116
+ WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
3117
+ RETURNING *`,
3118
+ [
3119
+ input.id,
3120
+ input.tenantId,
3121
+ input.spaceId,
3122
+ input.actionId,
3123
+ input.actionVersion,
3124
+ input.actorId,
3125
+ input.actorType,
3126
+ input.status,
3127
+ JSON.stringify(input.parameters),
3128
+ JSON.stringify(input.result),
3129
+ input.correlationId,
3130
+ input.causationId ?? null,
3131
+ input.idempotencyKey ?? null,
3132
+ input.parameterDigest ?? null,
3133
+ input.parameterDigestAlgorithm ?? null,
3134
+ input.idempotencyActorId ?? null,
3135
+ input.idempotencyAuthorizationBindingId ?? null,
3136
+ input.provenance ? JSON.stringify(input.provenance) : null,
3137
+ input.authorizationBinding ? JSON.stringify(input.authorizationBinding) : null,
3138
+ input.executionReason ?? null,
3139
+ input.authorizationBindingId ?? null,
3140
+ input.error ?? null,
3141
+ input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
3142
+ now,
3143
+ ...admitted ? [input.admittedAt] : []
3144
+ ]
3145
+ );
3146
+ } catch (error) {
3147
+ if (admitted && error?.code === "42703") {
3148
+ throw new Error(
3149
+ "An admitted invocation needs the action_invocations.admitted_at column; run ensureSchema() to apply PostgreSQL migration 6 (invocation_admission) before submitting actions that declare admitSubmission.",
3150
+ { cause: error }
3151
+ );
3152
+ }
3153
+ throw error;
3154
+ }
2542
3155
  return toActionRecord(result.rows[0]);
2543
3156
  }
2544
3157
  async getActionInvocation(id, tenantId, spaceId) {
@@ -2813,8 +3426,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
2813
3426
  ON CONFLICT (id) DO NOTHING RETURNING id
2814
3427
  )
2815
3428
  INSERT INTO fabric_platform.event_outbox
2816
- (id,tenant_id,space_id,event,status,attempt_count,available_at,created_at)
2817
- SELECT $1,$2,$3,$18::jsonb,'pending',0,$14,$14 FROM inserted_event
3429
+ (id,tenant_id,space_id,event,actor_disclosure,status,attempt_count,available_at,created_at)
3430
+ SELECT $1,$2,$3,$18::jsonb,$20,'pending',0,$14,$14 FROM inserted_event
2818
3431
  ON CONFLICT (id) DO NOTHING`,
2819
3432
  [
2820
3433
  event.id,
@@ -2835,7 +3448,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
2835
3448
  event.causationId ?? null,
2836
3449
  event.provenance ? JSON.stringify(event.provenance) : null,
2837
3450
  JSON.stringify(envelope),
2838
- event.consistency ?? null
3451
+ event.consistency ?? null,
3452
+ outboxActorDisclosure(metadata)
2839
3453
  ]
2840
3454
  );
2841
3455
  }
@@ -2903,6 +3517,90 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
2903
3517
  );
2904
3518
  return result.rows.map(toOutboxRecord);
2905
3519
  }
3520
+ async getOutboxRecord(id, tenantId, spaceId) {
3521
+ const result = await this.sql.query(
3522
+ `SELECT * FROM fabric_platform.event_outbox
3523
+ WHERE id=$1 AND tenant_id=$2 AND space_id=$3`,
3524
+ [id, tenantId, spaceId]
3525
+ );
3526
+ return result.rows[0] ? toOutboxRecord(result.rows[0]) : void 0;
3527
+ }
3528
+ async requeueOutboxDeadLetter(input) {
3529
+ const result = await this.sql.query(
3530
+ `WITH prior AS (
3531
+ SELECT id, tenant_id, space_id, event->>'eventId' AS event_id,
3532
+ attempt_count, last_error, available_at, lease_token
3533
+ FROM fabric_platform.event_outbox
3534
+ WHERE id=$1 AND tenant_id=$2 AND space_id=$3 AND status='dead_letter'
3535
+ AND lease_token=$10
3536
+ FOR UPDATE
3537
+ ), requeued AS (
3538
+ UPDATE fabric_platform.event_outbox AS item
3539
+ SET status='pending', attempt_count=0, available_at=$9,
3540
+ lease_owner=NULL, lease_expires_at=NULL
3541
+ FROM prior WHERE item.id=prior.id
3542
+ RETURNING item.*
3543
+ ), evidence AS (
3544
+ INSERT INTO fabric_platform.event_outbox_requeues
3545
+ (id,outbox_id,tenant_id,space_id,event_id,actor_id,actor_type,reason,
3546
+ requeued_at,available_at,prior_attempt_count,prior_last_error,
3547
+ prior_available_at,prior_lease_token)
3548
+ SELECT $4,prior.id,prior.tenant_id,prior.space_id,prior.event_id,$5,$6,$7,
3549
+ $8,$9,prior.attempt_count,prior.last_error,prior.available_at,prior.lease_token
3550
+ FROM prior JOIN requeued ON requeued.id=prior.id
3551
+ RETURNING *
3552
+ )
3553
+ SELECT requeued.*,
3554
+ evidence.id AS requeue_id, evidence.outbox_id AS requeue_outbox_id,
3555
+ evidence.tenant_id AS requeue_tenant_id, evidence.space_id AS requeue_space_id,
3556
+ evidence.event_id AS requeue_event_id, evidence.actor_id AS requeue_actor_id,
3557
+ evidence.actor_type AS requeue_actor_type, evidence.reason AS requeue_reason,
3558
+ evidence.requeued_at AS requeue_requeued_at, evidence.available_at AS requeue_available_at,
3559
+ evidence.prior_attempt_count AS requeue_prior_attempt_count,
3560
+ evidence.prior_last_error AS requeue_prior_last_error,
3561
+ evidence.prior_available_at AS requeue_prior_available_at,
3562
+ evidence.prior_lease_token AS requeue_prior_lease_token
3563
+ FROM requeued JOIN evidence ON evidence.outbox_id=requeued.id`,
3564
+ [
3565
+ input.id,
3566
+ input.tenantId,
3567
+ input.spaceId,
3568
+ input.requeueId,
3569
+ input.actorId,
3570
+ input.actorType,
3571
+ input.reason,
3572
+ input.requeuedAt,
3573
+ input.availableAt,
3574
+ input.expectedLeaseToken
3575
+ ]
3576
+ );
3577
+ const row = result.rows[0];
3578
+ if (row) {
3579
+ return {
3580
+ applied: true,
3581
+ record: toOutboxRecord(row),
3582
+ requeue: toOutboxRequeueRecord(row, "requeue_")
3583
+ };
3584
+ }
3585
+ const current = await this.sql.query(
3586
+ `SELECT status FROM fabric_platform.event_outbox
3587
+ WHERE id=$1 AND tenant_id=$2 AND space_id=$3`,
3588
+ [input.id, input.tenantId, input.spaceId]
3589
+ );
3590
+ const found = current.rows[0];
3591
+ if (!found) return { applied: false, refusal: "not_found" };
3592
+ const status = found.status;
3593
+ return status === "dead_letter" ? { applied: false, refusal: "stale", currentStatus: status } : { applied: false, refusal: "not_dead_letter", currentStatus: status };
3594
+ }
3595
+ async listOutboxRequeues(input) {
3596
+ const result = await this.sql.query(
3597
+ `SELECT * FROM fabric_platform.event_outbox_requeues
3598
+ WHERE tenant_id=$1 AND space_id=$2 AND ($3::text IS NULL OR outbox_id=$3)
3599
+ ORDER BY requeued_at,id`,
3600
+ [input.tenantId, input.spaceId, input.outboxId ?? null]
3601
+ );
3602
+ return result.rows.map((row) => toOutboxRequeueRecord(row, ""));
3603
+ }
2906
3604
  async nextEventSequence(tenantId, spaceId) {
2907
3605
  const result = await this.sql.query(
2908
3606
  `INSERT INTO fabric_platform.event_sequences (tenant_id,space_id,next_sequence)
@@ -3175,6 +3873,7 @@ function toActionRecord(row) {
3175
3873
  ...row.mutation_footprint ? { mutationFootprint: row.mutation_footprint } : {},
3176
3874
  ...row.execution_principal ? { executionPrincipal: row.execution_principal } : {},
3177
3875
  ...row.runtime_evidence ? { runtimeEvidence: row.runtime_evidence } : {},
3876
+ ...row.admitted_at ? { admittedAt: new Date(row.admitted_at) } : {},
3178
3877
  ...row.error ? { error: String(row.error) } : {},
3179
3878
  createdAt: new Date(row.created_at),
3180
3879
  updatedAt: new Date(row.updated_at)
@@ -3243,10 +3942,13 @@ function toEventRecord(row) {
3243
3942
  }
3244
3943
  function toOutboxRecord(row) {
3245
3944
  const event = row.event;
3945
+ const actorDisclosure = row.actor_disclosure === "included" || row.actor_disclosure === "suppressed" ? row.actor_disclosure : void 0;
3946
+ const actorIncluded = actorDisclosure === "included";
3246
3947
  return {
3247
3948
  id: String(row.id),
3248
3949
  tenantId: String(row.tenant_id),
3249
3950
  spaceId: String(row.space_id),
3951
+ ...actorDisclosure ? { actorDisclosure } : {},
3250
3952
  event: {
3251
3953
  eventId: String(event.eventId),
3252
3954
  eventType: String(event.eventType),
@@ -3259,6 +3961,8 @@ function toOutboxRecord(row) {
3259
3961
  ...event.actionInvocationId ? { actionInvocationId: String(event.actionInvocationId) } : {},
3260
3962
  correlationId: String(event.correlationId),
3261
3963
  ...event.causationId ? { causationId: String(event.causationId) } : {},
3964
+ ...actorIncluded && event.actorId ? { actorId: String(event.actorId) } : {},
3965
+ ...actorIncluded && event.actorType ? { actorType: String(event.actorType) } : {},
3262
3966
  ...event.provenance ? { provenance: event.provenance } : {},
3263
3967
  ...event.consistency ? { consistency: event.consistency } : {},
3264
3968
  occurredAt: new Date(event.occurredAt),
@@ -3279,6 +3983,26 @@ function toOutboxRecord(row) {
3279
3983
  ...row.published_at ? { publishedAt: new Date(row.published_at) } : {}
3280
3984
  };
3281
3985
  }
3986
+ function toOutboxRequeueRecord(row, prefix) {
3987
+ const field = (name) => row[`${prefix}${name}`];
3988
+ const priorLastError = field("prior_last_error");
3989
+ return {
3990
+ id: String(field("id")),
3991
+ outboxId: String(field("outbox_id")),
3992
+ tenantId: String(field("tenant_id")),
3993
+ spaceId: String(field("space_id")),
3994
+ eventId: String(field("event_id")),
3995
+ actorId: String(field("actor_id")),
3996
+ actorType: String(field("actor_type")),
3997
+ reason: String(field("reason")),
3998
+ requeuedAt: new Date(field("requeued_at")),
3999
+ availableAt: new Date(field("available_at")),
4000
+ priorAttemptCount: Number(field("prior_attempt_count")),
4001
+ ...priorLastError !== null && priorLastError !== void 0 ? { priorLastError: String(priorLastError) } : {},
4002
+ priorAvailableAt: new Date(field("prior_available_at")),
4003
+ priorLeaseToken: Number(field("prior_lease_token"))
4004
+ };
4005
+ }
3282
4006
 
3283
4007
  // src/worker.ts
3284
4008
  var DEFAULT_BATCH_SIZE = 10;
@@ -3739,6 +4463,11 @@ async function runTenantIsolationChecks(subject) {
3739
4463
  return { passed: checks.every((check) => check.status !== "failed"), checks };
3740
4464
  }
3741
4465
 
4466
+ Object.defineProperty(exports, "canonicalJson", {
4467
+ enumerable: true,
4468
+ get: function () { return platform.canonicalJson; }
4469
+ });
4470
+ exports.ActionAdmissionError = ActionAdmissionError;
3742
4471
  exports.ExternalCompletionError = ExternalCompletionError;
3743
4472
  exports.IdempotencyConflictError = IdempotencyConflictError;
3744
4473
  exports.MemoryPlatformHostStore = MemoryPlatformHostStore;
@@ -3748,7 +4477,6 @@ exports.PLATFORM_HOST_HEALTH_CONTRACT_VERSION = PLATFORM_HOST_HEALTH_CONTRACT_VE
3748
4477
  exports.PLATFORM_HOST_METRIC_NAMES = PLATFORM_HOST_METRIC_NAMES;
3749
4478
  exports.PostgresPlatformHostStore = PostgresPlatformHostStore;
3750
4479
  exports.applyPostgresMigrations = applyPostgresMigrations;
3751
- exports.canonicalJson = canonicalJson;
3752
4480
  exports.cloneOutboxRecord = cloneOutboxRecord;
3753
4481
  exports.createDurableSagaParentLifecycle = createDurableSagaParentLifecycle;
3754
4482
  exports.createGovernedActionHost = createGovernedActionHost;
@@ -3756,6 +4484,7 @@ exports.createStoreBackedActionDispatcher = createStoreBackedActionDispatcher;
3756
4484
  exports.digestParameters = digestParameters;
3757
4485
  exports.emitPlatformHostTelemetry = emitPlatformHostTelemetry;
3758
4486
  exports.getPlatformHostHealthSnapshot = getPlatformHostHealthSnapshot;
4487
+ exports.outboxActorDisclosure = outboxActorDisclosure;
3759
4488
  exports.runOutboxRelayCycle = runOutboxRelayCycle;
3760
4489
  exports.runPlatformActionWorker = runPlatformActionWorker;
3761
4490
  exports.runPlatformActionWorkerCycle = runPlatformActionWorkerCycle;