@fabricorg/platform-host 7.1.2 → 7.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1167,4 +1167,66 @@ interface CreateDurableSagaParentLifecycleOptions<TDb> {
1167
1167
  */
1168
1168
  declare function createDurableSagaParentLifecycle<TDb>(options: CreateDurableSagaParentLifecycleOptions<TDb>): HostSagaParentLifecycle;
1169
1169
 
1170
- export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionExecutionAuthorizationInput, type ActionExecutionReason, type ActionInvocationPatch, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type AdapterReconciliationOutcome, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type AtomicMutationPlatformHostStore, type AuthorizationBinding, type AuthorizationGoverningMoment, type AuthorizationReconciliationOutcome, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type ClaimOutboxInput, type CreateActionInvocationInput, type CreateDurableSagaParentLifecycleOptions, type DispatchActionInput, type DispatchActionResult, type EnterpriseEventEnvelope, type EnterpriseEventPublisher, type EventPayloadClassification, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalCompletion, ExternalCompletionError, type ExternalCompletionRefusal, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type HostCompensationRecord, type HostSagaParentApprovalInput, type HostSagaParentCancellationInput, type HostSagaParentCompletionInput, type HostSagaParentFailureInput, type HostSagaParentLifecycle, type HostSagaParentProgressInput, type HostSagaParentProgressRecord, type IdempotencyConflict, IdempotencyConflictError, type InvocationProvenance, type InvocationSource, type KnownActionExecutionReason, type ListActionInvocationsInput, MemoryPlatformHostStore, type MemoryPlatformHostTransactionProvider, type NamespacedAttributes, type OutboxEventMetadata, type OutboxPlatformHostStore, type OutboxRecord, type OutboxRelayOptions, type OutboxRelayResult, type OutboxStatus, PARAMETER_DIGEST_ALGORITHM, PLATFORM_HOST_CONTRACT_VERSION, PLATFORM_HOST_HEALTH_CONTRACT_VERSION, PLATFORM_HOST_METRIC_NAMES, type PendingAssetEvent, type PendingExternalCompletion, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostHealthCounts, type PlatformHostHealthOptions, type PlatformHostHealthQuery, type PlatformHostHealthSnapshot, type PlatformHostHealthStatus, type PlatformHostHealthStore, type PlatformHostLifecycleTelemetry, type PlatformHostMetricName, type PlatformHostMetricTelemetry, type PlatformHostMetricType, type PlatformHostMutationTransaction, type PlatformHostOptions, type PlatformHostReadinessReason, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PlatformHostTelemetry, type PlatformHostTelemetryAttribute, type PlatformHostTelemetryEventName, type PlatformHostTelemetryHook, type PlatformHostTelemetryInput, type PlatformHostTelemetryRecord, type PlatformHostWorkerHealthInput, type PolicyEvaluationRecord, type PolicyObligationRecord, type PostgresMigration, PostgresPlatformHostStore, type PostgresPlatformHostTransactionContext, type PostgresPlatformHostTransactionProvider, type ReconcileOverdueCompletionsInput, type RecordedExternalCompletion, type RecoverablePlatformHostStore, type RenewActionInvocationLeaseInput, type SubmitActionInput, type SubmitActionResult, type UpdateLeasedActionInvocationInput, applyPostgresMigrations, canonicalJson, cloneOutboxRecord, createDurableSagaParentLifecycle, createGovernedActionHost, createStoreBackedActionDispatcher, digestParameters, emitPlatformHostTelemetry, getPlatformHostHealthSnapshot, runOutboxRelayCycle, runPlatformActionWorker, runPlatformActionWorkerCycle, toEnterpriseEventEnvelope };
1170
+ interface TenantIsolationScope {
1171
+ tenantId: string;
1172
+ spaceId: string;
1173
+ actorId: string;
1174
+ actorType: ActorType;
1175
+ }
1176
+ interface TenantIsolationSubject<TDb = unknown> {
1177
+ host: GovernedActionHost;
1178
+ store: RecoverablePlatformHostStore<TDb>;
1179
+ /** A governed action registered on the host, and parameters it accepts. */
1180
+ actionId: ActionId;
1181
+ parameters?: Record<string, unknown>;
1182
+ /** Two scopes in different tenants. */
1183
+ ours: TenantIsolationScope;
1184
+ theirs: TenantIsolationScope;
1185
+ /**
1186
+ * Optional: a governed read the subject exposes, scoped as the projection
1187
+ * host scopes it. Returns what a viewer in `scope` sees for the view. Every
1188
+ * row must carry the `tenantId` it belongs to, so the check can tell a row
1189
+ * of ours from a row of theirs that merely looks the same; two tenants may
1190
+ * legitimately hold equal-looking rows.
1191
+ */
1192
+ read?(scope: TenantIsolationScope): Promise<{
1193
+ allowed: boolean;
1194
+ rows: readonly {
1195
+ tenantId: string;
1196
+ }[];
1197
+ }>;
1198
+ /**
1199
+ * Optional: put the host and store back to a known state before a check.
1200
+ * The checks tolerate accumulated state, and each cleans up any row it
1201
+ * creates directly, so this exists for subjects whose store is shared.
1202
+ */
1203
+ reset?(): Promise<void> | void;
1204
+ }
1205
+ interface TenantIsolationCheck {
1206
+ id: "fabric.tenancy.invocation-scoped-read.v1" | "fabric.tenancy.idempotency-key-scoped.v1" | "fabric.tenancy.events-scoped.v1" | "fabric.tenancy.worker-claim-scoped.v1" | "fabric.tenancy.completion-scoped.v1" | "fabric.tenancy.projection-scoped.v1";
1207
+ run(subject: TenantIsolationSubject): Promise<string[]>;
1208
+ }
1209
+ interface TenantIsolationResult {
1210
+ passed: boolean;
1211
+ checks: Array<{
1212
+ id: TenantIsolationCheck["id"];
1213
+ status: "passed" | "failed" | "skipped";
1214
+ evidence: string[];
1215
+ error?: string;
1216
+ }>;
1217
+ }
1218
+ /**
1219
+ * The suite any deployment of the host must pass before it claims isolation.
1220
+ *
1221
+ * Six checks: an invocation cannot be read, executed, or completed under
1222
+ * another tenant's scope; an idempotency key is a tenant's own, so the same
1223
+ * key in two tenants is two invocations; a tenant's event stream carries no
1224
+ * other tenant's events; a worker scoped to one tenant never claims another's
1225
+ * work; and, when the subject lends a read, a viewer in the other tenant sees
1226
+ * nothing of ours.
1227
+ */
1228
+ declare function tenantIsolationChecks(): readonly TenantIsolationCheck[];
1229
+ /** Run every check, returning each result rather than throwing on the first. */
1230
+ declare function runTenantIsolationChecks(subject: TenantIsolationSubject): Promise<TenantIsolationResult>;
1231
+
1232
+ export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionExecutionAuthorizationInput, type ActionExecutionReason, type ActionInvocationPatch, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type AdapterReconciliationOutcome, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type AtomicMutationPlatformHostStore, type AuthorizationBinding, type AuthorizationGoverningMoment, type AuthorizationReconciliationOutcome, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type ClaimOutboxInput, type CreateActionInvocationInput, type CreateDurableSagaParentLifecycleOptions, type DispatchActionInput, type DispatchActionResult, type EnterpriseEventEnvelope, type EnterpriseEventPublisher, type EventPayloadClassification, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalCompletion, ExternalCompletionError, type ExternalCompletionRefusal, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type HostCompensationRecord, type HostSagaParentApprovalInput, type HostSagaParentCancellationInput, type HostSagaParentCompletionInput, type HostSagaParentFailureInput, type HostSagaParentLifecycle, type HostSagaParentProgressInput, type HostSagaParentProgressRecord, type IdempotencyConflict, IdempotencyConflictError, type InvocationProvenance, type InvocationSource, type KnownActionExecutionReason, type ListActionInvocationsInput, MemoryPlatformHostStore, type MemoryPlatformHostTransactionProvider, type NamespacedAttributes, type OutboxEventMetadata, type OutboxPlatformHostStore, type OutboxRecord, type OutboxRelayOptions, type OutboxRelayResult, type OutboxStatus, PARAMETER_DIGEST_ALGORITHM, PLATFORM_HOST_CONTRACT_VERSION, PLATFORM_HOST_HEALTH_CONTRACT_VERSION, PLATFORM_HOST_METRIC_NAMES, type PendingAssetEvent, type PendingExternalCompletion, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostHealthCounts, type PlatformHostHealthOptions, type PlatformHostHealthQuery, type PlatformHostHealthSnapshot, type PlatformHostHealthStatus, type PlatformHostHealthStore, type PlatformHostLifecycleTelemetry, type PlatformHostMetricName, type PlatformHostMetricTelemetry, type PlatformHostMetricType, type PlatformHostMutationTransaction, type PlatformHostOptions, type PlatformHostReadinessReason, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PlatformHostTelemetry, type PlatformHostTelemetryAttribute, type PlatformHostTelemetryEventName, type PlatformHostTelemetryHook, type PlatformHostTelemetryInput, type PlatformHostTelemetryRecord, type PlatformHostWorkerHealthInput, type PolicyEvaluationRecord, type PolicyObligationRecord, type PostgresMigration, PostgresPlatformHostStore, type PostgresPlatformHostTransactionContext, type PostgresPlatformHostTransactionProvider, type ReconcileOverdueCompletionsInput, type RecordedExternalCompletion, type RecoverablePlatformHostStore, type RenewActionInvocationLeaseInput, type SubmitActionInput, type SubmitActionResult, type TenantIsolationCheck, type TenantIsolationResult, type TenantIsolationScope, type TenantIsolationSubject, type UpdateLeasedActionInvocationInput, applyPostgresMigrations, canonicalJson, cloneOutboxRecord, createDurableSagaParentLifecycle, createGovernedActionHost, createStoreBackedActionDispatcher, digestParameters, emitPlatformHostTelemetry, getPlatformHostHealthSnapshot, runOutboxRelayCycle, runPlatformActionWorker, runPlatformActionWorkerCycle, runTenantIsolationChecks, tenantIsolationChecks, toEnterpriseEventEnvelope };
package/dist/index.d.ts CHANGED
@@ -1167,4 +1167,66 @@ interface CreateDurableSagaParentLifecycleOptions<TDb> {
1167
1167
  */
1168
1168
  declare function createDurableSagaParentLifecycle<TDb>(options: CreateDurableSagaParentLifecycleOptions<TDb>): HostSagaParentLifecycle;
1169
1169
 
1170
- export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionExecutionAuthorizationInput, type ActionExecutionReason, type ActionInvocationPatch, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type AdapterReconciliationOutcome, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type AtomicMutationPlatformHostStore, type AuthorizationBinding, type AuthorizationGoverningMoment, type AuthorizationReconciliationOutcome, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type ClaimOutboxInput, type CreateActionInvocationInput, type CreateDurableSagaParentLifecycleOptions, type DispatchActionInput, type DispatchActionResult, type EnterpriseEventEnvelope, type EnterpriseEventPublisher, type EventPayloadClassification, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalCompletion, ExternalCompletionError, type ExternalCompletionRefusal, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type HostCompensationRecord, type HostSagaParentApprovalInput, type HostSagaParentCancellationInput, type HostSagaParentCompletionInput, type HostSagaParentFailureInput, type HostSagaParentLifecycle, type HostSagaParentProgressInput, type HostSagaParentProgressRecord, type IdempotencyConflict, IdempotencyConflictError, type InvocationProvenance, type InvocationSource, type KnownActionExecutionReason, type ListActionInvocationsInput, MemoryPlatformHostStore, type MemoryPlatformHostTransactionProvider, type NamespacedAttributes, type OutboxEventMetadata, type OutboxPlatformHostStore, type OutboxRecord, type OutboxRelayOptions, type OutboxRelayResult, type OutboxStatus, PARAMETER_DIGEST_ALGORITHM, PLATFORM_HOST_CONTRACT_VERSION, PLATFORM_HOST_HEALTH_CONTRACT_VERSION, PLATFORM_HOST_METRIC_NAMES, type PendingAssetEvent, type PendingExternalCompletion, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostHealthCounts, type PlatformHostHealthOptions, type PlatformHostHealthQuery, type PlatformHostHealthSnapshot, type PlatformHostHealthStatus, type PlatformHostHealthStore, type PlatformHostLifecycleTelemetry, type PlatformHostMetricName, type PlatformHostMetricTelemetry, type PlatformHostMetricType, type PlatformHostMutationTransaction, type PlatformHostOptions, type PlatformHostReadinessReason, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PlatformHostTelemetry, type PlatformHostTelemetryAttribute, type PlatformHostTelemetryEventName, type PlatformHostTelemetryHook, type PlatformHostTelemetryInput, type PlatformHostTelemetryRecord, type PlatformHostWorkerHealthInput, type PolicyEvaluationRecord, type PolicyObligationRecord, type PostgresMigration, PostgresPlatformHostStore, type PostgresPlatformHostTransactionContext, type PostgresPlatformHostTransactionProvider, type ReconcileOverdueCompletionsInput, type RecordedExternalCompletion, type RecoverablePlatformHostStore, type RenewActionInvocationLeaseInput, type SubmitActionInput, type SubmitActionResult, type UpdateLeasedActionInvocationInput, applyPostgresMigrations, canonicalJson, cloneOutboxRecord, createDurableSagaParentLifecycle, createGovernedActionHost, createStoreBackedActionDispatcher, digestParameters, emitPlatformHostTelemetry, getPlatformHostHealthSnapshot, runOutboxRelayCycle, runPlatformActionWorker, runPlatformActionWorkerCycle, toEnterpriseEventEnvelope };
1170
+ interface TenantIsolationScope {
1171
+ tenantId: string;
1172
+ spaceId: string;
1173
+ actorId: string;
1174
+ actorType: ActorType;
1175
+ }
1176
+ interface TenantIsolationSubject<TDb = unknown> {
1177
+ host: GovernedActionHost;
1178
+ store: RecoverablePlatformHostStore<TDb>;
1179
+ /** A governed action registered on the host, and parameters it accepts. */
1180
+ actionId: ActionId;
1181
+ parameters?: Record<string, unknown>;
1182
+ /** Two scopes in different tenants. */
1183
+ ours: TenantIsolationScope;
1184
+ theirs: TenantIsolationScope;
1185
+ /**
1186
+ * Optional: a governed read the subject exposes, scoped as the projection
1187
+ * host scopes it. Returns what a viewer in `scope` sees for the view. Every
1188
+ * row must carry the `tenantId` it belongs to, so the check can tell a row
1189
+ * of ours from a row of theirs that merely looks the same; two tenants may
1190
+ * legitimately hold equal-looking rows.
1191
+ */
1192
+ read?(scope: TenantIsolationScope): Promise<{
1193
+ allowed: boolean;
1194
+ rows: readonly {
1195
+ tenantId: string;
1196
+ }[];
1197
+ }>;
1198
+ /**
1199
+ * Optional: put the host and store back to a known state before a check.
1200
+ * The checks tolerate accumulated state, and each cleans up any row it
1201
+ * creates directly, so this exists for subjects whose store is shared.
1202
+ */
1203
+ reset?(): Promise<void> | void;
1204
+ }
1205
+ interface TenantIsolationCheck {
1206
+ id: "fabric.tenancy.invocation-scoped-read.v1" | "fabric.tenancy.idempotency-key-scoped.v1" | "fabric.tenancy.events-scoped.v1" | "fabric.tenancy.worker-claim-scoped.v1" | "fabric.tenancy.completion-scoped.v1" | "fabric.tenancy.projection-scoped.v1";
1207
+ run(subject: TenantIsolationSubject): Promise<string[]>;
1208
+ }
1209
+ interface TenantIsolationResult {
1210
+ passed: boolean;
1211
+ checks: Array<{
1212
+ id: TenantIsolationCheck["id"];
1213
+ status: "passed" | "failed" | "skipped";
1214
+ evidence: string[];
1215
+ error?: string;
1216
+ }>;
1217
+ }
1218
+ /**
1219
+ * The suite any deployment of the host must pass before it claims isolation.
1220
+ *
1221
+ * Six checks: an invocation cannot be read, executed, or completed under
1222
+ * another tenant's scope; an idempotency key is a tenant's own, so the same
1223
+ * key in two tenants is two invocations; a tenant's event stream carries no
1224
+ * other tenant's events; a worker scoped to one tenant never claims another's
1225
+ * work; and, when the subject lends a read, a viewer in the other tenant sees
1226
+ * nothing of ours.
1227
+ */
1228
+ declare function tenantIsolationChecks(): readonly TenantIsolationCheck[];
1229
+ /** Run every check, returning each result rather than throwing on the first. */
1230
+ declare function runTenantIsolationChecks(subject: TenantIsolationSubject): Promise<TenantIsolationResult>;
1231
+
1232
+ export { type ActionApprovalDecision, type ActionAuthorizationInput, type ActionExecutionAuthorizationInput, type ActionExecutionReason, type ActionInvocationPatch, type ActionInvocationRecord, type AdapterInvocationRecord, type AdapterInvocationStatus, type AdapterReconciliationOutcome, type ApprovalDecisionTransitionResult, type ApprovalPlatformHostStore, type AtomicMutationPlatformHostStore, type AuthorizationBinding, type AuthorizationGoverningMoment, type AuthorizationReconciliationOutcome, type BeginApprovalDecisionInput, type ClaimActionInvocationsInput, type ClaimOutboxInput, type CreateActionInvocationInput, type CreateDurableSagaParentLifecycleOptions, type DispatchActionInput, type DispatchActionResult, type EnterpriseEventEnvelope, type EnterpriseEventPublisher, type EventPayloadClassification, type ExecuteActionResult, type ExecuteInvocationOptions, type ExecutionAttestationRecord, type ExternalCompletion, ExternalCompletionError, type ExternalCompletionRefusal, type ExternalReconciliationRecord, type GovernancePlatformHostStore, type GovernedActionHost, type HitlDecisionEvidence, type HostCompensationRecord, type HostSagaParentApprovalInput, type HostSagaParentCancellationInput, type HostSagaParentCompletionInput, type HostSagaParentFailureInput, type HostSagaParentLifecycle, type HostSagaParentProgressInput, type HostSagaParentProgressRecord, type IdempotencyConflict, IdempotencyConflictError, type InvocationProvenance, type InvocationSource, type KnownActionExecutionReason, type ListActionInvocationsInput, MemoryPlatformHostStore, type MemoryPlatformHostTransactionProvider, type NamespacedAttributes, type OutboxEventMetadata, type OutboxPlatformHostStore, type OutboxRecord, type OutboxRelayOptions, type OutboxRelayResult, type OutboxStatus, PARAMETER_DIGEST_ALGORITHM, PLATFORM_HOST_CONTRACT_VERSION, PLATFORM_HOST_HEALTH_CONTRACT_VERSION, PLATFORM_HOST_METRIC_NAMES, type PendingAssetEvent, type PendingExternalCompletion, type PersistedActionApprovalDecision, type PlatformActionDispatcher, type PlatformActionWorkerCycleResult, type PlatformActionWorkerOptions, type PlatformHostAuthorization, type PlatformHostHealthCounts, type PlatformHostHealthOptions, type PlatformHostHealthQuery, type PlatformHostHealthSnapshot, type PlatformHostHealthStatus, type PlatformHostHealthStore, type PlatformHostLifecycleTelemetry, type PlatformHostMetricName, type PlatformHostMetricTelemetry, type PlatformHostMetricType, type PlatformHostMutationTransaction, type PlatformHostOptions, type PlatformHostReadinessReason, type PlatformHostSqlClient, type PlatformHostSqlResult, type PlatformHostStore, type PlatformHostTelemetry, type PlatformHostTelemetryAttribute, type PlatformHostTelemetryEventName, type PlatformHostTelemetryHook, type PlatformHostTelemetryInput, type PlatformHostTelemetryRecord, type PlatformHostWorkerHealthInput, type PolicyEvaluationRecord, type PolicyObligationRecord, type PostgresMigration, PostgresPlatformHostStore, type PostgresPlatformHostTransactionContext, type PostgresPlatformHostTransactionProvider, type ReconcileOverdueCompletionsInput, type RecordedExternalCompletion, type RecoverablePlatformHostStore, type RenewActionInvocationLeaseInput, type SubmitActionInput, type SubmitActionResult, type TenantIsolationCheck, type TenantIsolationResult, type TenantIsolationScope, type TenantIsolationSubject, type UpdateLeasedActionInvocationInput, applyPostgresMigrations, canonicalJson, cloneOutboxRecord, createDurableSagaParentLifecycle, createGovernedActionHost, createStoreBackedActionDispatcher, digestParameters, emitPlatformHostTelemetry, getPlatformHostHealthSnapshot, runOutboxRelayCycle, runPlatformActionWorker, runPlatformActionWorkerCycle, runTenantIsolationChecks, tenantIsolationChecks, toEnterpriseEventEnvelope };
package/dist/index.js CHANGED
@@ -713,24 +713,24 @@ function createGovernedActionHost(options) {
713
713
  }
