@fabricorg/platform-host 8.9.0 → 8.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,6 +3,201 @@ import { AdapterCircuitBreakerRegistry, AdapterRegistry, FABRIC_GOVERNANCE_CONTR
3
3
  import { performance as performance$1 } from "perf_hooks";
4
4
  import { assertAssemblyRuntimeCompatible } from "@fabricorg/assembly";
5
5
  import { createHash } from "crypto";
6
+ //#region src/erasure-types.ts
7
+ const HOST_RECORDS_ERASED_EVENT = "HostRecordsErased";
8
+ /** Sentinel replacing required payload digests after stored-record erasure. */
9
+ const ERASED_DIGEST = "erased";
10
+ //#endregion
11
+ //#region src/types.ts
12
+ /** Durable Host lifecycle generation, independent from the npm package version. */
13
+ const PLATFORM_HOST_CONTRACT_VERSION = 2;
14
+ var IdempotencyConflictError = class extends Error {
15
+ conflict;
16
+ code = "IDEMPOTENCY_CONFLICT";
17
+ constructor(conflict) {
18
+ super(`Idempotency key "${conflict.idempotencyKey}" conflicts with invocation ${conflict.existingInvocationId}: ${conflict.reasons.join(", ")}`);
19
+ this.conflict = conflict;
20
+ this.name = "IdempotencyConflictError";
21
+ }
22
+ };
23
+ /**
24
+ * Thrown by `submitAction` when a declared admission check did not admit the
25
+ * submission. By default Host writes nothing for it. With recordRefusals
26
+ * enabled and an idempotency key, a definitive refusal can be recorded;
27
+ * `recorded` confirms persistence of the terminal refusal. The message holds
28
+ * the action id, the refusal and, for `refused`, the check's fixed code. It omits parameter
29
+ * payloads and caught callback error text.
30
+ */
31
+ var ActionAdmissionError = class extends Error {
32
+ actionId;
33
+ refusal;
34
+ refusalCode;
35
+ recorded;
36
+ code = "ACTION_ADMISSION_REFUSED";
37
+ constructor(actionId, refusal, refusalCode, recorded = false) {
38
+ super(`Action ${actionId} submission was not admitted (${refusal}${refusalCode ? `: ${refusalCode}` : ""}); ${recorded ? "the refusal was recorded under the idempotency key" : "nothing was recorded"}.`);
39
+ this.actionId = actionId;
40
+ this.refusal = refusal;
41
+ this.refusalCode = refusalCode;
42
+ this.recorded = recorded;
43
+ this.name = "ActionAdmissionError";
44
+ }
45
+ };
46
+ /** A definitive authorization-boundary refusal before an invocation is created. */
47
+ var SubmissionRefusalError = class extends Error {
48
+ actionId;
49
+ refusal;
50
+ recorded;
51
+ code = "ACTION_SUBMISSION_REFUSED";
52
+ constructor(actionId, refusal, message, recorded = false) {
53
+ super(recorded ? `Action ${actionId} submission was refused (${refusal}); the refusal was recorded under the idempotency key.` : message);
54
+ this.actionId = actionId;
55
+ this.refusal = refusal;
56
+ this.recorded = recorded;
57
+ this.name = "SubmissionRefusalError";
58
+ }
59
+ };
60
+ /**
61
+ * Why a completion was not applied, typed so a webhook handler can answer
62
+ * the external system correctly: `retryable` means the invocation is still
63
+ * being finalized and the same callback will be accepted once it parks, so
64
+ * answer with something the sender retries; anything else is permanent and
65
+ * a retry will get the same refusal without a configuration/input change.
66
+ * `redaction_failed` means completion result or error-text redaction failed before writes;
67
+ * it is not retryable.
68
+ */
69
+ var ExternalCompletionError = class extends Error {
70
+ refusal;
71
+ code = "EXTERNAL_COMPLETION_REFUSED";
72
+ constructor(refusal, message) {
73
+ super(message);
74
+ this.refusal = refusal;
75
+ this.name = "ExternalCompletionError";
76
+ }
77
+ get retryable() {
78
+ return this.refusal === "still_executing";
79
+ }
80
+ };
81
+ /** Maximum serialized UTF-8 evidence size. */
82
+ const MAX_APPROVAL_EVIDENCE_BYTES = 16384;
83
+ const APPROVAL_REQUESTED_EVENT = "ApprovalRequested";
84
+ const APPROVAL_DECIDED_EVENT = "ApprovalDecided";
85
+ const APPROVAL_EXPIRED_EVENT = "ApprovalExpired";
86
+ const APPROVAL_DECISION_REFUSED_EVENT = "ApprovalDecisionRefused";
87
+ //#endregion
88
+ //#region src/submission-refusal.ts
89
+ /** Validate and copy only the descriptor's declared fields, on both sides of storage. */
90
+ function submissionRefusalDescriptor(value) {
91
+ if (!value || typeof value !== "object") throw new Error("Invalid submission refusal descriptor.");
92
+ const fields = Object.getOwnPropertyDescriptors(value);
93
+ const data = (key) => fields[key]?.value;
94
+ const kind = data("class");
95
+ const keys = Reflect.ownKeys(value);
96
+ if ((kind === "entitlement" || kind === "authorization") && keys.length === 1) return { class: kind };
97
+ if (kind === "admission" && keys.every((key) => key === "class" || key === "refusal" || key === "refusalCode")) {
98
+ const refusal = data("refusal");
99
+ const code = data("refusalCode");
100
+ if (refusal === "invalid_parameters" && !Object.hasOwn(fields, "refusalCode")) return {
101
+ class: kind,
102
+ refusal
103
+ };
104
+ if (refusal === "refused" && (!Object.hasOwn(fields, "refusalCode") || typeof code === "string" && /^[a-z][A-Za-z0-9_.:-]{0,127}$/.test(code))) return {
105
+ class: kind,
106
+ refusal,
107
+ ...code === void 0 ? {} : { refusalCode: code }
108
+ };
109
+ }
110
+ throw new Error("Invalid submission refusal descriptor.");
111
+ }
112
+ function validateRefusalRecord(record) {
113
+ if (record?.status === "submission_refused") submissionRefusalDescriptor(record.submissionRefusal);
114
+ return record;
115
+ }
116
+ /** Reconstruct errors from fixed validated evidence, without row error text or parameters. */
117
+ function recordedSubmissionRefusal(record) {
118
+ const descriptor = submissionRefusalDescriptor(record.submissionRefusal);
119
+ return descriptor.class === "admission" ? new ActionAdmissionError(record.actionId, descriptor.refusal, descriptor.refusalCode, true) : new SubmissionRefusalError(record.actionId, descriptor.class, "", true);
120
+ }
121
+ /** Refusals enter the ledger through the narrow insert, rather than generic status writes. */
122
+ function assertInvocationWriteStatus(status) {
123
+ if (status === "submission_refused") throw new Error("submission_refused can only be inserted by recordSubmissionRefusal.");
124
+ }
125
+ //#endregion
126
+ //#region src/submission-outcome.ts
127
+ var SubmissionOutcomeUnsupportedError = class extends Error {
128
+ constructor() {
129
+ super("Store lacks getSubmissionOutcomeByKey.");
130
+ this.name = "SubmissionOutcomeUnsupportedError";
131
+ }
132
+ };
133
+ /** Copy receipt fields only, without exposing execution data. */
134
+ function submissionOutcomeProjection(row) {
135
+ return structuredClone({
136
+ tenantId: row.tenantId,
137
+ spaceId: row.spaceId,
138
+ actionId: row.actionId,
139
+ idempotencyKey: row.idempotencyKey,
140
+ id: row.id,
141
+ actionVersion: row.actionVersion,
142
+ actorId: row.actorId,
143
+ actorType: row.actorType,
144
+ authorizationBindingId: row.authorizationBindingId,
145
+ idempotencyActorId: row.idempotencyActorId,
146
+ idempotencyAuthorizationBindingId: row.idempotencyAuthorizationBindingId,
147
+ status: row.status,
148
+ submissionRefusal: row.submissionRefusal,
149
+ storedRecordsErased: row.storedRecordsErased
150
+ });
151
+ }
152
+ function createSubmissionOutcomeReader(store) {
153
+ return { async readSubmissionOutcome(input) {
154
+ const snapshot = {
155
+ tenantId: input.tenantId,
156
+ spaceId: input.spaceId,
157
+ actionId: input.actionId,
158
+ idempotencyKey: input.idempotencyKey,
159
+ actorId: input.actorId,
160
+ actorType: input.actorType,
161
+ authorizationBindingId: input.authorizationBindingId
162
+ };
163
+ for (const field of [
164
+ "tenantId",
165
+ "spaceId",
166
+ "actionId",
167
+ "idempotencyKey",
168
+ "actorId",
169
+ "actorType"
170
+ ]) if (typeof snapshot[field] !== "string") throw new TypeError(`Invalid ${field}.`);
171
+ if (!store.getSubmissionOutcomeByKey) throw new SubmissionOutcomeUnsupportedError();
172
+ const row = await store.getSubmissionOutcomeByKey({
173
+ tenantId: snapshot.tenantId,
174
+ spaceId: snapshot.spaceId,
175
+ actionId: snapshot.actionId,
176
+ idempotencyKey: snapshot.idempotencyKey
177
+ });
178
+ if (!row || (row.idempotencyActorId ?? row.actorId) !== snapshot.actorId || row.actorType !== snapshot.actorType || ((row.idempotencyAuthorizationBindingId ?? row.authorizationBindingId) || void 0) !== (snapshot.authorizationBindingId || void 0)) return { kind: "no_record" };
179
+ const identity = {
180
+ actionInvocationId: row.id,
181
+ actionVersion: row.actionVersion
182
+ };
183
+ if (row.storedRecordsErased) return {
184
+ ...identity,
185
+ kind: "records_erased",
186
+ recordedStatus: row.status
187
+ };
188
+ if (row.status === "submission_refused") return {
189
+ ...identity,
190
+ kind: "submission_refused",
191
+ submissionRefusal: submissionRefusalDescriptor(row.submissionRefusal)
192
+ };
193
+ return {
194
+ ...identity,
195
+ kind: "invocation",
196
+ status: row.status
197
+ };
198
+ } };
199
+ }
200
+ //#endregion
6
201
  //#region src/transaction-rollback.ts
7
202
  /** Evidence from a transaction provider that rollback completed successfully. */
8
203
  var ConfirmedTransactionRollback = class extends Error {
@@ -181,126 +376,6 @@ function createStoredRecordErasureHost(store, authorization) {
181
376
  } };
182
377
  }
