@fabricorg/platform-host 7.1.0 → 7.1.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
@@ -348,6 +348,14 @@ interface ExternalCompletion {
348
348
  }
349
349
  interface RecordedExternalCompletion extends ExternalCompletion {
350
350
  recordedAt: Date;
351
+ /**
352
+ * Canonical digest of provider, reference, outcome, result, error, and
353
+ * evidence. A repeat with the same digest is the same news; a repeat with
354
+ * the same outcome and a different digest is changed evidence, and is a
355
+ * contradiction like a flipped outcome. Observation time is left out,
356
+ * since a redelivery legitimately carries a new one.
357
+ */
358
+ digest: string;
351
359
  }
352
360
  interface ReconcileOverdueCompletionsInput {
353
361
  tenantId?: string;
package/dist/index.d.ts CHANGED
@@ -348,6 +348,14 @@ interface ExternalCompletion {
348
348
  }
349
349
  interface RecordedExternalCompletion extends ExternalCompletion {
350
350
  recordedAt: Date;
351
+ /**
352
+ * Canonical digest of provider, reference, outcome, result, error, and
353
+ * evidence. A repeat with the same digest is the same news; a repeat with
354
+ * the same outcome and a different digest is changed evidence, and is a
355
+ * contradiction like a flipped outcome. Observation time is left out,
356
+ * since a redelivery legitimately carries a new one.
357
+ */
358
+ digest: string;
351
359
  }
352
360
  interface ReconcileOverdueCompletionsInput {
353
361
  tenantId?: string;
package/dist/index.js CHANGED
@@ -1350,11 +1350,12 @@ function createGovernedActionHost(options) {
1350
1350
  if (completion.provider !== void 0 && recorded.provider !== void 0 && completion.provider !== recorded.provider) {
1351
1351
  throw new ExternalCompletionError("provider_mismatch", `ActionInvocation ${actionInvocationId} was completed by "${recorded.provider}", not "${completion.provider}".`);
1352
1352
  }
1353
- if (recorded.outcome === completion.outcome) {
1353
+ const incomingDigest = completionDigest(completion, recorded.provider ?? completion.provider);
1354
+ if (recorded.digest === incomingDigest) {
1354
1355
  return withConsistency(actionResult(invocation), invocation.actionId);
1355
1356
  }
1356
- const message = `External operation "${completion.externalReference}" reported ${completion.outcome} after reporting ${recorded.outcome}.`;
1357
- await recordCompletionReconciliation(invocation, { ...completion, kind: `contradicted:${completion.outcome}` }, message);
1357
+ const message = recorded.outcome === completion.outcome ? `External operation "${completion.externalReference}" reported ${completion.outcome} again with different evidence.` : `External operation "${completion.externalReference}" reported ${completion.outcome} after reporting ${recorded.outcome}.`;
1358
+ await recordCompletionReconciliation(invocation, { ...completion, kind: `contradicted:${incomingDigest.slice(0, 16)}` }, message);
1358
1359
  await appendEvent(invocation, {
1359
1360
  eventType: "ExternalOperationContradicted",
1360
1361
  subjectType: "AdapterInvocation",
@@ -1384,7 +1385,8 @@ function createGovernedActionHost(options) {
1384
1385
  ...completion,
1385
1386
  provider: completion.provider ?? pending.provider,
1386
1387
  observedAt: completion.observedAt instanceof Date ? completion.observedAt.toISOString() : completion.observedAt,
1387
- recordedAt
1388
+ recordedAt,
1389
+ digest: completionDigest(completion, completion.provider ?? pending.provider)
1388
1390
  };
1389
1391
  const subject = { subjectType: "AdapterInvocation", subjectId: pending.adapterInvocationId };
1390
1392
  const settles = invocation.status === "running" && pending.parkedAt !== void 0 || invocation.status === "reconciliation_required" && invocation.error?.includes("did not complete by") === true;
@@ -1423,6 +1425,16 @@ function createGovernedActionHost(options) {
1423
1425
  if (settles) emitInvocationStatusTelemetry(invocation, "failed", options.telemetry, now(), invocation.status);
1424
1426
  return withConsistency(actionResult({ ...invocation, ...patch }), invocation.actionId);
1425
1427
  }
1428
+ function completionDigest(completion, provider) {
1429
+ return createHash("sha256").update(canonicalJson({
1430
+ provider: provider ?? null,
1431
+ externalReference: completion.externalReference,
1432
+ outcome: completion.outcome,
1433
+ result: completion.result ?? null,
1434
+ error: completion.error ?? null,
1435
+ evidenceReferences: completion.evidenceReferences ?? null
1436
+ })).digest("hex");
1437
+ }
1426
1438
  async function recordCompletionReconciliation(invocation, completion, reason) {
1427
1439
  const governanceStore = asGovernanceStore(options.store);
1428
1440
  if (!governanceStore) throw new Error("External completion reconciliation requires a governance-capable store.");