714
714
  const action = actionResolver(invocation.actionId);
715
715
  if (!action) {
716
- return fail(invocation, "failed", `Unknown action: ${invocation.actionId}`);
716
+ return fail2(invocation, "failed", `Unknown action: ${invocation.actionId}`);
717
717
  }
718
718
  if (action.kind === "saga") {
719
- return fail(
719
+ return fail2(
720
720
  invocation,
721
721
  "failed",
722
722
  `Saga action ${invocation.actionId} requires a durable PlatformActionDispatcher and cannot execute as an atomic Host action.`
723
723
  );
724
724
  }
725
725
  if (resumingRunningInvocation && invocation.attemptCount === 0 && !invocation.leaseOwner && !action.idempotent) {
726
- return fail(
726
+ return fail2(
727
727
  invocation,
728
728
  "failed",
729
729
  `Interrupted inline action ${invocation.actionId} is not declared idempotent; manual reconciliation is required.`
730
730
  );
731
731
  }
732
732
  if (invocation.attemptCount > 1 && !action.idempotent) {
733
- return fail(
733
+ return fail2(
734
734
  invocation,
735
735
  "failed",
736
736
  `Interrupted action ${invocation.actionId} is not declared idempotent; manual reconciliation is required.`
@@ -754,12 +754,12 @@ function createGovernedActionHost(options) {
754
754
  const parsed = action.schema.safeParse(invocation.parameters);
755
755
  if (!parsed.success) {
756
756
  const message = parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
757
- return fail(invocation, "validation_failed", message);
757
+ return fail2(invocation, "validation_failed", message);
758
758
  }
759
759
  if (options.resolveMutationGovernance && !invocation.mutationFootprint) {
760
760
  const governanceStore2 = asGovernanceStore(options.store);
761
761
  if (!governanceStore2) {
762
- return fail(invocation, "failed", "The configured mutation-governance resolver requires a governance-capable store.");
762
+ return fail2(invocation, "failed", "The configured mutation-governance resolver requires a governance-capable store.");
763
763
  }
764
764
  const context = await options.resolveMutationGovernance({
765
765
  actionInvocationId,
@@ -777,7 +777,7 @@ function createGovernedActionHost(options) {
777
777
  const hitlParameters = isRecord(parsed.data) ? parsed.data : invocation.parameters;
778
778
  const approvalStore = asApprovalStore(options.store);
779
779
  if (!approvalStore) {
780
- return fail(
780
+ return fail2(
781
781
  invocation,
782
782
  "failed",
783
783
  "The configured HITL evaluator requires a store with approval persistence support."
@@ -808,7 +808,7 @@ function createGovernedActionHost(options) {
808
808
  );
809
809
  }
810
810
  if (invocation.hitlRoute === "rejected") {
811
- return fail(
811
+ return fail2(
812
812
  invocation,
813
813
  "failed",
814
814
  `HITL rejected: ${invocation.hitlReason ?? "no reason provided"}`
@@ -827,7 +827,7 @@ function createGovernedActionHost(options) {
827
827
  const authorizationInput = toAuthorizationInput(action, invocation);
828
828
  const executionReason = executionReasonOverride ?? (invocation.attemptCount > 1 || resumingRunningInvocation && invocation.attemptCount === 0 ? "recovery" : invocation.executionReason ?? "initial");
829
829
  if (!await options.authorization.checkEntitlement(authorizationInput)) {
830
- return fail(
830
+ return fail2(
831
831
  invocation,
832
832
  "failed",
833
833
  `Module "${action.namespace}" is no longer enabled for tenant ${tenantId}`
@@ -867,7 +867,7 @@ function createGovernedActionHost(options) {
867
867
  }) : await options.authorization.authorize(authorizationInput);
868
868
  if (!executionAuthorized) {
869
869
  if (!action.execution) {
870
- return fail(invocation, "failed", `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`);
870
+ return fail2(invocation, "failed", `Actor ${invocation.actorId} is not authorized to execute action ${action.actionId}`);
871
871
  }
872
872
  const reconciliation = {
873
873
  kind: "authorization_denied",
@@ -917,12 +917,12 @@ function createGovernedActionHost(options) {
917
917
  subjectId: actionInvocationId,
918
918
  payload: { actionId: action.actionId, policyId: aggregate.policyId, reason }
919
919
  }, `compliance:${aggregate.policyId}`);
920
- return fail(invocation, "blocked_by_policy", reason);
920
+ return fail2(invocation, "blocked_by_policy", reason);
921
921
  }
922
922
  if (options.resolvePolicyObligations) {
923
923
  const governanceStore2 = asGovernanceStore(options.store);
924
924
  if (!governanceStore2) {
925
- return fail(invocation, "failed", "Policy obligations require a governance-capable store.");
925
+ return fail2(invocation, "failed", "Policy obligations require a governance-capable store.");
926
926
  }
927
927
  const existing = await governanceStore2.listPolicyObligations(actionInvocationId, tenantId, spaceId);
928
928
  if (existing.length === 0) {
@@ -1037,7 +1037,7 @@ function createGovernedActionHost(options) {
1037
1037
  data = executed.data;
1038
1038
  domainEvents = executed.domainEvents;
1039
1039
  } catch (error) {
1040
- return fail(invocation, "failed", errorMessage(error));
1040
+ return fail2(invocation, "failed", errorMessage(error));
1041
1041
  }
1042
1042
  let pendingHandoff = invocation.pendingCompletion;
1043
1043
  for (const [stepIndex, step] of (action.adapterSteps ?? []).entries()) {
@@ -1217,7 +1217,7 @@ function createGovernedActionHost(options) {
1217
1217
  subjectId: adapterEventSubject.subjectId,
1218
1218
  payload: { adapterType: step.adapterType, operation: step.operation }
1219
1219
  }, `adapter:${stepIndex}:failed`);
1220
- return fail(invocation, "failed", result2.error ?? "Adapter failed");
1220
+ return fail2(invocation, "failed", result2.error ?? "Adapter failed");
1221
1221
  }
1222
1222
  const resultRecord = result2;
1223
1223
  const output = isRecord(resultRecord.output) ? resultRecord.output : Object.fromEntries(
@@ -1321,7 +1321,7 @@ function createGovernedActionHost(options) {
1321
1321
  error: message,
1322
1322
  updatedAt: now()
1323
1323
  });
1324
- return fail(invocation, "failed", message);
1324
+ return fail2(invocation, "failed", message);
1325
1325
  } finally {
1326
1326
  if (adapterDeadlineTimer) clearTimeout(adapterDeadlineTimer);
1327
1327
  }
@@ -1343,7 +1343,7 @@ function createGovernedActionHost(options) {
1343
1343
  }
1344
1344
  return withConsistency({ actionInvocationId, status: "reconciliation_required", error, pendingCompletion: pendingHandoff }, action.actionId);
1345
1345
  }
1346
- return fail(invocation, "failed", message);
1346
+ return fail2(invocation, "failed", message);
1347
1347
  }
1348
1348
  }
1349
1349
  const result = withoutPrivateHostFields(data, eventResultFields);
@@ -1416,7 +1416,7 @@ function createGovernedActionHost(options) {
1416
1416
  };
1417
1417
  } catch (error) {
1418
1418
  if (error instanceof RecoverableFinalizationError) throw error;
1419
- return fail(invocation, "failed", errorMessage(error));
1419
+ return fail2(invocation, "failed", errorMessage(error));
1420
1420
  }
1421
1421
  }
1422
1422
  async function resumeApprovedInvocation(actionInvocationId, tenantId, spaceId, decision) {
@@ -1435,7 +1435,7 @@ function createGovernedActionHost(options) {
1435
1435
  };
1436
1436
  }
1437
1437
  const action = actionResolver(invocation.actionId);
1438
- if (!action) return fail(invocation, "failed", `Unknown action: ${invocation.actionId}`);
1438
+ if (!action) return fail2(invocation, "failed", `Unknown action: ${invocation.actionId}`);
1439
1439
  const approvalAuthorizationInput = toAuthorizationInput(action, {
1440
1440
  tenantId,
1441
1441
  spaceId,
@@ -1588,7 +1588,7 @@ function createGovernedActionHost(options) {
1588
1588
  await (transaction ?? options.store).appendEvent(envelope);
1589
1589
  }
1590
1590
  }
1591
- async function fail(invocation, status, error) {
1591
+ async function fail2(invocation, status, error) {
1592
1592
  await persistInvocation(invocation, { status, error });
1593
1593
  return {
1594
1594
  actionInvocationId: invocation.id,
@@ -3562,6 +3562,181 @@ function createDurableSagaParentLifecycle(options) {
3562
3562
  };
3563
3563
  }
3564
3564
 
3565
- 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, toEnterpriseEventEnvelope };
3565
+ // src/tenant-isolation.ts
3566
+ function fail(message) {
3567
+ throw new Error(message);
3568
+ }
3569
+ function submitInput(subject, scope, idempotencyKey) {
3570
+ return {
3571
+ actionId: subject.actionId,
3572
+ parameters: subject.parameters ?? {},
3573
+ tenantId: scope.tenantId,
3574
+ spaceId: scope.spaceId,
3575
+ actorId: scope.actorId,
3576
+ actorType: scope.actorType,
3577
+ idempotencyKey
3578
+ };
3579
+ }
3580
+ function tenantIsolationChecks() {
3581
+ return [
3582
+ {
3583
+ id: "fabric.tenancy.invocation-scoped-read.v1",
3584
+ async run(subject) {
3585
+ await subject.reset?.();
3586
+ if (subject.ours.tenantId === subject.theirs.tenantId) fail("the two scopes must be in different tenants");
3587
+ const ours = await subject.host.submitAction(submitInput(subject, subject.ours, "iso:read"));
3588
+ const crossRead = await subject.store.getActionInvocation(ours.actionInvocationId, subject.theirs.tenantId, subject.theirs.spaceId);
3589
+ if (crossRead !== void 0) fail("an invocation was readable under another tenant's scope");
3590
+ const shape = (record) => JSON.stringify({ status: record?.status, result: record?.result, error: record?.error, attemptCount: record?.attemptCount, updatedAt: record?.updatedAt });
3591
+ const before = await subject.store.getActionInvocation(ours.actionInvocationId, subject.ours.tenantId, subject.ours.spaceId);
3592
+ if (!before) fail("our own invocation was not readable under our scope, so the check proves nothing");
3593
+ const untouched = shape(before);
3594
+ let crossExecute;
3595
+ try {
3596
+ crossExecute = await subject.host.executeInvocation(ours.actionInvocationId, subject.theirs.tenantId, subject.theirs.spaceId);
3597
+ } catch {
3598
+ crossExecute = void 0;
3599
+ }
3600
+ if (crossExecute !== void 0) fail("an invocation was executable under another tenant's scope");
3601
+ const after = await subject.store.getActionInvocation(ours.actionInvocationId, subject.ours.tenantId, subject.ours.spaceId);
3602
+ if (untouched !== shape(after)) fail("an execution attempted under another tenant's scope changed our invocation");
3603
+ return ["invocation readable under our scope, invisible and untouched under the other tenant's"];
3604
+ }
3605
+ },
3606
+ {
3607
+ id: "fabric.tenancy.idempotency-key-scoped.v1",
3608
+ async run(subject) {
3609
+ await subject.reset?.();
3610
+ const ours = await subject.host.submitAction(submitInput(subject, subject.ours, "iso:shared-key"));
3611
+ const again = await subject.host.submitAction(submitInput(subject, subject.ours, "iso:shared-key"));
3612
+ if (ours.actionInvocationId !== again.actionInvocationId) fail("the same idempotency key in one tenant produced two invocations, so the key is not doing anything and the check proves nothing");
3613
+ const theirs = await subject.host.submitAction(submitInput(subject, subject.theirs, "iso:shared-key"));
3614
+ if (ours.actionInvocationId === theirs.actionInvocationId) fail("the same idempotency key in two tenants resolved to one invocation");
3615
+ return ["one idempotency key: one invocation within a tenant, two across tenants"];
3616
+ }
3617
+ },
3618
+ {
3619
+ id: "fabric.tenancy.events-scoped.v1",
3620
+ async run(subject) {
3621
+ await subject.reset?.();
3622
+ const ours = await subject.host.submitAction(submitInput(subject, subject.ours, "iso:events"));
3623
+ const theirEvents = await subject.store.listEvents(subject.theirs.tenantId, subject.theirs.spaceId);
3624
+ const leaked = theirEvents.filter((event) => event.tenantId === subject.ours.tenantId || event.actionInvocationId === ours.actionInvocationId);
3625
+ if (leaked.length > 0) fail(`${leaked.length} of our events appeared in the other tenant's stream`);
3626
+ const ourEvents = await subject.store.listEvents(subject.ours.tenantId, subject.ours.spaceId);
3627
+ if (!ourEvents.some((event) => event.actionInvocationId === ours.actionInvocationId)) fail("our own stream does not carry the event we just caused, so the check proves nothing");
3628
+ if (ourEvents.some((event) => event.tenantId !== subject.ours.tenantId)) fail("another tenant's event appeared in our stream");
3629
+ return ["event streams carry their own tenant's events and only those"];
3630
+ }
3631
+ },
3632
+ {
3633
+ id: "fabric.tenancy.worker-claim-scoped.v1",
3634
+ async run(subject) {
3635
+ await subject.reset?.();
3636
+ const probeSpace = `iso-probe-${Date.now()}`;
3637
+ const created = await subject.store.createActionInvocation({
3638
+ id: `iso_pending_${Date.now()}`,
3639
+ tenantId: subject.ours.tenantId,
3640
+ spaceId: probeSpace,
3641
+ actionId: subject.actionId,
3642
+ actionVersion: 1,
3643
+ actorId: subject.ours.actorId,
3644
+ actorType: subject.ours.actorType,
3645
+ status: "pending",
3646
+ parameters: subject.parameters ?? {},
3647
+ result: {},
3648
+ correlationId: `iso-corr-${Date.now()}`
3649
+ });
3650
+ try {
3651
+ const theirs = await subject.store.claimActionInvocations({
3652
+ workerId: "iso-worker",
3653
+ leaseDurationMs: 1e3,
3654
+ limit: 1e3,
3655
+ tenantId: subject.theirs.tenantId,
3656
+ spaceId: probeSpace
3657
+ });
3658
+ if (theirs.some((row) => row.id === created.id)) fail("a worker scoped to another tenant claimed our pending invocation");
3659
+ const ours = await subject.store.claimActionInvocations({
3660
+ workerId: "iso-worker",
3661
+ leaseDurationMs: 1e3,
3662
+ limit: 1e3,
3663
+ tenantId: subject.ours.tenantId,
3664
+ spaceId: probeSpace
3665
+ });
3666
+ if (!ours.some((row) => row.id === created.id)) fail("a worker scoped to our own tenant did not claim our pending invocation, so the check proves nothing");
3667
+ } finally {
3668
+ await subject.store.updateActionInvocation(created.id, subject.ours.tenantId, probeSpace, {
3669
+ status: "failed",
3670
+ error: "tenant isolation probe; never executed"
3671
+ });
3672
+ }
3673
+ return ["tenant-scoped claim takes our work and leaves other tenants' alone"];
3674
+ }
3675
+ },
3676
+ {
3677
+ id: "fabric.tenancy.completion-scoped.v1",
3678
+ async run(subject) {
3679
+ await subject.reset?.();
3680
+ const ours = await subject.host.submitAction(submitInput(subject, subject.ours, "iso:complete"));
3681
+ let outcome;
3682
+ try {
3683
+ await subject.host.completeExternalInvocation(ours.actionInvocationId, subject.theirs.tenantId, subject.theirs.spaceId, {
3684
+ externalReference: "iso-ref",
3685
+ outcome: "completed",
3686
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
3687
+ });
3688
+ outcome = "accepted";
3689
+ } catch (error) {
3690
+ const refusal = error.refusal;
3691
+ outcome = refusal ?? "threw";
3692
+ }
3693
+ if (outcome !== "not_found") fail(`a completion under another tenant's scope was ${outcome}; it must be refused as not_found`);
3694
+ let mine;
3695
+ try {
3696
+ await subject.host.completeExternalInvocation(ours.actionInvocationId, subject.ours.tenantId, subject.ours.spaceId, {
3697
+ externalReference: "iso-ref",
3698
+ outcome: "completed",
3699
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
3700
+ });
3701
+ mine = "accepted";
3702
+ } catch (error) {
3703
+ mine = error.refusal ?? "threw";
3704
+ }
3705
+ if (mine === "not_found") fail("a completion under our own scope was refused as not_found, so the check proves nothing");
3706
+ return ["completion under the other tenant's scope refused as not_found; found under ours"];
3707
+ }
3708
+ },
3709
+ {
3710
+ id: "fabric.tenancy.projection-scoped.v1",
3711
+ async run(subject) {
3712
+ if (!subject.read) return ["skipped: subject lends no read"];
3713
+ await subject.reset?.();
3714
+ await subject.host.submitAction(submitInput(subject, subject.ours, "iso:projection"));
3715
+ const ours = await subject.read(subject.ours);
3716
+ const theirs = await subject.read(subject.theirs);
3717
+ if (!ours.allowed) fail("our own viewer was denied the read");
3718
+ if (ours.rows.length === 0) fail("our own viewer saw nothing, so the check proves nothing");
3719
+ if (ours.rows.some((row) => row?.tenantId !== subject.ours.tenantId)) fail("our own viewer saw rows that are not ours, or rows carrying no tenant");
3720
+ const leaked = theirs.allowed ? theirs.rows.filter((row) => row?.tenantId === subject.ours.tenantId) : [];
3721
+ if (leaked.length > 0) fail(`a viewer in the other tenant saw ${leaked.length} of our rows`);
3722
+ return [theirs.allowed ? "other tenant's viewer sees none of our rows" : "other tenant's viewer is denied"];
3723
+ }
3724
+ }
3725
+ ];
3726
+ }
3727
+ async function runTenantIsolationChecks(subject) {
3728
+ const checks = [];
3729
+ for (const check of tenantIsolationChecks()) {
3730
+ try {
3731
+ const evidence = await check.run(subject);
3732
+ checks.push({ id: check.id, status: evidence[0]?.startsWith("skipped") ? "skipped" : "passed", evidence });
3733
+ } catch (error) {
3734
+ checks.push({ id: check.id, status: "failed", evidence: [], error: error instanceof Error ? error.message : String(error) });
3735
+ }
3736
+ }
3737
+ return { passed: checks.every((check) => check.status !== "failed"), checks };
3738
+ }
3739
+
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 };
3566
3741
  //# sourceMappingURL=index.js.map
3567
3742
  //# sourceMappingURL=index.js.map