183
378
  //#endregion
184
- //#region src/erasure-types.ts
185
- const HOST_RECORDS_ERASED_EVENT = "HostRecordsErased";
186
- /** Sentinel replacing required payload digests after stored-record erasure. */
187
- const ERASED_DIGEST = "erased";
188
- //#endregion
189
- //#region src/types.ts
190
- /** Durable Host lifecycle generation, independent from the npm package version. */
191
- const PLATFORM_HOST_CONTRACT_VERSION = 2;
192
- var IdempotencyConflictError = class extends Error {
193
- conflict;
194
- code = "IDEMPOTENCY_CONFLICT";
195
- constructor(conflict) {
196
- super(`Idempotency key "${conflict.idempotencyKey}" conflicts with invocation ${conflict.existingInvocationId}: ${conflict.reasons.join(", ")}`);
197
- this.conflict = conflict;
198
- this.name = "IdempotencyConflictError";
199
- }
200
- };
201
- /**
202
- * Thrown by `submitAction` when a declared admission check did not admit the
203
- * submission. By default Host writes nothing for it. With recordRefusals
204
- * enabled and an idempotency key, a definitive refusal can be recorded;
205
- * `recorded` confirms persistence of the terminal refusal. The message holds
206
- * the action id, the refusal and, for `refused`, the check's fixed code. It omits parameter
207
- * payloads and caught callback error text.
208
- */
209
- var ActionAdmissionError = class extends Error {
210
- actionId;
211
- refusal;
212
- refusalCode;
213
- recorded;
214
- code = "ACTION_ADMISSION_REFUSED";
215
- constructor(actionId, refusal, refusalCode, recorded = false) {
216
- super(`Action ${actionId} submission was not admitted (${refusal}${refusalCode ? `: ${refusalCode}` : ""}); ${recorded ? "the refusal was recorded under the idempotency key" : "nothing was recorded"}.`);
217
- this.actionId = actionId;
218
- this.refusal = refusal;
219
- this.refusalCode = refusalCode;
220
- this.recorded = recorded;
221
- this.name = "ActionAdmissionError";
222
- }
223
- };
224
- /** A definitive authorization-boundary refusal before an invocation is created. */
225
- var SubmissionRefusalError = class extends Error {
226
- actionId;
227
- refusal;
228
- recorded;
229
- code = "ACTION_SUBMISSION_REFUSED";
230
- constructor(actionId, refusal, message, recorded = false) {
231
- super(recorded ? `Action ${actionId} submission was refused (${refusal}); the refusal was recorded under the idempotency key.` : message);
232
- this.actionId = actionId;
233
- this.refusal = refusal;
234
- this.recorded = recorded;
235
- this.name = "SubmissionRefusalError";
236
- }
237
- };
238
- /**
239
- * Why a completion was not applied, typed so a webhook handler can answer
240
- * the external system correctly: `retryable` means the invocation is still
241
- * being finalized and the same callback will be accepted once it parks, so
242
- * answer with something the sender retries; anything else is permanent and
243
- * a retry will get the same refusal without a configuration/input change.
244
- * `redaction_failed` means completion result or error-text redaction failed before writes;
245
- * it is not retryable.
246
- */
247
- var ExternalCompletionError = class extends Error {
248
- refusal;
249
- code = "EXTERNAL_COMPLETION_REFUSED";
250
- constructor(refusal, message) {
251
- super(message);
252
- this.refusal = refusal;
253
- this.name = "ExternalCompletionError";
254
- }
255
- get retryable() {
256
- return this.refusal === "still_executing";
257
- }
258
- };
259
- /** Maximum serialized UTF-8 evidence size. */
260
- const MAX_APPROVAL_EVIDENCE_BYTES = 16384;
261
- const APPROVAL_REQUESTED_EVENT = "ApprovalRequested";
262
- const APPROVAL_DECIDED_EVENT = "ApprovalDecided";
263
- const APPROVAL_EXPIRED_EVENT = "ApprovalExpired";
264
- const APPROVAL_DECISION_REFUSED_EVENT = "ApprovalDecisionRefused";
265
- //#endregion
266
- //#region src/submission-refusal.ts
267
- /** Validate and copy only the descriptor's declared fields, on both sides of storage. */
268
- function submissionRefusalDescriptor(value) {
269
- if (!value || typeof value !== "object") throw new Error("Invalid submission refusal descriptor.");
270
- const fields = Object.getOwnPropertyDescriptors(value);
271
- const data = (key) => fields[key]?.value;
272
- const kind = data("class");
273
- const keys = Reflect.ownKeys(value);
274
- if ((kind === "entitlement" || kind === "authorization") && keys.length === 1) return { class: kind };
275
- if (kind === "admission" && keys.every((key) => key === "class" || key === "refusal" || key === "refusalCode")) {
276
- const refusal = data("refusal");
277
- const code = data("refusalCode");
278
- if (refusal === "invalid_parameters" && !Object.hasOwn(fields, "refusalCode")) return {
279
- class: kind,
280
- refusal
281
- };
282
- if (refusal === "refused" && (!Object.hasOwn(fields, "refusalCode") || typeof code === "string" && /^[a-z][A-Za-z0-9_.:-]{0,127}$/.test(code))) return {
283
- class: kind,
284
- refusal,
285
- ...code === void 0 ? {} : { refusalCode: code }
286
- };
287
- }
288
- throw new Error("Invalid submission refusal descriptor.");
289
- }
290
- function validateRefusalRecord(record) {
291
- if (record?.status === "submission_refused") submissionRefusalDescriptor(record.submissionRefusal);
292
- return record;
293
- }
294
- /** Reconstruct errors from fixed validated evidence, without row error text or parameters. */
295
- function recordedSubmissionRefusal(record) {
296
- const descriptor = submissionRefusalDescriptor(record.submissionRefusal);
297
- return descriptor.class === "admission" ? new ActionAdmissionError(record.actionId, descriptor.refusal, descriptor.refusalCode, true) : new SubmissionRefusalError(record.actionId, descriptor.class, "", true);
298
- }
299
- /** Refusals enter the ledger through the narrow insert, rather than generic status writes. */
300
- function assertInvocationWriteStatus(status) {
301
- if (status === "submission_refused") throw new Error("submission_refused can only be inserted by recordSubmissionRefusal.");
302
- }
303
- //#endregion
304
379
  //#region src/action-finalization.ts
