@fabricorg/platform-host 7.2.2 → 7.3.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 +54 -0
- package/README.md +238 -3
- package/dist/index.cjs +806 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +298 -12
- package/dist/index.d.ts +298 -12
- package/dist/index.js +805 -95
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AdapterRegistry, createFabricId, assertGovernanceRuntimeEvidence, FABRIC_GOVERNANCE_CONTRACT_VERSION, assertMutationGovernanceContext, evaluatePolicyDefinitions, aggregatePolicyOutcomes, evaluateStateMachineTransition, executeWithAdapterRetry, assertAdapterOutcomeConsistency } from '@fabricorg/platform';
|
|
2
|
+
import { performance } from 'perf_hooks';
|
|
2
3
|
import { assertAssemblyRuntimeCompatible } from '@fabricorg/assembly';
|
|
3
4
|
import { createHash } from 'crypto';
|
|
5
|
+
import { types } from 'util';
|
|
4
6
|
|
|
5
7
|
// src/host.ts
|
|
6
8
|
|
|
@@ -15,6 +17,19 @@ var IdempotencyConflictError = class extends Error {
|
|
|
15
17
|
conflict;
|
|
16
18
|
code = "IDEMPOTENCY_CONFLICT";
|
|
17
19
|
};
|
|
20
|
+
var ActionAdmissionError = class extends Error {
|
|
21
|
+
constructor(actionId, refusal, refusalCode) {
|
|
22
|
+
super(`Action ${actionId} submission was not admitted (${refusal}${refusalCode ? `: ${refusalCode}` : ""}); nothing was recorded.`);
|
|
23
|
+
this.actionId = actionId;
|
|
24
|
+
this.refusal = refusal;
|
|
25
|
+
this.refusalCode = refusalCode;
|
|
26
|
+
this.name = "ActionAdmissionError";
|
|
27
|
+
}
|
|
28
|
+
actionId;
|
|
29
|
+
refusal;
|
|
30
|
+
refusalCode;
|
|
31
|
+
code = "ACTION_ADMISSION_REFUSED";
|
|
32
|
+
};
|
|
18
33
|
var ExternalCompletionError = class extends Error {
|
|
19
34
|
constructor(refusal, message) {
|
|
20
35
|
super(message);
|
|
@@ -82,6 +97,7 @@ var PLATFORM_HOST_METRIC_NAMES = {
|
|
|
82
97
|
workerCyclesCompleted: "fabric.platform.worker.cycles.completed",
|
|
83
98
|
workerCyclesFailed: "fabric.platform.worker.cycles.failed",
|
|
84
99
|
invocationSubmitted: "fabric.platform.invocation.submitted",
|
|
100
|
+
invocationAdmissionRefused: "fabric.platform.invocation.admission_refused",
|
|
85
101
|
invocationExecutionStarted: "fabric.platform.invocation.execution_started",
|
|
86
102
|
invocationLeaseClaimed: "fabric.platform.invocation.lease_claimed",
|
|
87
103
|
invocationCompleted: "fabric.platform.invocation.completed",
|
|
@@ -97,6 +113,8 @@ var PLATFORM_HOST_METRIC_NAMES = {
|
|
|
97
113
|
outboxPublished: "fabric.platform.outbox.published",
|
|
98
114
|
outboxFailed: "fabric.platform.outbox.failed",
|
|
99
115
|
outboxDeadLettered: "fabric.platform.outbox.dead_lettered",
|
|
116
|
+
outboxRequeued: "fabric.platform.outbox.requeued",
|
|
117
|
+
outboxRequeueRefused: "fabric.platform.outbox.requeue_refused",
|
|
100
118
|
invocationBacklog: "fabric.platform.invocation.backlog",
|
|
101
119
|
invocationRunning: "fabric.platform.invocation.running",
|
|
102
120
|
invocationExpiredLeases: "fabric.platform.invocation.expired_leases",
|
|
@@ -496,6 +514,340 @@ function createExternalCompletionLifecycle(deps) {
|
|
|
496
514
|
}
|
|
497
515
|
return { completeExternalInvocation, reconcileOverdueCompletions, recordCompletionReconciliation };
|
|
498
516
|
}
|
|
517
|
+
var SubmissionUnreadableError = class extends Error {
|
|
518
|
+
name = "SubmissionUnreadableError";
|
|
519
|
+
constructor() {
|
|
520
|
+
super("Platform Host submission could not be read as plain data; nothing was recorded.");
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
var NotJsonData = class extends Error {
|
|
524
|
+
};
|
|
525
|
+
function copyJsonData(value) {
|
|
526
|
+
try {
|
|
527
|
+
return { ok: true, value: copy(value, /* @__PURE__ */ new Set()) };
|
|
528
|
+
} catch {
|
|
529
|
+
return { ok: false };
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
function copy(value, ancestors) {
|
|
533
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
534
|
+
if (typeof value === "number") {
|
|
535
|
+
if (!Number.isFinite(value)) throw new NotJsonData();
|
|
536
|
+
return value === 0 ? 0 : value;
|
|
537
|
+
}
|
|
538
|
+
if (typeof value !== "object") throw new NotJsonData();
|
|
539
|
+
if (types.isProxy(value)) throw new NotJsonData();
|
|
540
|
+
if (ancestors.has(value)) throw new NotJsonData();
|
|
541
|
+
const prototype = Reflect.getPrototypeOf(value);
|
|
542
|
+
ancestors.add(value);
|
|
543
|
+
try {
|
|
544
|
+
if (Array.isArray(value)) {
|
|
545
|
+
if (prototype !== Array.prototype) throw new NotJsonData();
|
|
546
|
+
const length = value.length;
|
|
547
|
+
const out2 = [];
|
|
548
|
+
for (let index = 0; index < length; index += 1) {
|
|
549
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, String(index));
|
|
550
|
+
if (descriptor === void 0) throw new NotJsonData();
|
|
551
|
+
const item = readOnce(value, descriptor);
|
|
552
|
+
if (item === void 0) throw new NotJsonData();
|
|
553
|
+
out2.push(copy(item, ancestors));
|
|
554
|
+
}
|
|
555
|
+
return out2;
|
|
556
|
+
}
|
|
557
|
+
if (prototype !== Object.prototype && prototype !== null) throw new NotJsonData();
|
|
558
|
+
const out = {};
|
|
559
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
560
|
+
if (typeof key !== "string") continue;
|
|
561
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
|
|
562
|
+
if (descriptor === void 0 || descriptor.enumerable !== true) continue;
|
|
563
|
+
const item = readOnce(value, descriptor);
|
|
564
|
+
if (item === void 0) continue;
|
|
565
|
+
Reflect.defineProperty(out, key, { value: copy(item, ancestors), enumerable: true, writable: true, configurable: true });
|
|
566
|
+
}
|
|
567
|
+
return out;
|
|
568
|
+
} finally {
|
|
569
|
+
ancestors.delete(value);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function readOnce(target, descriptor) {
|
|
573
|
+
if ("value" in descriptor) return descriptor.value;
|
|
574
|
+
return descriptor.get ? Reflect.apply(descriptor.get, target, []) : void 0;
|
|
575
|
+
}
|
|
576
|
+
function snapshotSubmission(input) {
|
|
577
|
+
if (input === null || typeof input !== "object") throw new SubmissionUnreadableError();
|
|
578
|
+
const scalar = (key) => primitiveOrAbsent(readField(input, key));
|
|
579
|
+
const actionId = scalar("actionId");
|
|
580
|
+
const tenantId = scalar("tenantId");
|
|
581
|
+
const spaceId = scalar("spaceId");
|
|
582
|
+
const actorId = scalar("actorId");
|
|
583
|
+
const actorType = scalar("actorType");
|
|
584
|
+
const correlationId = scalar("correlationId");
|
|
585
|
+
const causationId = scalar("causationId");
|
|
586
|
+
const executionReason = scalar("executionReason");
|
|
587
|
+
const idempotencyKey = scalar("idempotencyKey");
|
|
588
|
+
const authorizationBindingId = scalar("authorizationBindingId");
|
|
589
|
+
const scalars = {
|
|
590
|
+
actionId,
|
|
591
|
+
tenantId,
|
|
592
|
+
spaceId,
|
|
593
|
+
actorId,
|
|
594
|
+
actorType,
|
|
595
|
+
...correlationId != null ? { correlationId } : {},
|
|
596
|
+
...causationId != null ? { causationId } : {},
|
|
597
|
+
...executionReason != null ? { executionReason } : {},
|
|
598
|
+
...idempotencyKey != null ? { idempotencyKey } : {},
|
|
599
|
+
...authorizationBindingId != null ? { authorizationBindingId } : {}
|
|
600
|
+
};
|
|
601
|
+
const live = { ...scalars, parametersArePrivate: false };
|
|
602
|
+
Object.defineProperties(live, {
|
|
603
|
+
parameters: { get: () => input.parameters, enumerable: true },
|
|
604
|
+
provenance: { get: () => input.provenance, enumerable: true },
|
|
605
|
+
authorizationBinding: { get: () => input.authorizationBinding, enumerable: true }
|
|
606
|
+
});
|
|
607
|
+
return Object.freeze(live);
|
|
608
|
+
}
|
|
609
|
+
var PROVENANCE_SCALARS = ["source", "correlationId", "causationId"];
|
|
610
|
+
var PROVENANCE_ATTRIBUTES = ["traceAttributes", "auditAttributes"];
|
|
611
|
+
var BINDING_FIELDS = ["id", "tenantId", "actorId", "actionId", "parameterDigest", "resourceScope", "capturedAt", "expiresAt", "governingMoment"];
|
|
612
|
+
function privateSubmission(snapshot) {
|
|
613
|
+
const parameters = readField(snapshot, "parameters");
|
|
614
|
+
const provenanceRead = readField(snapshot, "provenance");
|
|
615
|
+
const bindingRead = readField(snapshot, "authorizationBinding");
|
|
616
|
+
const copied = copyJsonData(parameters);
|
|
617
|
+
const provenance = provenanceRead ? copyProvenance(provenanceRead) : void 0;
|
|
618
|
+
const authorizationBinding = bindingRead ? copyBinding(bindingRead) : void 0;
|
|
619
|
+
return Object.freeze({
|
|
620
|
+
actionId: snapshot.actionId,
|
|
621
|
+
tenantId: snapshot.tenantId,
|
|
622
|
+
spaceId: snapshot.spaceId,
|
|
623
|
+
actorId: snapshot.actorId,
|
|
624
|
+
actorType: snapshot.actorType,
|
|
625
|
+
...snapshot.correlationId !== void 0 ? { correlationId: snapshot.correlationId } : {},
|
|
626
|
+
...snapshot.causationId !== void 0 ? { causationId: snapshot.causationId } : {},
|
|
627
|
+
...snapshot.executionReason !== void 0 ? { executionReason: snapshot.executionReason } : {},
|
|
628
|
+
...snapshot.idempotencyKey !== void 0 ? { idempotencyKey: snapshot.idempotencyKey } : {},
|
|
629
|
+
...snapshot.authorizationBindingId !== void 0 ? { authorizationBindingId: snapshot.authorizationBindingId } : {},
|
|
630
|
+
...provenance ? { provenance } : {},
|
|
631
|
+
...authorizationBinding ? { authorizationBinding } : {},
|
|
632
|
+
parameters: copied.ok ? copied.value : parameters,
|
|
633
|
+
parametersArePrivate: copied.ok
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
function readField(target, key) {
|
|
637
|
+
try {
|
|
638
|
+
return target[key];
|
|
639
|
+
} catch {
|
|
640
|
+
throw new SubmissionUnreadableError();
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function primitiveOrAbsent(value) {
|
|
644
|
+
if (value !== null && (typeof value === "object" || typeof value === "function" || typeof value === "symbol")) {
|
|
645
|
+
throw new SubmissionUnreadableError();
|
|
646
|
+
}
|
|
647
|
+
return value;
|
|
648
|
+
}
|
|
649
|
+
function objectOf(value) {
|
|
650
|
+
if (value === null || typeof value !== "object") throw new SubmissionUnreadableError();
|
|
651
|
+
return value;
|
|
652
|
+
}
|
|
653
|
+
function copyFields(source, fields) {
|
|
654
|
+
const out = {};
|
|
655
|
+
for (const field of fields) {
|
|
656
|
+
const value = primitiveOrAbsent(readField(source, field));
|
|
657
|
+
if (value !== void 0) out[field] = value;
|
|
658
|
+
}
|
|
659
|
+
return out;
|
|
660
|
+
}
|
|
661
|
+
function copyAttributes(source, field) {
|
|
662
|
+
const value = readField(source, field);
|
|
663
|
+
if (value === void 0 || value === null) return void 0;
|
|
664
|
+
const map = objectOf(value);
|
|
665
|
+
let entries;
|
|
666
|
+
try {
|
|
667
|
+
entries = Object.entries(map);
|
|
668
|
+
} catch {
|
|
669
|
+
throw new SubmissionUnreadableError();
|
|
670
|
+
}
|
|
671
|
+
const out = {};
|
|
672
|
+
for (const [key, item] of entries) {
|
|
673
|
+
Reflect.defineProperty(out, key, { value: primitiveOrAbsent(item), enumerable: true, writable: true, configurable: true });
|
|
674
|
+
}
|
|
675
|
+
return out;
|
|
676
|
+
}
|
|
677
|
+
function copyProvenance(value) {
|
|
678
|
+
const source = objectOf(value);
|
|
679
|
+
const out = copyFields(source, PROVENANCE_SCALARS);
|
|
680
|
+
for (const field of PROVENANCE_ATTRIBUTES) {
|
|
681
|
+
const attributes = copyAttributes(source, field);
|
|
682
|
+
if (attributes !== void 0) out[field] = attributes;
|
|
683
|
+
}
|
|
684
|
+
return out;
|
|
685
|
+
}
|
|
686
|
+
function copyBinding(value) {
|
|
687
|
+
return copyFields(objectOf(value), BINDING_FIELDS);
|
|
688
|
+
}
|
|
689
|
+
function parametersCopy(snapshot) {
|
|
690
|
+
if (!snapshot.parametersArePrivate) throw new SubmissionUnreadableError();
|
|
691
|
+
const copied = copyJsonData(snapshot.parameters);
|
|
692
|
+
if (!copied.ok) throw new SubmissionUnreadableError();
|
|
693
|
+
return copied.value;
|
|
694
|
+
}
|
|
695
|
+
function liveSubmissionView(snapshot, input) {
|
|
696
|
+
const view = {
|
|
697
|
+
actionId: snapshot.actionId,
|
|
698
|
+
tenantId: snapshot.tenantId,
|
|
699
|
+
spaceId: snapshot.spaceId,
|
|
700
|
+
actorId: snapshot.actorId,
|
|
701
|
+
actorType: snapshot.actorType,
|
|
702
|
+
...snapshot.correlationId !== void 0 ? { correlationId: snapshot.correlationId } : {},
|
|
703
|
+
...snapshot.causationId !== void 0 ? { causationId: snapshot.causationId } : {},
|
|
704
|
+
...snapshot.executionReason !== void 0 ? { executionReason: snapshot.executionReason } : {},
|
|
705
|
+
...snapshot.idempotencyKey !== void 0 ? { idempotencyKey: snapshot.idempotencyKey } : {},
|
|
706
|
+
...snapshot.authorizationBindingId !== void 0 ? { authorizationBindingId: snapshot.authorizationBindingId } : {}
|
|
707
|
+
};
|
|
708
|
+
const caller = input;
|
|
709
|
+
for (const field of ["parameters", "provenance", "authorizationBinding"]) {
|
|
710
|
+
Object.defineProperty(view, field, {
|
|
711
|
+
get: () => caller[field],
|
|
712
|
+
set: (value) => {
|
|
713
|
+
caller[field] = value;
|
|
714
|
+
},
|
|
715
|
+
enumerable: true,
|
|
716
|
+
configurable: true
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
return view;
|
|
720
|
+
}
|
|
721
|
+
function submissionView(snapshot) {
|
|
722
|
+
const fresh = (value) => {
|
|
723
|
+
if (value === void 0) return void 0;
|
|
724
|
+
const copied = copyJsonData(value);
|
|
725
|
+
if (!copied.ok) throw new SubmissionUnreadableError();
|
|
726
|
+
return copied.value;
|
|
727
|
+
};
|
|
728
|
+
const provenance = fresh(snapshot.provenance);
|
|
729
|
+
const authorizationBinding = fresh(snapshot.authorizationBinding);
|
|
730
|
+
return {
|
|
731
|
+
actionId: snapshot.actionId,
|
|
732
|
+
parameters: parametersCopy(snapshot),
|
|
733
|
+
tenantId: snapshot.tenantId,
|
|
734
|
+
spaceId: snapshot.spaceId,
|
|
735
|
+
actorId: snapshot.actorId,
|
|
736
|
+
actorType: snapshot.actorType,
|
|
737
|
+
...snapshot.correlationId !== void 0 ? { correlationId: snapshot.correlationId } : {},
|
|
738
|
+
...snapshot.causationId !== void 0 ? { causationId: snapshot.causationId } : {},
|
|
739
|
+
...provenance ? { provenance } : {},
|
|
740
|
+
...snapshot.executionReason !== void 0 ? { executionReason: snapshot.executionReason } : {},
|
|
741
|
+
...snapshot.idempotencyKey !== void 0 ? { idempotencyKey: snapshot.idempotencyKey } : {},
|
|
742
|
+
...snapshot.authorizationBindingId !== void 0 ? { authorizationBindingId: snapshot.authorizationBindingId } : {},
|
|
743
|
+
...authorizationBinding ? { authorizationBinding } : {}
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
var MAX_REASON_LENGTH = 1e3;
|
|
747
|
+
function asOutboxRequeueStore(store) {
|
|
748
|
+
const candidate = store;
|
|
749
|
+
return typeof candidate.requeueOutboxDeadLetter === "function" && typeof candidate.getOutboxRecord === "function" && typeof candidate.listOutboxRequeues === "function" ? store : void 0;
|
|
750
|
+
}
|
|
751
|
+
function createOutboxRequeue(deps) {
|
|
752
|
+
return async function requeueOutboxDeadLetter(input) {
|
|
753
|
+
for (const field of ["outboxId", "tenantId", "spaceId", "actorId", "actorType"]) {
|
|
754
|
+
if (typeof input[field] !== "string" || input[field].trim() === "") {
|
|
755
|
+
throw new Error(`Outbox requeue requires ${field}.`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
if (typeof input.reason !== "string" || input.reason.trim() === "") {
|
|
759
|
+
throw new Error("Outbox requeue requires a reason.");
|
|
760
|
+
}
|
|
761
|
+
if (input.reason.length > MAX_REASON_LENGTH) {
|
|
762
|
+
throw new Error(`Outbox requeue reason must be at most ${MAX_REASON_LENGTH} characters.`);
|
|
763
|
+
}
|
|
764
|
+
if (input.availableAt !== void 0 && !(input.availableAt instanceof Date && Number.isFinite(input.availableAt.getTime()))) {
|
|
765
|
+
throw new Error("Outbox requeue availableAt must be a valid Date.");
|
|
766
|
+
}
|
|
767
|
+
const store = asOutboxRequeueStore(deps.store);
|
|
768
|
+
if (!store) throw new Error("Outbox requeue requires a store with outbox requeue support.");
|
|
769
|
+
const refuse = (refusal, message, currentStatus) => {
|
|
770
|
+
emitPlatformHostTelemetry(deps.telemetry, {
|
|
771
|
+
kind: "event",
|
|
772
|
+
name: "outbox.requeue_refused",
|
|
773
|
+
metricName: PLATFORM_HOST_METRIC_NAMES.outboxRequeueRefused,
|
|
774
|
+
occurredAt: deps.now(),
|
|
775
|
+
tenantId: input.tenantId,
|
|
776
|
+
spaceId: input.spaceId,
|
|
777
|
+
attributes: { refusal }
|
|
778
|
+
});
|
|
779
|
+
return { status: "refused", refusal, message, ...currentStatus ? { currentStatus } : {} };
|
|
780
|
+
};
|
|
781
|
+
const record = await store.getOutboxRecord(input.outboxId, input.tenantId, input.spaceId);
|
|
782
|
+
const authorization = deps.authorization;
|
|
783
|
+
const hasAuthorizer = typeof authorization.authorizeOutboxRequeue === "function";
|
|
784
|
+
const authorized = hasAuthorizer ? await authorization.authorizeOutboxRequeue({
|
|
785
|
+
tenantId: input.tenantId,
|
|
786
|
+
spaceId: input.spaceId,
|
|
787
|
+
actorId: input.actorId,
|
|
788
|
+
actorType: input.actorType,
|
|
789
|
+
outboxId: input.outboxId,
|
|
790
|
+
reason: input.reason,
|
|
791
|
+
...record ? {
|
|
792
|
+
record: {
|
|
793
|
+
eventId: record.event.eventId,
|
|
794
|
+
eventType: record.event.eventType,
|
|
795
|
+
payloadClassification: record.event.payloadClassification,
|
|
796
|
+
status: record.status,
|
|
797
|
+
attemptCount: record.attemptCount
|
|
798
|
+
}
|
|
799
|
+
} : {}
|
|
800
|
+
}) : false;
|
|
801
|
+
if (authorized !== true) {
|
|
802
|
+
return refuse(
|
|
803
|
+
"not_authorized",
|
|
804
|
+
hasAuthorizer ? `Actor ${input.actorId} is not authorized to requeue outbox records in this scope.` : "Outbox requeue is refused because no authorizeOutboxRequeue is configured."
|
|
805
|
+
);
|
|
806
|
+
}
|
|
807
|
+
if (!record) return refuse("not_found", `Outbox record not found: ${input.outboxId}`);
|
|
808
|
+
if (record.status !== "dead_letter") {
|
|
809
|
+
return refuse("not_dead_letter", `Outbox record ${input.outboxId} is ${record.status}, not dead_letter.`, record.status);
|
|
810
|
+
}
|
|
811
|
+
const requeuedAt = deps.now();
|
|
812
|
+
const result = await store.requeueOutboxDeadLetter({
|
|
813
|
+
id: input.outboxId,
|
|
814
|
+
tenantId: input.tenantId,
|
|
815
|
+
spaceId: input.spaceId,
|
|
816
|
+
requeueId: createFabricId("orq"),
|
|
817
|
+
actorId: input.actorId,
|
|
818
|
+
actorType: input.actorType,
|
|
819
|
+
reason: input.reason,
|
|
820
|
+
requeuedAt,
|
|
821
|
+
availableAt: input.availableAt ?? requeuedAt,
|
|
822
|
+
expectedLeaseToken: record.leaseToken ?? 0
|
|
823
|
+
});
|
|
824
|
+
if (!result.applied) {
|
|
825
|
+
if (result.refusal === "not_found") return refuse("not_found", `Outbox record not found: ${input.outboxId}`);
|
|
826
|
+
if (result.refusal === "stale") {
|
|
827
|
+
return refuse(
|
|
828
|
+
"stale",
|
|
829
|
+
`Outbox record ${input.outboxId} changed after it was authorized; read and authorize it again.`,
|
|
830
|
+
result.currentStatus
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
return refuse(
|
|
834
|
+
"not_dead_letter",
|
|
835
|
+
`Outbox record ${input.outboxId} is ${result.currentStatus ?? "no longer dead_letter"}, not dead_letter.`,
|
|
836
|
+
result.currentStatus
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
emitPlatformHostTelemetry(deps.telemetry, {
|
|
840
|
+
kind: "event",
|
|
841
|
+
name: "outbox.requeued",
|
|
842
|
+
metricName: PLATFORM_HOST_METRIC_NAMES.outboxRequeued,
|
|
843
|
+
occurredAt: requeuedAt,
|
|
844
|
+
tenantId: input.tenantId,
|
|
845
|
+
spaceId: input.spaceId,
|
|
846
|
+
attributes: { priorAttempt: result.requeue.priorAttemptCount }
|
|
847
|
+
});
|
|
848
|
+
return { status: "requeued", record: result.record, requeue: result.requeue };
|
|
849
|
+
};
|
|
850
|
+
}
|
|
499
851
|
|
|
500
852
|
// src/host.ts
|
|
501
853
|
var DEFAULT_EXTRACT_EVENTS = (data) => {
|
|
@@ -505,6 +857,12 @@ var DEFAULT_EXTRACT_EVENTS = (data) => {
|
|
|
505
857
|
var RecoverableFinalizationError = class extends Error {
|
|
506
858
|
name = "RecoverableFinalizationError";
|
|
507
859
|
};
|
|
860
|
+
var DEFAULT_ADMISSION_TIMEOUT_MS = 1e4;
|
|
861
|
+
var MAX_TIMER_MS = 2147483647;
|
|
862
|
+
var monotonicNow = performance.now.bind(performance);
|
|
863
|
+
var ADMISSION_FAILED = Object.freeze({ kind: "failed" });
|
|
864
|
+
var ADMISSION_TIMED_OUT = Object.freeze({ kind: "timed_out" });
|
|
865
|
+
var ADMISSION_REFUSAL_CODE = /^[a-z][A-Za-z0-9_.:-]{0,127}$/;
|
|
508
866
|
function createGovernedActionHost(options) {
|
|
509
867
|
if (options.composition) {
|
|
510
868
|
if (!options.registry?.orderedModules) {
|
|
@@ -534,37 +892,51 @@ function createGovernedActionHost(options) {
|
|
|
534
892
|
const actionResolver = configuredActionResolver;
|
|
535
893
|
const stateMachineResolver = options.registry?.resolveStateMachine ?? (() => void 0);
|
|
536
894
|
const eventResultFields = options.eventResultFields ?? ["_events"];
|
|
895
|
+
const admissionTimeoutMs = options.admissionTimeoutMs ?? DEFAULT_ADMISSION_TIMEOUT_MS;
|
|
896
|
+
if (typeof admissionTimeoutMs !== "number" || !Number.isInteger(admissionTimeoutMs) || admissionTimeoutMs < 1 || admissionTimeoutMs > MAX_TIMER_MS) {
|
|
897
|
+
throw new Error(`admissionTimeoutMs must be a whole number of milliseconds from 1 to ${MAX_TIMER_MS}.`);
|
|
898
|
+
}
|
|
537
899
|
async function submitAction(input) {
|
|
538
|
-
|
|
539
|
-
|
|
900
|
+
const read = snapshotSubmission(input);
|
|
901
|
+
if (read.executionReason && !["initial", "offline_replay"].includes(read.executionReason)) {
|
|
902
|
+
throw new Error(`Unsupported submitted execution reason: ${read.executionReason}`);
|
|
540
903
|
}
|
|
541
|
-
const action = actionResolver(
|
|
542
|
-
if (!action) throw new Error(`Unknown action: ${
|
|
543
|
-
|
|
544
|
-
|
|
904
|
+
const action = actionResolver(read.actionId);
|
|
905
|
+
if (!action) throw new Error(`Unknown action: ${read.actionId}`);
|
|
906
|
+
const declaresAdmission = action.admitSubmission !== void 0;
|
|
907
|
+
const submission = declaresAdmission ? privateSubmission(read) : read;
|
|
908
|
+
if (action.execution?.connectivity === "online-required" && submission.executionReason === "offline_replay") {
|
|
909
|
+
throw new Error(`Action ${submission.actionId} is online-required and cannot be submitted as an offline replay.`);
|
|
545
910
|
}
|
|
546
|
-
if (action.execution?.sensitivity === "restricted" && Object.keys(
|
|
547
|
-
throw new Error(`Action ${
|
|
911
|
+
if (action.execution?.sensitivity === "restricted" && Object.keys(submission.provenance?.auditAttributes ?? {}).length > 0) {
|
|
912
|
+
throw new Error(`Action ${submission.actionId} declares restricted sensitivity; provenance audit attributes must not be durably recorded.`);
|
|
548
913
|
}
|
|
549
|
-
const authorizationInput = toAuthorizationInput(action,
|
|
914
|
+
const authorizationInput = toAuthorizationInput(action, submission);
|
|
550
915
|
if (!await options.authorization.checkEntitlement(authorizationInput)) {
|
|
551
|
-
throw new Error(`Module "${action.namespace}" is not enabled for tenant ${
|
|
916
|
+
throw new Error(`Module "${action.namespace}" is not enabled for tenant ${submission.tenantId}`);
|
|
552
917
|
}
|
|
553
918
|
if (!await options.authorization.authorize(authorizationInput)) {
|
|
554
|
-
throw new Error(`Actor ${
|
|
919
|
+
throw new Error(`Actor ${submission.actorId} is not authorized for action ${submission.actionId}`);
|
|
920
|
+
}
|
|
921
|
+
if (declaresAdmission && (!submission.parametersArePrivate || !isRecord(submission.parameters))) {
|
|
922
|
+
refuseAdmission(action, submission, "invalid_parameters");
|
|
555
923
|
}
|
|
556
924
|
const actionInvocationId = createFabricId("act");
|
|
557
|
-
if (
|
|
558
|
-
if (
|
|
559
|
-
const correlationId =
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
925
|
+
if (submission.correlationId && submission.provenance?.correlationId && submission.correlationId !== submission.provenance.correlationId) throw new Error("Invocation provenance correlationId does not match submission correlationId.");
|
|
926
|
+
if (submission.causationId && submission.provenance?.causationId && submission.causationId !== submission.provenance.causationId) throw new Error("Invocation provenance causationId does not match submission causationId.");
|
|
927
|
+
const correlationId = submission.correlationId ?? submission.provenance?.correlationId ?? createFabricId("corr");
|
|
928
|
+
const prepare = () => {
|
|
929
|
+
const durableParameters2 = options.redactActionParameters ? options.redactActionParameters(submission.actionId, submission.parameters) : submission.parameters;
|
|
930
|
+
const parameterDigest2 = digestParameters(submission.parameters);
|
|
931
|
+
if (submission.authorizationBinding) validateAuthorizationBinding(submission, parameterDigest2, action.execution?.authorityMoment, now());
|
|
932
|
+
return { durableParameters: durableParameters2, parameterDigest: parameterDigest2 };
|
|
933
|
+
};
|
|
934
|
+
const early = declaresAdmission ? void 0 : prepare();
|
|
563
935
|
const durableProvenance = normalizeProvenance(
|
|
564
|
-
|
|
936
|
+
submission.provenance,
|
|
565
937
|
options.provenance
|
|
566
938
|
);
|
|
567
|
-
const initiatingReleaseDigest = await options.composition?.resolveInitiatingReleaseDigest?.(input);
|
|
939
|
+
const initiatingReleaseDigest = await options.composition?.resolveInitiatingReleaseDigest?.(declaresAdmission ? submissionView(submission) : liveSubmissionView(submission, input));
|
|
568
940
|
const runtimeEvidence = {
|
|
569
941
|
governanceContractVersion: FABRIC_GOVERNANCE_CONTRACT_VERSION,
|
|
570
942
|
hostContractVersion: PLATFORM_HOST_CONTRACT_VERSION,
|
|
@@ -573,48 +945,51 @@ function createGovernedActionHost(options) {
|
|
|
573
945
|
...initiatingReleaseDigest ? { initiatingReleaseDigest } : {}
|
|
574
946
|
};
|
|
575
947
|
assertGovernanceRuntimeEvidence(runtimeEvidence);
|
|
948
|
+
const admission = declaresAdmission ? await admitSubmission(action, submission) : void 0;
|
|
949
|
+
const { durableParameters, parameterDigest } = early ?? prepare();
|
|
576
950
|
const durableInvocation = await options.store.createActionInvocation({
|
|
577
951
|
id: actionInvocationId,
|
|
578
|
-
tenantId:
|
|
579
|
-
spaceId:
|
|
580
|
-
actionId:
|
|
952
|
+
tenantId: submission.tenantId,
|
|
953
|
+
spaceId: submission.spaceId,
|
|
954
|
+
actionId: submission.actionId,
|
|
581
955
|
actionVersion: action.version,
|
|
582
|
-
actorId:
|
|
583
|
-
actorType:
|
|
956
|
+
actorId: submission.actorId,
|
|
957
|
+
actorType: submission.actorType,
|
|
584
958
|
status: "pending",
|
|
585
959
|
parameters: durableParameters,
|
|
586
960
|
result: {},
|
|
587
961
|
runtimeEvidence,
|
|
588
962
|
correlationId,
|
|
589
|
-
...
|
|
590
|
-
...
|
|
591
|
-
...
|
|
963
|
+
...submission.causationId ?? submission.provenance?.causationId ? { causationId: submission.causationId ?? submission.provenance?.causationId } : {},
|
|
964
|
+
...submission.idempotencyKey ? { idempotencyKey: submission.idempotencyKey } : {},
|
|
965
|
+
...submission.idempotencyKey ? {
|
|
592
966
|
parameterDigest,
|
|
593
967
|
parameterDigestAlgorithm: PARAMETER_DIGEST_ALGORITHM,
|
|
594
|
-
idempotencyActorId:
|
|
595
|
-
...
|
|
968
|
+
idempotencyActorId: submission.actorId,
|
|
969
|
+
...submission.authorizationBindingId ? { idempotencyAuthorizationBindingId: submission.authorizationBindingId } : {}
|
|
596
970
|
} : {},
|
|
597
971
|
...durableProvenance ? { provenance: durableProvenance } : {},
|
|
598
|
-
...
|
|
599
|
-
...
|
|
600
|
-
...
|
|
972
|
+
...submission.authorizationBinding ? { authorizationBinding: submission.authorizationBinding } : {},
|
|
973
|
+
...submission.executionReason ? { executionReason: submission.executionReason } : {},
|
|
974
|
+
...submission.authorizationBindingId ? { authorizationBindingId: submission.authorizationBindingId } : {},
|
|
975
|
+
...admission ? { admittedAt: admission.admittedAt } : {}
|
|
601
976
|
});
|
|
602
977
|
emitPlatformHostTelemetry(options.telemetry, {
|
|
603
978
|
kind: "event",
|
|
604
979
|
name: "invocation.submitted",
|
|
605
980
|
metricName: PLATFORM_HOST_METRIC_NAMES.invocationSubmitted,
|
|
606
981
|
occurredAt: now(),
|
|
607
|
-
tenantId:
|
|
608
|
-
spaceId:
|
|
982
|
+
tenantId: submission.tenantId,
|
|
983
|
+
spaceId: submission.spaceId,
|
|
609
984
|
attributes: { actionVersion: action.version }
|
|
610
985
|
});
|
|
611
|
-
if (durableInvocation.id !== actionInvocationId &&
|
|
986
|
+
if (durableInvocation.id !== actionInvocationId && submission.idempotencyKey) {
|
|
612
987
|
const conflict = idempotencyConflict(durableInvocation, {
|
|
613
|
-
actorId:
|
|
614
|
-
authorizationBindingId:
|
|
988
|
+
actorId: submission.actorId,
|
|
989
|
+
authorizationBindingId: submission.authorizationBindingId,
|
|
615
990
|
actionVersion: action.version,
|
|
616
991
|
parameterDigest,
|
|
617
|
-
idempotencyKey:
|
|
992
|
+
idempotencyKey: submission.idempotencyKey
|
|
618
993
|
});
|
|
619
994
|
if (!durableInvocation.parameterDigest) {
|
|
620
995
|
options.idempotency?.onLegacyRecord?.(durableInvocation);
|
|
@@ -634,8 +1009,8 @@ function createGovernedActionHost(options) {
|
|
|
634
1009
|
const dispatched = await options.dispatcher.dispatch({
|
|
635
1010
|
actionInvocationId: durableInvocation.id,
|
|
636
1011
|
actionId: durableInvocation.actionId,
|
|
637
|
-
tenantId:
|
|
638
|
-
spaceId:
|
|
1012
|
+
tenantId: submission.tenantId,
|
|
1013
|
+
spaceId: submission.spaceId,
|
|
639
1014
|
workflowId: durableWorkflowId
|
|
640
1015
|
});
|
|
641
1016
|
return withConsistency({
|
|
@@ -645,7 +1020,7 @@ function createGovernedActionHost(options) {
|
|
|
645
1020
|
...dispatched.runId ? { runId: dispatched.runId } : {},
|
|
646
1021
|
...durableInvocation.hitlRoute ? { hitlRoute: durableInvocation.hitlRoute } : {},
|
|
647
1022
|
...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {}
|
|
648
|
-
},
|
|
1023
|
+
}, submission.actionId);
|
|
649
1024
|
}
|
|
650
1025
|
return withConsistency({
|
|
651
1026
|
actionInvocationId: durableInvocation.id,
|
|
@@ -658,15 +1033,15 @@ function createGovernedActionHost(options) {
|
|
|
658
1033
|
...durableInvocation.authorizationReconciliation ? { reconciliation: durableInvocation.authorizationReconciliation } : {},
|
|
659
1034
|
...durableInvocation.adapterReconciliation ? { adapterReconciliation: durableInvocation.adapterReconciliation } : {},
|
|
660
1035
|
...durableInvocation.pendingCompletion ? { pendingCompletion: durableInvocation.pendingCompletion } : {}
|
|
661
|
-
},
|
|
1036
|
+
}, submission.actionId);
|
|
662
1037
|
}
|
|
663
1038
|
if (options.dispatcher) {
|
|
664
1039
|
try {
|
|
665
1040
|
const dispatched = await options.dispatcher.dispatch({
|
|
666
1041
|
actionInvocationId: durableInvocation.id,
|
|
667
1042
|
actionId: durableInvocation.actionId,
|
|
668
|
-
tenantId:
|
|
669
|
-
spaceId:
|
|
1043
|
+
tenantId: submission.tenantId,
|
|
1044
|
+
spaceId: submission.spaceId,
|
|
670
1045
|
workflowId: durableWorkflowId
|
|
671
1046
|
});
|
|
672
1047
|
return withConsistency({
|
|
@@ -676,17 +1051,78 @@ function createGovernedActionHost(options) {
|
|
|
676
1051
|
...dispatched.runId ? { runId: dispatched.runId } : {},
|
|
677
1052
|
...durableInvocation.hitlRoute ? { hitlRoute: durableInvocation.hitlRoute } : {},
|
|
678
1053
|
...durableInvocation.hitlRiskTier ? { hitlRiskTier: durableInvocation.hitlRiskTier } : {}
|
|
679
|
-
},
|
|
1054
|
+
}, submission.actionId);
|
|
680
1055
|
} catch (error) {
|
|
681
1056
|
throw error;
|
|
682
1057
|
}
|
|
683
1058
|
}
|
|
684
1059
|
const executed = await executeInvocation(
|
|
685
1060
|
durableInvocation.id,
|
|
686
|
-
|
|
687
|
-
|
|
1061
|
+
submission.tenantId,
|
|
1062
|
+
submission.spaceId
|
|
688
1063
|
);
|
|
689
|
-
return { ...withConsistency(executed,
|
|
1064
|
+
return { ...withConsistency(executed, submission.actionId), workflowId: durableWorkflowId };
|
|
1065
|
+
}
|
|
1066
|
+
async function admitSubmission(action, submission) {
|
|
1067
|
+
const refuse = (refusal, refusalCode) => refuseAdmission(action, submission, refusal, refusalCode);
|
|
1068
|
+
const check = action.admitSubmission;
|
|
1069
|
+
if (typeof check !== "function") return refuse("failed");
|
|
1070
|
+
const checked = parametersCopy(submission);
|
|
1071
|
+
const controller = new AbortController();
|
|
1072
|
+
let timer;
|
|
1073
|
+
const deadline = new Promise((resolve) => {
|
|
1074
|
+
timer = setTimeout(() => {
|
|
1075
|
+
controller.abort();
|
|
1076
|
+
resolve(ADMISSION_TIMED_OUT);
|
|
1077
|
+
}, admissionTimeoutMs);
|
|
1078
|
+
});
|
|
1079
|
+
const startedAt = monotonicNow();
|
|
1080
|
+
const settledInTime = () => monotonicNow() - startedAt < admissionTimeoutMs;
|
|
1081
|
+
let answered;
|
|
1082
|
+
try {
|
|
1083
|
+
answered = Promise.resolve(check(Object.freeze({
|
|
1084
|
+
actionId: action.actionId,
|
|
1085
|
+
tenantId: submission.tenantId,
|
|
1086
|
+
spaceId: submission.spaceId,
|
|
1087
|
+
actorId: submission.actorId,
|
|
1088
|
+
actorType: submission.actorType,
|
|
1089
|
+
parameters: checked,
|
|
1090
|
+
signal: controller.signal
|
|
1091
|
+
}))).then(
|
|
1092
|
+
(value) => settledInTime() ? { kind: "answered", value } : ADMISSION_TIMED_OUT,
|
|
1093
|
+
() => settledInTime() ? ADMISSION_FAILED : ADMISSION_TIMED_OUT
|
|
1094
|
+
);
|
|
1095
|
+
} catch {
|
|
1096
|
+
answered = Promise.resolve(settledInTime() ? ADMISSION_FAILED : ADMISSION_TIMED_OUT);
|
|
1097
|
+
}
|
|
1098
|
+
let outcome;
|
|
1099
|
+
try {
|
|
1100
|
+
outcome = await Promise.race([answered, deadline]);
|
|
1101
|
+
} finally {
|
|
1102
|
+
clearTimeout(timer);
|
|
1103
|
+
}
|
|
1104
|
+
if (outcome.kind === "timed_out") {
|
|
1105
|
+
controller.abort();
|
|
1106
|
+
return refuse("timed_out");
|
|
1107
|
+
}
|
|
1108
|
+
if (outcome.kind === "failed") return refuse("failed");
|
|
1109
|
+
const admitted = ownDataProperty(outcome.value, "admitted");
|
|
1110
|
+
if (admitted === true) return { admittedAt: now() };
|
|
1111
|
+
const code = ownDataProperty(outcome.value, "code");
|
|
1112
|
+
if (admitted === false && typeof code === "string" && ADMISSION_REFUSAL_CODE.test(code)) return refuse("refused", code);
|
|
1113
|
+
return refuse("invalid_decision");
|
|
1114
|
+
}
|
|
1115
|
+
function refuseAdmission(action, submission, refusal, refusalCode) {
|
|
1116
|
+
emitPlatformHostTelemetry(options.telemetry, {
|
|
1117
|
+
kind: "event",
|
|
1118
|
+
name: "invocation.admission_refused",
|
|
1119
|
+
metricName: PLATFORM_HOST_METRIC_NAMES.invocationAdmissionRefused,
|
|
1120
|
+
occurredAt: now(),
|
|
1121
|
+
tenantId: submission.tenantId,
|
|
1122
|
+
spaceId: submission.spaceId,
|
|
1123
|
+
attributes: { actionVersion: action.version, refusal }
|
|
1124
|
+
});
|
|
1125
|
+
throw new ActionAdmissionError(action.actionId, refusal, refusalCode);
|
|
690
1126
|
}
|
|
691
1127
|
async function executeInvocation(actionInvocationId, tenantId, spaceId, executionOptions = {}, executionReasonOverride) {
|
|
692
1128
|
const loadedInvocation = await options.store.getActionInvocation(
|
|
@@ -715,6 +1151,13 @@ function createGovernedActionHost(options) {
|
|
|
715
1151
|
if (!action) {
|
|
716
1152
|
return fail2(invocation, "failed", `Unknown action: ${invocation.actionId}`);
|
|
717
1153
|
}
|
|
1154
|
+
if (action.admitSubmission !== void 0 && !isAdmissionTime(invocation.admittedAt)) {
|
|
1155
|
+
return fail2(
|
|
1156
|
+
invocation,
|
|
1157
|
+
"failed",
|
|
1158
|
+
`Invocation of ${action.actionId} was not admitted at submission; an action that declares admitSubmission runs only on invocations its check admitted.`
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
718
1161
|
if (action.kind === "saga") {
|
|
719
1162
|
return fail2(
|
|
720
1163
|
invocation,
|
|
@@ -882,6 +1325,10 @@ function createGovernedActionHost(options) {
|
|
|
882
1325
|
}
|
|
883
1326
|
const definitions = options.resolvePolicies ? await options.resolvePolicies({
|
|
884
1327
|
...authorizationInput,
|
|
1328
|
+
actionInvocationId,
|
|
1329
|
+
parameters: parsed.data,
|
|
1330
|
+
invocation,
|
|
1331
|
+
executionReason,
|
|
885
1332
|
declaredPolicyIds: action.policies ?? []
|
|
886
1333
|
}) : declaredPolicies(action.policies ?? []);
|
|
887
1334
|
const outcomes = await evaluatePolicyDefinitions(definitions, {
|
|
@@ -992,6 +1439,7 @@ function createGovernedActionHost(options) {
|
|
|
992
1439
|
correlationId: invocation.correlationId,
|
|
993
1440
|
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
994
1441
|
...invocation.provenance ? { provenance: invocation.provenance } : {},
|
|
1442
|
+
...isAdmissionTime(invocation.admittedAt) ? { admittedAt: invocation.admittedAt } : {},
|
|
995
1443
|
db,
|
|
996
1444
|
services: options.services
|
|
997
1445
|
},
|
|
@@ -1574,6 +2022,8 @@ function createGovernedActionHost(options) {
|
|
|
1574
2022
|
producerModuleVersion: options.outbox.producerModuleVersion(invocation.actionId, invocation.actionVersion),
|
|
1575
2023
|
payloadClassification,
|
|
1576
2024
|
includeProvenance: options.outbox.includeProvenance?.(envelope, payloadClassification) ?? payloadClassification !== "restricted",
|
|
2025
|
+
// Actor identity is excluded for every classification unless the application opts in.
|
|
2026
|
+
includeActor: options.outbox.includeActor?.(envelope, payloadClassification) === true,
|
|
1577
2027
|
...traceContext ? { traceContext } : {}
|
|
1578
2028
|
};
|
|
1579
2029
|
if (transaction) {
|
|
@@ -1643,7 +2093,13 @@ function createGovernedActionHost(options) {
|
|
|
1643
2093
|
recordExecutionAttestation,
|
|
1644
2094
|
recordExternalReconciliation,
|
|
1645
2095
|
completeExternalInvocation: externalCompletion.completeExternalInvocation,
|
|
1646
|
-
reconcileOverdueCompletions: externalCompletion.reconcileOverdueCompletions
|
|
2096
|
+
reconcileOverdueCompletions: externalCompletion.reconcileOverdueCompletions,
|
|
2097
|
+
requeueOutboxDeadLetter: createOutboxRequeue({
|
|
2098
|
+
store: options.store,
|
|
2099
|
+
authorization: options.authorization,
|
|
2100
|
+
telemetry: options.telemetry,
|
|
2101
|
+
now
|
|
2102
|
+
})
|
|
1647
2103
|
};
|
|
1648
2104
|
}
|
|
1649
2105
|
function actionResult(invocation) {
|
|
@@ -1705,6 +2161,18 @@ function isTerminal(status) {
|
|
|
1705
2161
|
function isRecord(value) {
|
|
1706
2162
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1707
2163
|
}
|
|
2164
|
+
function isAdmissionTime(value) {
|
|
2165
|
+
return value instanceof Date && Number.isFinite(value.getTime());
|
|
2166
|
+
}
|
|
2167
|
+
function ownDataProperty(value, key) {
|
|
2168
|
+
if (value === null || typeof value !== "object") return void 0;
|
|
2169
|
+
try {
|
|
2170
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
|
|
2171
|
+
return descriptor && "value" in descriptor ? descriptor.value : void 0;
|
|
2172
|
+
} catch {
|
|
2173
|
+
return void 0;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
1708
2176
|
function validateAuthorizationBinding(input, parameterDigest, authorityMoment, currentTime = /* @__PURE__ */ new Date()) {
|
|
1709
2177
|
const binding = input.authorizationBinding;
|
|
1710
2178
|
if (binding.id !== input.authorizationBindingId && input.authorizationBindingId) throw new Error("Authorization binding ID does not match authorizationBindingId.");
|
|
@@ -1744,6 +2212,9 @@ function toEnterpriseEventEnvelope(event, metadata) {
|
|
|
1744
2212
|
...event.actionInvocationId ? { actionInvocationId: event.actionInvocationId } : {},
|
|
1745
2213
|
correlationId: event.correlationId,
|
|
1746
2214
|
...event.causationId ? { causationId: event.causationId } : {},
|
|
2215
|
+
// Opt-in only: without it the keys are absent, so existing publishers see an unchanged envelope.
|
|
2216
|
+
...includesActor(metadata) && event.actorId ? { actorId: event.actorId } : {},
|
|
2217
|
+
...includesActor(metadata) && event.actorType ? { actorType: event.actorType } : {},
|
|
1747
2218
|
occurredAt: event.occurredAt,
|
|
1748
2219
|
recordedAt: event.recordedAt,
|
|
1749
2220
|
producerModuleVersion: metadata.producerModuleVersion,
|
|
@@ -1755,6 +2226,12 @@ function toEnterpriseEventEnvelope(event, metadata) {
|
|
|
1755
2226
|
payload: event.payload
|
|
1756
2227
|
};
|
|
1757
2228
|
}
|
|
2229
|
+
function outboxActorDisclosure(metadata) {
|
|
2230
|
+
return includesActor(metadata) ? "included" : "suppressed";
|
|
2231
|
+
}
|
|
2232
|
+
function includesActor(metadata) {
|
|
2233
|
+
return metadata.includeActor === true;
|
|
2234
|
+
}
|
|
1758
2235
|
async function runOutboxRelayCycle(options) {
|
|
1759
2236
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1760
2237
|
const maxAttempts = options.maxAttempts ?? 10;
|
|
@@ -1789,7 +2266,7 @@ async function runOutboxRelayCycle(options) {
|
|
|
1789
2266
|
try {
|
|
1790
2267
|
await publishWithTimeout(
|
|
1791
2268
|
options.publisher,
|
|
1792
|
-
record
|
|
2269
|
+
record,
|
|
1793
2270
|
options.publishTimeoutMs ?? 1e4
|
|
1794
2271
|
);
|
|
1795
2272
|
await stopHeartbeat();
|
|
@@ -1851,7 +2328,7 @@ async function runOutboxRelayCycle(options) {
|
|
|
1851
2328
|
});
|
|
1852
2329
|
return result;
|
|
1853
2330
|
}
|
|
1854
|
-
async function publishWithTimeout(publisher,
|
|
2331
|
+
async function publishWithTimeout(publisher, record, timeoutMs) {
|
|
1855
2332
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
1856
2333
|
throw new Error("publishTimeoutMs must be a positive finite number");
|
|
1857
2334
|
}
|
|
@@ -1865,7 +2342,11 @@ async function publishWithTimeout(publisher, event, timeoutMs) {
|
|
|
1865
2342
|
}, timeoutMs);
|
|
1866
2343
|
});
|
|
1867
2344
|
try {
|
|
1868
|
-
|
|
2345
|
+
const context = {
|
|
2346
|
+
signal: controller.signal,
|
|
2347
|
+
actorDisclosure: record.actorDisclosure ?? "legacy"
|
|
2348
|
+
};
|
|
2349
|
+
await Promise.race([publisher.publish(record.event, context), timeout]);
|
|
1869
2350
|
} finally {
|
|
1870
2351
|
if (timer) clearTimeout(timer);
|
|
1871
2352
|
}
|
|
@@ -1895,9 +2376,23 @@ function startOutboxHeartbeat(options, record, leaseDurationMs) {
|
|
|
1895
2376
|
};
|
|
1896
2377
|
}
|
|
1897
2378
|
function cloneOutboxRecord(record) {
|
|
2379
|
+
const event = {
|
|
2380
|
+
...record.event,
|
|
2381
|
+
occurredAt: new Date(record.event.occurredAt),
|
|
2382
|
+
recordedAt: new Date(record.event.recordedAt),
|
|
2383
|
+
...record.event.traceContext ? { traceContext: { ...record.event.traceContext } } : {}
|
|
2384
|
+
};
|
|
2385
|
+
if (record.actorDisclosure !== "included") {
|
|
2386
|
+
delete event.actorId;
|
|
2387
|
+
delete event.actorType;
|
|
2388
|
+
}
|
|
1898
2389
|
return {
|
|
1899
2390
|
...record,
|
|
1900
|
-
|
|
2391
|
+
availableAt: new Date(record.availableAt),
|
|
2392
|
+
createdAt: new Date(record.createdAt),
|
|
2393
|
+
...record.leaseExpiresAt ? { leaseExpiresAt: new Date(record.leaseExpiresAt) } : {},
|
|
2394
|
+
...record.publishedAt ? { publishedAt: new Date(record.publishedAt) } : {},
|
|
2395
|
+
event
|
|
1901
2396
|
};
|
|
1902
2397
|
}
|
|
1903
2398
|
function emitRelayTelemetry(telemetry, name, occurredAt, workerId, attributes) {
|
|
@@ -1929,6 +2424,13 @@ var MemoryPlatformHostStore = class {
|
|
|
1929
2424
|
executionAttestations = [];
|
|
1930
2425
|
externalReconciliations = [];
|
|
1931
2426
|
outbox = [];
|
|
2427
|
+
/**
|
|
2428
|
+
* Requeue evidence is append-only audit history, so unlike the ledger
|
|
2429
|
+
* arrays above, which tests have always inspected and seeded directly, it
|
|
2430
|
+
* is not public: `listOutboxRequeues` returns copies with their own Dates. TypeScript
|
|
2431
|
+
* `private`, as for the store's other internals, is a compile-time guard.
|
|
2432
|
+
*/
|
|
2433
|
+
outboxRequeues = [];
|
|
1932
2434
|
async transaction(run) {
|
|
1933
2435
|
return run(this.db);
|
|
1934
2436
|
}
|
|
@@ -2129,6 +2631,7 @@ var MemoryPlatformHostStore = class {
|
|
|
2129
2631
|
tenantId: event.tenantId,
|
|
2130
2632
|
spaceId: event.spaceId,
|
|
2131
2633
|
event: toEnterpriseEventEnvelope(event, metadata),
|
|
2634
|
+
actorDisclosure: outboxActorDisclosure(metadata),
|
|
2132
2635
|
status: "pending",
|
|
2133
2636
|
attemptCount: 0,
|
|
2134
2637
|
availableAt: createdAt,
|
|
@@ -2170,6 +2673,42 @@ var MemoryPlatformHostStore = class {
|
|
|
2170
2673
|
async listOutbox(input = {}) {
|
|
2171
2674
|
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);
|
|
2172
2675
|
}
|
|
2676
|
+
async getOutboxRecord(id, tenantId, spaceId) {
|
|
2677
|
+
const record = this.outbox.find((candidate) => candidate.id === id && candidate.tenantId === tenantId && candidate.spaceId === spaceId);
|
|
2678
|
+
return record ? cloneOutboxRecord(record) : void 0;
|
|
2679
|
+
}
|
|
2680
|
+
async requeueOutboxDeadLetter(input) {
|
|
2681
|
+
const record = this.outbox.find((candidate) => candidate.id === input.id && candidate.tenantId === input.tenantId && candidate.spaceId === input.spaceId);
|
|
2682
|
+
if (!record) return { applied: false, refusal: "not_found" };
|
|
2683
|
+
if (record.status !== "dead_letter") return { applied: false, refusal: "not_dead_letter", currentStatus: record.status };
|
|
2684
|
+
if ((record.leaseToken ?? 0) !== input.expectedLeaseToken) return { applied: false, refusal: "stale", currentStatus: record.status };
|
|
2685
|
+
const requeue = {
|
|
2686
|
+
id: input.requeueId,
|
|
2687
|
+
outboxId: record.id,
|
|
2688
|
+
tenantId: record.tenantId,
|
|
2689
|
+
spaceId: record.spaceId,
|
|
2690
|
+
eventId: record.event.eventId,
|
|
2691
|
+
actorId: input.actorId,
|
|
2692
|
+
actorType: input.actorType,
|
|
2693
|
+
reason: input.reason,
|
|
2694
|
+
requeuedAt: new Date(input.requeuedAt),
|
|
2695
|
+
availableAt: new Date(input.availableAt),
|
|
2696
|
+
priorAttemptCount: record.attemptCount,
|
|
2697
|
+
...record.lastError !== void 0 ? { priorLastError: record.lastError } : {},
|
|
2698
|
+
priorAvailableAt: new Date(record.availableAt),
|
|
2699
|
+
priorLeaseToken: record.leaseToken ?? 0
|
|
2700
|
+
};
|
|
2701
|
+
record.status = "pending";
|
|
2702
|
+
record.attemptCount = 0;
|
|
2703
|
+
record.availableAt = new Date(input.availableAt);
|
|
2704
|
+
delete record.leaseOwner;
|
|
2705
|
+
delete record.leaseExpiresAt;
|
|
2706
|
+
this.outboxRequeues.push(requeue);
|
|
2707
|
+
return { applied: true, record: cloneOutboxRecord(record), requeue: cloneOutboxRequeue(requeue) };
|
|
2708
|
+
}
|
|
2709
|
+
async listOutboxRequeues(input) {
|
|
2710
|
+
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);
|
|
2711
|
+
}
|
|
2173
2712
|
requireLeasedOutbox(id, workerId, leaseToken) {
|
|
2174
2713
|
const record = this.outbox.find((candidate) => candidate.id === id);
|
|
2175
2714
|
if (!record || record.leaseOwner !== workerId || (record.leaseToken ?? 0) !== (leaseToken ?? 0)) {
|
|
@@ -2251,6 +2790,14 @@ var MemoryPlatformHostStore = class {
|
|
|
2251
2790
|
return this.events.filter((event) => event.tenantId === tenantId && event.spaceId === spaceId).sort((left, right) => left.sequence - right.sequence);
|
|
2252
2791
|
}
|
|
2253
2792
|
};
|
|
2793
|
+
function cloneOutboxRequeue(requeue) {
|
|
2794
|
+
return {
|
|
2795
|
+
...requeue,
|
|
2796
|
+
requeuedAt: new Date(requeue.requeuedAt),
|
|
2797
|
+
availableAt: new Date(requeue.availableAt),
|
|
2798
|
+
priorAvailableAt: new Date(requeue.priorAvailableAt)
|
|
2799
|
+
};
|
|
2800
|
+
}
|
|
2254
2801
|
async function applyPostgresMigrations(client, migrations) {
|
|
2255
2802
|
const ordered = validateMigrations(migrations);
|
|
2256
2803
|
const statements = ordered.map((migration) => migrationStatement(migration));
|
|
@@ -2492,6 +3039,43 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
2492
3039
|
ON fabric_platform.action_invocations (completion_due_at)
|
|
2493
3040
|
WHERE completion_due_at IS NOT NULL;
|
|
2494
3041
|
`
|
|
3042
|
+
}, {
|
|
3043
|
+
version: 4,
|
|
3044
|
+
name: "outbox_actor_disclosure",
|
|
3045
|
+
// Nullable with no default, so rows written before this migration stay NULL and read back
|
|
3046
|
+
// as legacy. NOT VALID skips scanning existing rows; the check still applies to every
|
|
3047
|
+
// insert and update from here on.
|
|
3048
|
+
sql: `
|
|
3049
|
+
ALTER TABLE fabric_platform.event_outbox
|
|
3050
|
+
ADD COLUMN actor_disclosure text;
|
|
3051
|
+
ALTER TABLE fabric_platform.event_outbox
|
|
3052
|
+
ADD CONSTRAINT event_outbox_actor_disclosure_check
|
|
3053
|
+
CHECK (actor_disclosure IN ('included', 'suppressed')) NOT VALID;
|
|
3054
|
+
`
|
|
3055
|
+
}, {
|
|
3056
|
+
version: 5,
|
|
3057
|
+
name: "outbox_requeue",
|
|
3058
|
+
sql: `
|
|
3059
|
+
CREATE TABLE fabric_platform.event_outbox_requeues (
|
|
3060
|
+
id text PRIMARY KEY, outbox_id text NOT NULL,
|
|
3061
|
+
tenant_id text NOT NULL, space_id text NOT NULL, event_id text NOT NULL,
|
|
3062
|
+
actor_id text NOT NULL, actor_type text NOT NULL, reason text NOT NULL,
|
|
3063
|
+
requeued_at timestamptz NOT NULL, available_at timestamptz NOT NULL,
|
|
3064
|
+
prior_attempt_count integer NOT NULL, prior_last_error text,
|
|
3065
|
+
prior_available_at timestamptz NOT NULL, prior_lease_token bigint NOT NULL
|
|
3066
|
+
);
|
|
3067
|
+
CREATE INDEX event_outbox_requeues_record_idx
|
|
3068
|
+
ON fabric_platform.event_outbox_requeues (tenant_id, space_id, outbox_id, requeued_at);
|
|
3069
|
+
`
|
|
3070
|
+
}, {
|
|
3071
|
+
version: 6,
|
|
3072
|
+
name: "invocation_admission",
|
|
3073
|
+
// Nullable with no default: rows written before this migration, and
|
|
3074
|
+
// rows of actions that declare no admission check, stay NULL.
|
|
3075
|
+
sql: `
|
|
3076
|
+
ALTER TABLE fabric_platform.action_invocations
|
|
3077
|
+
ADD COLUMN admitted_at timestamptz;
|
|
3078
|
+
`
|
|
2495
3079
|
}]);
|
|
2496
3080
|
}
|
|
2497
3081
|
async transaction(run) {
|
|
@@ -2499,44 +3083,59 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
2499
3083
|
}
|
|
2500
3084
|
async createActionInvocation(input) {
|
|
2501
3085
|
const now = /* @__PURE__ */ new Date();
|
|
2502
|
-
const
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
3086
|
+
const admitted = input.admittedAt !== void 0;
|
|
3087
|
+
const admittedColumn = admitted ? "admitted_at," : "";
|
|
3088
|
+
const admittedValue = admitted ? "$25," : "";
|
|
3089
|
+
let result;
|
|
3090
|
+
try {
|
|
3091
|
+
result = await this.sql.query(
|
|
3092
|
+
`INSERT INTO fabric_platform.action_invocations
|
|
3093
|
+
(id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
|
|
3094
|
+
parameters,result,correlation_id,causation_id,idempotency_key,
|
|
3095
|
+
parameter_digest,parameter_digest_algorithm,idempotency_actor_id,
|
|
3096
|
+
idempotency_authorization_binding_id,invocation_provenance,authorization_binding,execution_reason,
|
|
3097
|
+
authorization_binding_id,error,runtime_evidence,${admittedColumn}created_at,updated_at)
|
|
3098
|
+
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)
|
|
3099
|
+
ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
|
|
3100
|
+
WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
3101
|
+
RETURNING *`,
|
|
3102
|
+
[
|
|
3103
|
+
input.id,
|
|
3104
|
+
input.tenantId,
|
|
3105
|
+
input.spaceId,
|
|
3106
|
+
input.actionId,
|
|
3107
|
+
input.actionVersion,
|
|
3108
|
+
input.actorId,
|
|
3109
|
+
input.actorType,
|
|
3110
|
+
input.status,
|
|
3111
|
+
JSON.stringify(input.parameters),
|
|
3112
|
+
JSON.stringify(input.result),
|
|
3113
|
+
input.correlationId,
|
|
3114
|
+
input.causationId ?? null,
|
|
3115
|
+
input.idempotencyKey ?? null,
|
|
3116
|
+
input.parameterDigest ?? null,
|
|
3117
|
+
input.parameterDigestAlgorithm ?? null,
|
|
3118
|
+
input.idempotencyActorId ?? null,
|
|
3119
|
+
input.idempotencyAuthorizationBindingId ?? null,
|
|
3120
|
+
input.provenance ? JSON.stringify(input.provenance) : null,
|
|
3121
|
+
input.authorizationBinding ? JSON.stringify(input.authorizationBinding) : null,
|
|
3122
|
+
input.executionReason ?? null,
|
|
3123
|
+
input.authorizationBindingId ?? null,
|
|
3124
|
+
input.error ?? null,
|
|
3125
|
+
input.runtimeEvidence ? JSON.stringify(input.runtimeEvidence) : null,
|
|
3126
|
+
now,
|
|
3127
|
+
...admitted ? [input.admittedAt] : []
|
|
3128
|
+
]
|
|
3129
|
+
);
|
|
3130
|
+
} catch (error) {
|
|
3131
|
+
if (admitted && error?.code === "42703") {
|
|
3132
|
+
throw new Error(
|
|
3133
|
+
"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.",
|
|
3134
|
+
{ cause: error }
|
|
3135
|
+
);
|
|
3136
|
+
}
|
|
3137
|
+
throw error;
|
|
3138
|
+
}
|
|
2540
3139
|
return toActionRecord(result.rows[0]);
|
|
2541
3140
|
}
|
|
2542
3141
|
async getActionInvocation(id, tenantId, spaceId) {
|
|
@@ -2811,8 +3410,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
2811
3410
|
ON CONFLICT (id) DO NOTHING RETURNING id
|
|
2812
3411
|
)
|
|
2813
3412
|
INSERT INTO fabric_platform.event_outbox
|
|
2814
|
-
(id,tenant_id,space_id,event,status,attempt_count,available_at,created_at)
|
|
2815
|
-
SELECT $1,$2,$3,$18::jsonb,'pending',0,$14,$14 FROM inserted_event
|
|
3413
|
+
(id,tenant_id,space_id,event,actor_disclosure,status,attempt_count,available_at,created_at)
|
|
3414
|
+
SELECT $1,$2,$3,$18::jsonb,$20,'pending',0,$14,$14 FROM inserted_event
|
|
2816
3415
|
ON CONFLICT (id) DO NOTHING`,
|
|
2817
3416
|
[
|
|
2818
3417
|
event.id,
|
|
@@ -2833,7 +3432,8 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
2833
3432
|
event.causationId ?? null,
|
|
2834
3433
|
event.provenance ? JSON.stringify(event.provenance) : null,
|
|
2835
3434
|
JSON.stringify(envelope),
|
|
2836
|
-
event.consistency ?? null
|
|
3435
|
+
event.consistency ?? null,
|
|
3436
|
+
outboxActorDisclosure(metadata)
|
|
2837
3437
|
]
|
|
2838
3438
|
);
|
|
2839
3439
|
}
|
|
@@ -2901,6 +3501,90 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
2901
3501
|
);
|
|
2902
3502
|
return result.rows.map(toOutboxRecord);
|
|
2903
3503
|
}
|
|
3504
|
+
async getOutboxRecord(id, tenantId, spaceId) {
|
|
3505
|
+
const result = await this.sql.query(
|
|
3506
|
+
`SELECT * FROM fabric_platform.event_outbox
|
|
3507
|
+
WHERE id=$1 AND tenant_id=$2 AND space_id=$3`,
|
|
3508
|
+
[id, tenantId, spaceId]
|
|
3509
|
+
);
|
|
3510
|
+
return result.rows[0] ? toOutboxRecord(result.rows[0]) : void 0;
|
|
3511
|
+
}
|
|
3512
|
+
async requeueOutboxDeadLetter(input) {
|
|
3513
|
+
const result = await this.sql.query(
|
|
3514
|
+
`WITH prior AS (
|
|
3515
|
+
SELECT id, tenant_id, space_id, event->>'eventId' AS event_id,
|
|
3516
|
+
attempt_count, last_error, available_at, lease_token
|
|
3517
|
+
FROM fabric_platform.event_outbox
|
|
3518
|
+
WHERE id=$1 AND tenant_id=$2 AND space_id=$3 AND status='dead_letter'
|
|
3519
|
+
AND lease_token=$10
|
|
3520
|
+
FOR UPDATE
|
|
3521
|
+
), requeued AS (
|
|
3522
|
+
UPDATE fabric_platform.event_outbox AS item
|
|
3523
|
+
SET status='pending', attempt_count=0, available_at=$9,
|
|
3524
|
+
lease_owner=NULL, lease_expires_at=NULL
|
|
3525
|
+
FROM prior WHERE item.id=prior.id
|
|
3526
|
+
RETURNING item.*
|
|
3527
|
+
), evidence AS (
|
|
3528
|
+
INSERT INTO fabric_platform.event_outbox_requeues
|
|
3529
|
+
(id,outbox_id,tenant_id,space_id,event_id,actor_id,actor_type,reason,
|
|
3530
|
+
requeued_at,available_at,prior_attempt_count,prior_last_error,
|
|
3531
|
+
prior_available_at,prior_lease_token)
|
|
3532
|
+
SELECT $4,prior.id,prior.tenant_id,prior.space_id,prior.event_id,$5,$6,$7,
|
|
3533
|
+
$8,$9,prior.attempt_count,prior.last_error,prior.available_at,prior.lease_token
|
|
3534
|
+
FROM prior JOIN requeued ON requeued.id=prior.id
|
|
3535
|
+
RETURNING *
|
|
3536
|
+
)
|
|
3537
|
+
SELECT requeued.*,
|
|
3538
|
+
evidence.id AS requeue_id, evidence.outbox_id AS requeue_outbox_id,
|
|
3539
|
+
evidence.tenant_id AS requeue_tenant_id, evidence.space_id AS requeue_space_id,
|
|
3540
|
+
evidence.event_id AS requeue_event_id, evidence.actor_id AS requeue_actor_id,
|
|
3541
|
+
evidence.actor_type AS requeue_actor_type, evidence.reason AS requeue_reason,
|
|
3542
|
+
evidence.requeued_at AS requeue_requeued_at, evidence.available_at AS requeue_available_at,
|
|
3543
|
+
evidence.prior_attempt_count AS requeue_prior_attempt_count,
|
|
3544
|
+
evidence.prior_last_error AS requeue_prior_last_error,
|
|
3545
|
+
evidence.prior_available_at AS requeue_prior_available_at,
|
|
3546
|
+
evidence.prior_lease_token AS requeue_prior_lease_token
|
|
3547
|
+
FROM requeued JOIN evidence ON evidence.outbox_id=requeued.id`,
|
|
3548
|
+
[
|
|
3549
|
+
input.id,
|
|
3550
|
+
input.tenantId,
|
|
3551
|
+
input.spaceId,
|
|
3552
|
+
input.requeueId,
|
|
3553
|
+
input.actorId,
|
|
3554
|
+
input.actorType,
|
|
3555
|
+
input.reason,
|
|
3556
|
+
input.requeuedAt,
|
|
3557
|
+
input.availableAt,
|
|
3558
|
+
input.expectedLeaseToken
|
|
3559
|
+
]
|
|
3560
|
+
);
|
|
3561
|
+
const row = result.rows[0];
|
|
3562
|
+
if (row) {
|
|
3563
|
+
return {
|
|
3564
|
+
applied: true,
|
|
3565
|
+
record: toOutboxRecord(row),
|
|
3566
|
+
requeue: toOutboxRequeueRecord(row, "requeue_")
|
|
3567
|
+
};
|
|
3568
|
+
}
|
|
3569
|
+
const current = await this.sql.query(
|
|
3570
|
+
`SELECT status FROM fabric_platform.event_outbox
|
|
3571
|
+
WHERE id=$1 AND tenant_id=$2 AND space_id=$3`,
|
|
3572
|
+
[input.id, input.tenantId, input.spaceId]
|
|
3573
|
+
);
|
|
3574
|
+
const found = current.rows[0];
|
|
3575
|
+
if (!found) return { applied: false, refusal: "not_found" };
|
|
3576
|
+
const status = found.status;
|
|
3577
|
+
return status === "dead_letter" ? { applied: false, refusal: "stale", currentStatus: status } : { applied: false, refusal: "not_dead_letter", currentStatus: status };
|
|
3578
|
+
}
|
|
3579
|
+
async listOutboxRequeues(input) {
|
|
3580
|
+
const result = await this.sql.query(
|
|
3581
|
+
`SELECT * FROM fabric_platform.event_outbox_requeues
|
|
3582
|
+
WHERE tenant_id=$1 AND space_id=$2 AND ($3::text IS NULL OR outbox_id=$3)
|
|
3583
|
+
ORDER BY requeued_at,id`,
|
|
3584
|
+
[input.tenantId, input.spaceId, input.outboxId ?? null]
|
|
3585
|
+
);
|
|
3586
|
+
return result.rows.map((row) => toOutboxRequeueRecord(row, ""));
|
|
3587
|
+
}
|
|
2904
3588
|
async nextEventSequence(tenantId, spaceId) {
|
|
2905
3589
|
const result = await this.sql.query(
|
|
2906
3590
|
`INSERT INTO fabric_platform.event_sequences (tenant_id,space_id,next_sequence)
|
|
@@ -3173,6 +3857,7 @@ function toActionRecord(row) {
|
|
|
3173
3857
|
...row.mutation_footprint ? { mutationFootprint: row.mutation_footprint } : {},
|
|
3174
3858
|
...row.execution_principal ? { executionPrincipal: row.execution_principal } : {},
|
|
3175
3859
|
...row.runtime_evidence ? { runtimeEvidence: row.runtime_evidence } : {},
|
|
3860
|
+
...row.admitted_at ? { admittedAt: new Date(row.admitted_at) } : {},
|
|
3176
3861
|
...row.error ? { error: String(row.error) } : {},
|
|
3177
3862
|
createdAt: new Date(row.created_at),
|
|
3178
3863
|
updatedAt: new Date(row.updated_at)
|
|
@@ -3241,10 +3926,13 @@ function toEventRecord(row) {
|
|
|
3241
3926
|
}
|
|
3242
3927
|
function toOutboxRecord(row) {
|
|
3243
3928
|
const event = row.event;
|
|
3929
|
+
const actorDisclosure = row.actor_disclosure === "included" || row.actor_disclosure === "suppressed" ? row.actor_disclosure : void 0;
|
|
3930
|
+
const actorIncluded = actorDisclosure === "included";
|
|
3244
3931
|
return {
|
|
3245
3932
|
id: String(row.id),
|
|
3246
3933
|
tenantId: String(row.tenant_id),
|
|
3247
3934
|
spaceId: String(row.space_id),
|
|
3935
|
+
...actorDisclosure ? { actorDisclosure } : {},
|
|
3248
3936
|
event: {
|
|
3249
3937
|
eventId: String(event.eventId),
|
|
3250
3938
|
eventType: String(event.eventType),
|
|
@@ -3257,6 +3945,8 @@ function toOutboxRecord(row) {
|
|
|
3257
3945
|
...event.actionInvocationId ? { actionInvocationId: String(event.actionInvocationId) } : {},
|
|
3258
3946
|
correlationId: String(event.correlationId),
|
|
3259
3947
|
...event.causationId ? { causationId: String(event.causationId) } : {},
|
|
3948
|
+
...actorIncluded && event.actorId ? { actorId: String(event.actorId) } : {},
|
|
3949
|
+
...actorIncluded && event.actorType ? { actorType: String(event.actorType) } : {},
|
|
3260
3950
|
...event.provenance ? { provenance: event.provenance } : {},
|
|
3261
3951
|
...event.consistency ? { consistency: event.consistency } : {},
|
|
3262
3952
|
occurredAt: new Date(event.occurredAt),
|
|
@@ -3277,6 +3967,26 @@ function toOutboxRecord(row) {
|
|
|
3277
3967
|
...row.published_at ? { publishedAt: new Date(row.published_at) } : {}
|
|
3278
3968
|
};
|
|
3279
3969
|
}
|
|
3970
|
+
function toOutboxRequeueRecord(row, prefix) {
|
|
3971
|
+
const field = (name) => row[`${prefix}${name}`];
|
|
3972
|
+
const priorLastError = field("prior_last_error");
|
|
3973
|
+
return {
|
|
3974
|
+
id: String(field("id")),
|
|
3975
|
+
outboxId: String(field("outbox_id")),
|
|
3976
|
+
tenantId: String(field("tenant_id")),
|
|
3977
|
+
spaceId: String(field("space_id")),
|
|
3978
|
+
eventId: String(field("event_id")),
|
|
3979
|
+
actorId: String(field("actor_id")),
|
|
3980
|
+
actorType: String(field("actor_type")),
|
|
3981
|
+
reason: String(field("reason")),
|
|
3982
|
+
requeuedAt: new Date(field("requeued_at")),
|
|
3983
|
+
availableAt: new Date(field("available_at")),
|
|
3984
|
+
priorAttemptCount: Number(field("prior_attempt_count")),
|
|
3985
|
+
...priorLastError !== null && priorLastError !== void 0 ? { priorLastError: String(priorLastError) } : {},
|
|
3986
|
+
priorAvailableAt: new Date(field("prior_available_at")),
|
|
3987
|
+
priorLeaseToken: Number(field("prior_lease_token"))
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3280
3990
|
|
|
3281
3991
|
// src/worker.ts
|
|
3282
3992
|
var DEFAULT_BATCH_SIZE = 10;
|
|
@@ -3737,6 +4447,6 @@ async function runTenantIsolationChecks(subject) {
|
|
|
3737
4447
|
return { passed: checks.every((check) => check.status !== "failed"), checks };
|
|
3738
4448
|
}
|
|
3739
4449
|
|
|
3740
|
-
export { ExternalCompletionError, IdempotencyConflictError, MemoryPlatformHostStore, PARAMETER_DIGEST_ALGORITHM, PLATFORM_HOST_CONTRACT_VERSION, PLATFORM_HOST_HEALTH_CONTRACT_VERSION, PLATFORM_HOST_METRIC_NAMES, PostgresPlatformHostStore, applyPostgresMigrations, canonicalJson, cloneOutboxRecord, createDurableSagaParentLifecycle, createGovernedActionHost, createStoreBackedActionDispatcher, digestParameters, emitPlatformHostTelemetry, getPlatformHostHealthSnapshot, runOutboxRelayCycle, runPlatformActionWorker, runPlatformActionWorkerCycle, runTenantIsolationChecks, tenantIsolationChecks, toEnterpriseEventEnvelope };
|
|
4450
|
+
export { ActionAdmissionError, ExternalCompletionError, IdempotencyConflictError, MemoryPlatformHostStore, PARAMETER_DIGEST_ALGORITHM, PLATFORM_HOST_CONTRACT_VERSION, PLATFORM_HOST_HEALTH_CONTRACT_VERSION, PLATFORM_HOST_METRIC_NAMES, PostgresPlatformHostStore, applyPostgresMigrations, canonicalJson, cloneOutboxRecord, createDurableSagaParentLifecycle, createGovernedActionHost, createStoreBackedActionDispatcher, digestParameters, emitPlatformHostTelemetry, getPlatformHostHealthSnapshot, outboxActorDisclosure, runOutboxRelayCycle, runPlatformActionWorker, runPlatformActionWorkerCycle, runTenantIsolationChecks, tenantIsolationChecks, toEnterpriseEventEnvelope };
|
|
3741
4451
|
//# sourceMappingURL=index.js.map
|
|
3742
4452
|
//# sourceMappingURL=index.js.map
|