305
380
  /** Remove private preparation and restore its compatible protocol on settlement. */
306
381
  function finalizationSettlementPatch(record, patch) {
@@ -1392,7 +1467,7 @@ function snapshotSubmission(input) {
1392
1467
  ...causationId != null ? { causationId } : {},
1393
1468
  ...executionReason != null ? { executionReason } : {},
1394
1469
  ...idempotencyKey != null ? { idempotencyKey } : {},
1395
- ...authorizationBindingId != null ? { authorizationBindingId } : {},
1470
+ ...authorizationBindingId != null && authorizationBindingId !== "" ? { authorizationBindingId } : {},
1396
1471
  parametersArePrivate: false
1397
1472
  };
1398
1473
  Object.defineProperties(live, {
@@ -4960,6 +5035,7 @@ function createGovernedActionHost(options) {
4960
5035
  }
4961
5036
  return {
4962
5037
  ...createStoredRecordErasureHost(options.store, options.authorization),
5038
+ ...createSubmissionOutcomeReader(options.store),
4963
5039
  submitAction,
4964
5040
  hasStagedInvocationActions: declaresStaging,
4965
5041
  ...stagedStore ? { dispatchStagedInvocations } : {},
@@ -5073,7 +5149,7 @@ function idempotencyConflict(existing, incoming) {
5073
5149
  const reasons = [];
5074
5150
  if (!existing.storedRecordsErased && digestParameters(existing.agentProposal ?? null) !== digestParameters(incoming.agentProposal ?? null)) reasons.push("agent_proposal");
5075
5151
  if ((existing.idempotencyActorId ?? existing.actorId) !== incoming.actorId) reasons.push("actor");
5076
- if ((existing.idempotencyAuthorizationBindingId ?? existing.authorizationBindingId) !== incoming.authorizationBindingId) reasons.push("authority_binding");
5152
+ if (((existing.idempotencyAuthorizationBindingId ?? existing.authorizationBindingId) || void 0) !== (incoming.authorizationBindingId || void 0)) reasons.push("authority_binding");
5077
5153
  if (existing.actionVersion !== incoming.actionVersion) reasons.push("action_version");
5078
5154
  if (!existing.storedRecordsErased && existing.parameterDigest !== incoming.parameterDigest) reasons.push("parameters");
5079
5155
  return reasons.length ? {
@@ -5090,7 +5166,7 @@ function isKnownAdapterOutcome(outcome) {
5090
5166
  function refusalIdentityConflict(row, submission, actionVersion, parameterDigest) {
5091
5167
  const reasons = [];
5092
5168
  if (row.actorId !== submission.actorId || row.actorType !== submission.actorType) reasons.push("actor");
5093
- if (row.authorizationBindingId !== submission.authorizationBindingId) reasons.push("authority_binding");
5169
+ if ((row.authorizationBindingId || void 0) !== (submission.authorizationBindingId || void 0)) reasons.push("authority_binding");
5094
5170
  if (row.actionVersion !== actionVersion) reasons.push("action_version");
5095
5171
  if (!row.storedRecordsErased && row.parameterDigest !== void 0 && row.parameterDigest !== parameterDigest) reasons.push("parameters");
5096
5172
  return reasons.length ? {
@@ -5715,6 +5791,13 @@ function emitRelayTelemetry(telemetry, name, occurredAt, workerId, attributes) {
5715
5791
  //#region src/memory-store.ts
5716
5792
  var MemoryPlatformHostStore = class {
5717
5793
  db;
5794
+ async getSubmissionOutcomeByKey(key) {
5795
+ const row = this.invocations.find((row) => row.tenantId === key.tenantId && row.spaceId === key.spaceId && row.actionId === key.actionId && row.idempotencyKey !== void 0 && row.idempotencyKey === key.idempotencyKey);
5796
+ return row ? submissionOutcomeProjection({
5797
+ ...row,
5798
+ idempotencyKey: row.idempotencyKey
5799
+ }) : void 0;
5800
+ }
5718
5801
  transactionalOutbox;
5719
5802
  transactionalApprovalLifecycle;
5720
5803
  transactionalApprovalAuthorization = true;
@@ -7481,6 +7564,34 @@ var PostgresPlatformHostStore = class PostgresPlatformHostStore {
7481
7564
  async transaction(run) {
7482
7565
  return run(this.db);
7483
7566
  }
7567
+ async getSubmissionOutcomeByKey(key) {
7568
+ const row = (await this.sql.query(`SELECT id,tenant_id,space_id,action_id,idempotency_key,action_version,actor_id,actor_type,
7569
+ authorization_binding_id,idempotency_actor_id,idempotency_authorization_binding_id,status,
7570
+ submission_refusal,records_erased_at FROM fabric_platform.action_invocations
7571
+ WHERE tenant_id=$1 AND space_id=$2 AND action_id=$3 AND idempotency_key=$4`, [
7572
+ key.tenantId,
7573
+ key.spaceId,
7574
+ key.actionId,
7575
+ key.idempotencyKey
7576
+ ])).rows[0];
7577
+ if (!row) return void 0;
7578
+ return {
7579
+ tenantId: row.tenant_id,
7580
+ spaceId: row.space_id,
7581
+ actionId: row.action_id,
7582
+ idempotencyKey: row.idempotency_key,
7583
+ id: row.id,
7584
+ actionVersion: Number(row.action_version),
7585
+ actorId: row.actor_id,
7586
+ actorType: row.actor_type,
7587
+ authorizationBindingId: row.authorization_binding_id ?? void 0,
7588
+ idempotencyActorId: row.idempotency_actor_id ?? void 0,
7589
+ idempotencyAuthorizationBindingId: row.idempotency_authorization_binding_id ?? void 0,
7590
+ status: row.status,
7591
+ submissionRefusal: row.submission_refusal ?? void 0,
7592
+ storedRecordsErased: row.records_erased_at ? true : void 0
7593
+ };
7594
+ }
7484
7595
  async createActionInvocation(input) {
7485
7596
  assertInvocationWriteStatus(input.status);
7486
7597
  const now = /* @__PURE__ */ new Date();
@@ -14299,6 +14410,161 @@ async function runInvocationIdempotencyChecks(subject) {
14299
14410
  };
14300
14411
  }
14301
14412
  //#endregion
14302
- export { APPROVAL_DECIDED_EVENT, APPROVAL_DECISION_REFUSED_EVENT, APPROVAL_EXPIRED_EVENT, APPROVAL_REQUESTED_EVENT, ActionAdmissionError, ConfirmedTransactionRollback, ERASED_DIGEST, ErasureBusyError, ExternalCompletionError, HOST_RECORDS_ERASED_EVENT, IdempotencyConflictError, LEGACY_BATCH_DIGEST, MAX_APPROVAL_EVIDENCE_BYTES, MAX_STAGED_INVOCATIONS, MAX_STAGED_INVOCATION_BATCH_BYTES, MAX_STAGED_INVOCATION_BYTES, MAX_STAGED_INVOCATION_KEY_LENGTH, MemoryPlatformHostStore, PARAMETER_DIGEST_ALGORITHM, PLATFORM_HOST_CONTRACT_VERSION, PLATFORM_HOST_HEALTH_CONTRACT_VERSION, PLATFORM_HOST_METRIC_NAMES, PostgresPlatformHostStore, STAGED_INVOCATION_KEY_PATTERN, STAGED_INVOCATION_PRINCIPAL_PATTERN, StoredRecordErasureAuthorizationError, StoredRecordsErasedError, SubmissionRefusalError, applyPostgresMigrations, approvalChecks, asStagedInvocationStore, canonicalJson, cloneOutboxRecord, cloneStagedInvocationRecord, createDurableSagaParentLifecycle, createGovernedActionHost, createStoreBackedActionDispatcher, createStoredRecordErasureHost, digestParameters, emitPlatformHostTelemetry, erasureChecks, eventAppendChecks, getPlatformHostHealthSnapshot, invocationIdempotencyChecks, isConfirmedTransactionRollback, isTransientPostgresTransactionError, outboxActorDisclosure, outboxRelayChecks, outboxRequeueChecks, prepareStagedInvocationBatch, runApprovalChecks, runErasureChecks, runEventAppendChecks, runInvocationIdempotencyChecks, runOutboxRelayChecks, runOutboxRelayCycle, runOutboxRequeueChecks, runPlatformActionWorker, runPlatformActionWorkerCycle, runStagedInvocationChecks, runTenantIsolationChecks, stagedInvocationChecks, stagedInvocationRequestIdentity, stagedInvocationRequestMatches, tenantIsolationChecks, toEnterpriseEventEnvelope };
14413
+ //#region src/submission-outcome-conformance.ts
14414
+ /**
14415
+ * Isolated destructive probe with fresh identities and timestamps from new Date().
14416
+ * Refusal and erasure sections run only with their respective store capabilities;
14417
+ * returned evidence identifies each unexercised optional section with `skipped:`.
14418
+ */
14419
+ async function checkSubmissionOutcomeStore(store) {
14420
+ if (!store.getSubmissionOutcomeByKey) throw new Error("Missing getSubmissionOutcomeByKey");
14421
+ const evidence = ["verified: submission outcome metadata and exact key scope"];
14422
+ const id = globalThis.crypto.randomUUID();
14423
+ const input = {
14424
+ id,
14425
+ tenantId: `tenant-${id}`,
14426
+ spaceId: `space-${id}`,
14427
+ actionId: "probe.read",
14428
+ idempotencyKey: "",
14429
+ actionVersion: 7,
14430
+ actorId: "actor",
14431
+ actorType: "natural_person",
14432
+ authorizationBindingId: "authorization-binding",
14433
+ idempotencyActorId: "idempotency-actor",
14434
+ idempotencyAuthorizationBindingId: "idempotency-binding",
14435
+ status: "pending",
14436
+ parameters: {},
14437
+ result: {},
14438
+ correlationId: id
14439
+ };
14440
+ const key = {
14441
+ tenantId: input.tenantId,
14442
+ spaceId: input.spaceId,
14443
+ actionId: input.actionId,
14444
+ idempotencyKey: ""
14445
+ };
14446
+ const expected = {
14447
+ ...key,
14448
+ id,
14449
+ actionVersion: input.actionVersion,
14450
+ actorId: input.actorId,
14451
+ actorType: input.actorType,
14452
+ authorizationBindingId: input.authorizationBindingId,
14453
+ idempotencyActorId: input.idempotencyActorId,
14454
+ idempotencyAuthorizationBindingId: input.idempotencyAuthorizationBindingId,
14455
+ status: input.status,
14456
+ submissionRefusal: void 0,
14457
+ storedRecordsErased: void 0
14458
+ };
14459
+ const assertProjection = (row, receipt) => {
14460
+ for (const field of Object.keys(receipt)) assertEqual(row?.[field], receipt[field], `receipt ${field}`);
14461
+ };
14462
+ assertEqual(await store.getSubmissionOutcomeByKey(key), void 0, "absence");
14463
+ await store.createActionInvocation(input);
14464
+ const before = structuredClone(await store.getActionInvocation(id, key.tenantId, key.spaceId));
14465
+ for (const field of [
14466
+ "tenantId",
14467
+ "spaceId",
14468
+ "actionId",
14469
+ "idempotencyKey"
14470
+ ]) assertEqual(await store.getSubmissionOutcomeByKey({
14471
+ ...key,
14472
+ [field]: "other.value"
14473
+ }), void 0, field);
14474
+ const spaced = {
14475
+ ...input,
14476
+ id: `${id}-spaced`,
14477
+ idempotencyKey: " key "
14478
+ };
14479
+ await store.createActionInvocation(spaced);
14480
+ assertProjection(await store.getSubmissionOutcomeByKey(spaced), {
14481
+ ...expected,
14482
+ id: spaced.id,
14483
+ idempotencyKey: spaced.idempotencyKey
14484
+ });
14485
+ assertEqual(await store.getSubmissionOutcomeByKey({
14486
+ ...key,
14487
+ idempotencyKey: "key"
14488
+ }), void 0, "key is not trimmed");
14489
+ const unkeyed = {
14490
+ ...input,
14491
+ id: `${id}-unkeyed`,
14492
+ actionId: "probe.unkeyed",
14493
+ idempotencyKey: void 0
14494
+ };
14495
+ await store.createActionInvocation(unkeyed);
14496
+ assertEqual(await store.getSubmissionOutcomeByKey({
14497
+ ...key,
14498
+ actionId: unkeyed.actionId
14499
+ }), void 0, "null key cannot match empty key");
14500
+ const row = await store.getSubmissionOutcomeByKey(key);
14501
+ assertProjection(row, expected);
14502
+ if (row) row.actorId = "changed";
14503
+ assertProjection(await store.getSubmissionOutcomeByKey(key), expected);
14504
+ assertEqual(await store.getActionInvocation(id, key.tenantId, key.spaceId), before, "read writes nothing");
14505
+ const erasureReceipts = [{
14506
+ ...expected,
14507
+ status: "completed"
14508
+ }];
14509
+ if (store.recordSubmissionRefusal) {
14510
+ const now = /* @__PURE__ */ new Date();
14511
+ const refused = {
14512
+ ...key,
14513
+ id: `${id}-refused`,
14514
+ idempotencyKey: "refused",
14515
+ actionVersion: 8,
14516
+ actorId: "refused-actor",
14517
+ actorType: "agent",
14518
+ authorizationBindingId: "refused-binding",
14519
+ correlationId: id,
14520
+ submissionRefusal: { class: "authorization" },
14521
+ createdAt: now,
14522
+ updatedAt: now
14523
+ };
14524
+ await store.recordSubmissionRefusal(refused);
14525
+ const refusedReceipt = {
14526
+ ...expected,
14527
+ id: refused.id,
14528
+ idempotencyKey: refused.idempotencyKey,
14529
+ actionVersion: refused.actionVersion,
14530
+ actorId: refused.actorId,
14531
+ actorType: refused.actorType,
14532
+ authorizationBindingId: refused.authorizationBindingId,
14533
+ idempotencyActorId: void 0,
14534
+ idempotencyAuthorizationBindingId: void 0,
14535
+ status: "submission_refused",
14536
+ submissionRefusal: refused.submissionRefusal
14537
+ };
14538
+ assertProjection(await store.getSubmissionOutcomeByKey(refused), refusedReceipt);
14539
+ erasureReceipts.push(refusedReceipt);
14540
+ evidence.push("verified: recorded refusal receipt");
14541
+ } else evidence.push("skipped: store lacks recordSubmissionRefusal");
14542
+ const erasable = store;
14543
+ if (erasable.eraseStoredRecords) {
14544
+ await store.updateActionInvocation(id, key.tenantId, key.spaceId, { status: "completed" });
14545
+ for (const receipt of erasureReceipts) {
14546
+ await erasable.eraseStoredRecords({
14547
+ tenantId: key.tenantId,
14548
+ spaceId: key.spaceId,
14549
+ actorId: "operator",
14550
+ actorType: "natural_person",
14551
+ requestId: globalThis.crypto.randomUUID(),
14552
+ targets: [{
14553
+ kind: "invocation",
14554
+ invocationId: receipt.id
14555
+ }],
14556
+ redact: (view) => view
14557
+ });
14558
+ assertProjection(await store.getSubmissionOutcomeByKey(receipt), {
14559
+ ...receipt,
14560
+ storedRecordsErased: true
14561
+ });
14562
+ }
14563
+ evidence.push("verified: erased receipts for created probe rows");
14564
+ } else evidence.push("skipped: store lacks eraseStoredRecords");
14565
+ return evidence;
14566
+ }
14567
+ //#endregion
14568
+ export { APPROVAL_DECIDED_EVENT, APPROVAL_DECISION_REFUSED_EVENT, APPROVAL_EXPIRED_EVENT, APPROVAL_REQUESTED_EVENT, ActionAdmissionError, ConfirmedTransactionRollback, ERASED_DIGEST, ErasureBusyError, ExternalCompletionError, HOST_RECORDS_ERASED_EVENT, IdempotencyConflictError, LEGACY_BATCH_DIGEST, MAX_APPROVAL_EVIDENCE_BYTES, MAX_STAGED_INVOCATIONS, MAX_STAGED_INVOCATION_BATCH_BYTES, MAX_STAGED_INVOCATION_BYTES, MAX_STAGED_INVOCATION_KEY_LENGTH, MemoryPlatformHostStore, PARAMETER_DIGEST_ALGORITHM, PLATFORM_HOST_CONTRACT_VERSION, PLATFORM_HOST_HEALTH_CONTRACT_VERSION, PLATFORM_HOST_METRIC_NAMES, PostgresPlatformHostStore, STAGED_INVOCATION_KEY_PATTERN, STAGED_INVOCATION_PRINCIPAL_PATTERN, StoredRecordErasureAuthorizationError, StoredRecordsErasedError, SubmissionOutcomeUnsupportedError, SubmissionRefusalError, applyPostgresMigrations, approvalChecks, asStagedInvocationStore, canonicalJson, checkSubmissionOutcomeStore, cloneOutboxRecord, cloneStagedInvocationRecord, createDurableSagaParentLifecycle, createGovernedActionHost, createStoreBackedActionDispatcher, createStoredRecordErasureHost, digestParameters, emitPlatformHostTelemetry, erasureChecks, eventAppendChecks, getPlatformHostHealthSnapshot, invocationIdempotencyChecks, isConfirmedTransactionRollback, isTransientPostgresTransactionError, outboxActorDisclosure, outboxRelayChecks, outboxRequeueChecks, prepareStagedInvocationBatch, runApprovalChecks, runErasureChecks, runEventAppendChecks, runInvocationIdempotencyChecks, runOutboxRelayChecks, runOutboxRelayCycle, runOutboxRequeueChecks, runPlatformActionWorker, runPlatformActionWorkerCycle, runStagedInvocationChecks, runTenantIsolationChecks, stagedInvocationChecks, stagedInvocationRequestIdentity, stagedInvocationRequestMatches, tenantIsolationChecks, toEnterpriseEventEnvelope };
14303
14569
 
14304
14570
  //# sourceMappingURL=index.js.map