@dvmkit/sdk 0.10.0 → 0.11.0-rc.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.
Files changed (57) hide show
  1. package/dist/{chunk-3Q6FTZDB.js → chunk-3YFFZLW7.js} +2 -2
  2. package/dist/{chunk-YOSZIWC3.js → chunk-55DVLBCD.js} +135 -8
  3. package/dist/{chunk-VMHYGKWT.js → chunk-566CFXGU.js} +9 -1
  4. package/dist/chunk-5H7P3ORL.js +92 -0
  5. package/dist/{chunk-3R7NQA3Z.js → chunk-7KQLPVV7.js} +1 -1
  6. package/dist/{chunk-QNKK7SPY.js → chunk-AIQGF52I.js} +350 -71
  7. package/dist/{chunk-JNECYURT.js → chunk-D4FFS3IS.js} +1 -1
  8. package/dist/{chunk-VTHYQCDB.js → chunk-E7ZBCT5U.js} +1 -1
  9. package/dist/{chunk-MMGSYMIC.js → chunk-EP7OVWI5.js} +1 -1
  10. package/dist/{chunk-LDIPVPKI.js → chunk-FFRTAQUW.js} +1 -1
  11. package/dist/chunk-H3GGFEE5.js +57 -0
  12. package/dist/{chunk-M3T5JYDJ.js → chunk-HNXMWHLU.js} +1 -1
  13. package/dist/{chunk-Y6DO5WSO.js → chunk-HQPQ4PFI.js} +43 -21
  14. package/dist/{chunk-UKD3R2KU.js → chunk-HW2UCOOC.js} +4 -55
  15. package/dist/{chunk-FFXXGCVG.js → chunk-ITRSITFP.js} +1 -1
  16. package/dist/{chunk-MU3IE434.js → chunk-JLT2YIBS.js} +267 -52
  17. package/dist/{chunk-STJBBVNL.js → chunk-K2ZNIQZP.js} +458 -69
  18. package/dist/{chunk-KFCEXCZM.js → chunk-KJZVTWMX.js} +1 -1
  19. package/dist/{chunk-W3VGCNG6.js → chunk-LDKV4UHI.js} +1 -1
  20. package/dist/{chunk-AJ6UHXUA.js → chunk-M4NFYEEY.js} +1 -1
  21. package/dist/{chunk-MB6R4XPZ.js → chunk-NS2OMIU7.js} +15 -3
  22. package/dist/{chunk-VUGEEMLT.js → chunk-RPUFWIT4.js} +11 -89
  23. package/dist/{chunk-EWZFANHJ.js → chunk-VWIMQ5H5.js} +1 -1
  24. package/dist/{chunk-2XQLL6N6.js → chunk-WFCVS6QB.js} +2 -2
  25. package/dist/{chunk-QGNXYVBO.js → chunk-Z4FOPROB.js} +17 -0
  26. package/dist/{content-retention-CrILCNxk.d.ts → content-retention-ySTD1wwZ.d.ts} +1 -1
  27. package/dist/{credit-ledger-4PZNAMDT.js → credit-ledger-4CBYLSLY.js} +6 -3
  28. package/dist/{credit-menu-VwX_6KM7.d.ts → credit-menu-BYySnZBk.d.ts} +6 -4
  29. package/dist/{fx-BM5h4o5S.d.ts → fx-DVTNs9q1.d.ts} +1 -1
  30. package/dist/index.d.ts +5 -5
  31. package/dist/index.js +6 -5
  32. package/dist/internal/caller.d.ts +6 -6
  33. package/dist/internal/caller.js +26 -22
  34. package/dist/internal/server.d.ts +7 -7
  35. package/dist/internal/server.js +24 -22
  36. package/dist/{job-store-92v8e1LQ.d.ts → job-store-DRwXCJDI.d.ts} +1 -1
  37. package/dist/{lightning-backend-9oky_wJi.d.ts → lightning-backend-BX5YvjnM.d.ts} +1 -1
  38. package/dist/memory-credit-ledger-DKCN7FY2.js +14 -0
  39. package/dist/{mpp-setup-FURD45LI.js → mpp-setup-JU3MT7QX.js} +3 -3
  40. package/dist/{payout-reporter-HVJ6WUF3.js → payout-reporter-NXWQVKXW.js} +2 -2
  41. package/dist/{postgres-consumed-credential-store-WO6OCFXZ.js → postgres-consumed-credential-store-WXJLM6J7.js} +1 -1
  42. package/dist/{postgres-job-store-FSRUPYH2.js → postgres-job-store-WIIVRZPD.js} +2 -2
  43. package/dist/{postgres-replay-store-XXJSA457.js → postgres-replay-store-QJ4FG6AR.js} +2 -2
  44. package/dist/{pricing-X4XU5HXY.js → pricing-KCLENT2C.js} +3 -3
  45. package/dist/{revenue-reporter-EV3K5L6B.js → revenue-reporter-HD4GMXGD.js} +2 -2
  46. package/dist/server/index.d.ts +29 -12
  47. package/dist/server/index.js +31 -29
  48. package/dist/{step-cache--7yA5Ihr.d.ts → step-cache-d-VKen4K.d.ts} +147 -13
  49. package/dist/{tempo-charge-store-5JOIYUIH.js → tempo-charge-store-7LTUAQHQ.js} +2 -2
  50. package/dist/{tempo-session-store-S6QMLQUA.js → tempo-session-store-LYAIDR2B.js} +3 -3
  51. package/dist/testing/index.d.ts +3 -3
  52. package/dist/testing/index.js +6 -6
  53. package/dist/{usd-C7lvLvLk.d.ts → usd-DSroMJ--.d.ts} +1 -1
  54. package/dist/{x402-EN5LSOVM.js → x402-RUJ4LUKF.js} +3 -3
  55. package/package.json +1 -1
  56. package/recipes/postgres/financial-export-v1.sql +19 -5
  57. package/dist/memory-credit-ledger-RP7DT5IY.js +0 -11
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-XRLKGCAW.js";
6
6
  import {
7
7
  x402EarnedCeiling
8
- } from "./chunk-FFXXGCVG.js";
8
+ } from "./chunk-ITRSITFP.js";
9
9
  import {
10
10
  createX402FacilitatorAuthHeaders,
11
11
  createX402FacilitatorClient,
@@ -14,16 +14,13 @@ import {
14
14
  verifyWithFacilitator,
15
15
  withPaymentDiagnostics,
16
16
  withPaymentStage
17
- } from "./chunk-VTHYQCDB.js";
17
+ } from "./chunk-E7ZBCT5U.js";
18
18
  import {
19
19
  MemoryCreditLedger
20
- } from "./chunk-YOSZIWC3.js";
20
+ } from "./chunk-55DVLBCD.js";
21
21
  import {
22
22
  PostgresJobStore
23
- } from "./chunk-Y6DO5WSO.js";
24
- import {
25
- parseStoreValue
26
- } from "./chunk-YG7G4DPZ.js";
23
+ } from "./chunk-HQPQ4PFI.js";
27
24
  import {
28
25
  SIGNED_ENVELOPE_FIELDS,
29
26
  SignedRequestError,
@@ -31,7 +28,7 @@ import {
31
28
  humanizeZodIssues,
32
29
  signedRequestInput,
33
30
  zodIssuesOf
34
- } from "./chunk-2XQLL6N6.js";
31
+ } from "./chunk-WFCVS6QB.js";
35
32
  import {
36
33
  MemoryJobStore,
37
34
  UNCAPPABLE_ASK_TOTAL,
@@ -40,7 +37,7 @@ import {
40
37
  isJobRetentionStore,
41
38
  isReceiptIssuingStore,
42
39
  isStreamableJobStore
43
- } from "./chunk-KFCEXCZM.js";
40
+ } from "./chunk-KJZVTWMX.js";
44
41
  import {
45
42
  ContentHoldError,
46
43
  JobCancelledError,
@@ -66,7 +63,6 @@ import {
66
63
  attestedDvmIdentity,
67
64
  buildAdminChallenge,
68
65
  checkMintHealth,
69
- computeResultHash,
70
66
  createMonotonicClock,
71
67
  formatStaleness,
72
68
  getWallet,
@@ -75,12 +71,9 @@ import {
75
71
  isX402SettlementReconciliationReason,
76
72
  parseTraceparent,
77
73
  receiptPubkeyFromSecret,
78
- signDrainReceipt,
79
- signFundingReceipt,
80
- signReceipt,
81
74
  toLockPubkey,
82
75
  verifyChallenge
83
- } from "./chunk-VUGEEMLT.js";
76
+ } from "./chunk-RPUFWIT4.js";
84
77
  import {
85
78
  X402_BATCH_SETTLEMENT_SCHEME,
86
79
  X402_DEFAULT_FACILITATOR,
@@ -103,12 +96,11 @@ import {
103
96
  import {
104
97
  RAIL_REFUNDABLE,
105
98
  canonicalRequestPath,
106
- canonicalize,
107
99
  isZodSchema
108
- } from "./chunk-UKD3R2KU.js";
100
+ } from "./chunk-HW2UCOOC.js";
109
101
  import {
110
102
  createConsoleLogger
111
- } from "./chunk-3R7NQA3Z.js";
103
+ } from "./chunk-7KQLPVV7.js";
112
104
  import {
113
105
  StepCache
114
106
  } from "./chunk-BQJIFRAZ.js";
@@ -125,7 +117,7 @@ import {
125
117
  roundUsd,
126
118
  satsToFiat,
127
119
  validateCurrency
128
- } from "./chunk-W3VGCNG6.js";
120
+ } from "./chunk-LDKV4UHI.js";
129
121
  import {
130
122
  CreditLedger,
131
123
  CreditLedgerError,
@@ -141,17 +133,29 @@ import {
141
133
  isNonChannelBitcoinRail,
142
134
  netOwedSats,
143
135
  x402SettlementPending
144
- } from "./chunk-STJBBVNL.js";
136
+ } from "./chunk-K2ZNIQZP.js";
137
+ import {
138
+ parseStoreValue
139
+ } from "./chunk-YG7G4DPZ.js";
145
140
  import {
146
141
  withSdkInitLock
147
142
  } from "./chunk-COYKJRMM.js";
143
+ import {
144
+ computeResultHash,
145
+ signDrainReceipt,
146
+ signFundingReceipt,
147
+ signReceipt
148
+ } from "./chunk-5H7P3ORL.js";
148
149
  import {
149
150
  withPostgresRecoveryContext
150
- } from "./chunk-MMGSYMIC.js";
151
+ } from "./chunk-EP7OVWI5.js";
151
152
  import {
152
153
  DvmError,
153
154
  x402NetworkByCaip2
154
155
  } from "./chunk-BPYMEMQX.js";
156
+ import {
157
+ canonicalize
158
+ } from "./chunk-H3GGFEE5.js";
155
159
  import {
156
160
  TEMPO_MODERATO_CHAIN_ID,
157
161
  TEMPO_MODERATO_RPC,
@@ -162,17 +166,17 @@ import {
162
166
  installHostedTempoCloseGuard,
163
167
  isTempoInsufficientFundsError,
164
168
  resolveTempoNetwork
165
- } from "./chunk-MU3IE434.js";
169
+ } from "./chunk-JLT2YIBS.js";
166
170
  import {
167
171
  loggers,
168
172
  pinDimension,
169
173
  withSpan,
170
174
  withTraceContext
171
- } from "./chunk-M3T5JYDJ.js";
175
+ } from "./chunk-HNXMWHLU.js";
172
176
  import {
173
177
  createDiagnosticCapabilityAuthority,
174
178
  diagnosticConsole
175
- } from "./chunk-QGNXYVBO.js";
179
+ } from "./chunk-Z4FOPROB.js";
176
180
 
177
181
  // src/sdk/server/lock-pubkey-store.ts
178
182
  async function initLockPubkeyTable(db) {
@@ -3884,7 +3888,7 @@ async function verifyUpfrontPaymentInScope(opts) {
3884
3888
  x402Requirements
3885
3889
  });
3886
3890
  }
3887
- const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
3891
+ const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
3888
3892
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
3889
3893
  x402Payment,
3890
3894
  x402Config,
@@ -4908,7 +4912,7 @@ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
4908
4912
  snapshot
4909
4913
  });
4910
4914
  }
4911
- const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
4915
+ const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
4912
4916
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
4913
4917
  body.content.x402_payment,
4914
4918
  opts.x402Config,
@@ -5339,7 +5343,7 @@ async function processIncomingPaymentInScope(job, body, opts) {
5339
5343
  try {
5340
5344
  const requiredMsats = job.pendingPaymentMsats ?? 0;
5341
5345
  const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
5342
- const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
5346
+ const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
5343
5347
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
5344
5348
  body.content.x402_payment,
5345
5349
  opts.x402Config,
@@ -6943,13 +6947,37 @@ async function fundingReceiptFor(ledger, issuer, funding) {
6943
6947
 
6944
6948
  // src/sdk/server/tempo-drain-leg.ts
6945
6949
  async function completeTempoChannelDrain(args) {
6946
- const settlementEvidence = refundFloorEvidence({
6947
- rail: "tempo",
6948
- refundedNative: args.refundedNative,
6949
- debitedNative: args.debitedNative,
6950
- creditId: args.creditId,
6951
- drainId: args.drainId
6952
- });
6950
+ if (!args.tx && args.refundedNative !== void 0 && /^\d{1,78}$/.test(String(args.refundedNative))) {
6951
+ try {
6952
+ if (args.finalization?.outcome === "payer_exit" || args.finalization?.outcome === "payer_exit_unbacked") {
6953
+ if (BigInt(args.refundedNative) > 0n) await args.deps.ledger.recordTempoDrainPayment?.({ creditId: args.creditId, drainId: args.drainId });
6954
+ } else {
6955
+ await args.deps.ledger.recordTempoDrainRefund?.({ creditId: args.creditId, drainId: args.drainId, refundedNative: args.refundedNative });
6956
+ }
6957
+ } catch (error) {
6958
+ loggers.tempo.warn("tempo_drain_refund_evidence_write_failed", {});
6959
+ throw error;
6960
+ }
6961
+ }
6962
+ let settlementEvidence;
6963
+ try {
6964
+ settlementEvidence = refundFloorEvidence({
6965
+ rail: "tempo",
6966
+ refundedNative: args.refundedNative,
6967
+ debitedNative: args.debitedNative,
6968
+ creditId: args.creditId,
6969
+ drainId: args.drainId
6970
+ });
6971
+ } catch (error) {
6972
+ if (error instanceof DrainRefundFloorError && !args.tx) {
6973
+ try {
6974
+ await args.deps.ledger.recordTempoDrainHold?.({ creditId: args.creditId, drainId: args.drainId, reason: "refund_short" });
6975
+ } catch {
6976
+ loggers.tempo.warn("tempo_drain_hold_reason_write_failed", {});
6977
+ }
6978
+ }
6979
+ throw error;
6980
+ }
6953
6981
  return commitTerminalDrain(
6954
6982
  args.deps.drainReport,
6955
6983
  async (innerTx) => {
@@ -7690,6 +7718,11 @@ async function recoverTempoDrain(deps, mpp, record, channelId) {
7690
7718
  }
7691
7719
  if (!result) return null;
7692
7720
  if (!/^0x[0-9a-fA-F]{64}$/.test(result)) {
7721
+ try {
7722
+ await deps.ledger.recordTempoDrainHold?.({ creditId: record.creditId, drainId: record.drainId, reason: "close_evidence_missing" });
7723
+ } catch {
7724
+ loggers.tempo.warn("tempo_drain_hold_reason_write_failed", { amount_micro: record.amountMicro });
7725
+ }
7693
7726
  loggers.tempo.warn("tempo_session_drain_refund_unproven", {
7694
7727
  channel_id: channelId,
7695
7728
  credit_id: record.creditId,
@@ -10130,6 +10163,7 @@ var HostedSettlementReportObligations = class {
10130
10163
  FROM sdk_hosted_settlement_report_obligations target
10131
10164
  WHERE target.dvm_id=$1 AND target.scope=$2 AND target.rail='tempo' AND target.operation='close'
10132
10165
  AND target.state IN ('unmatched','no_report')
10166
+ AND ($7::text IS NULL OR target.channel_id=$7)
10133
10167
  )
10134
10168
  SELECT * FROM candidates
10135
10169
  WHERE ($6::boolean OR (state='unmatched' AND (retryable OR write_off_allowed)))
@@ -10141,7 +10175,8 @@ var HostedSettlementReportObligations = class {
10141
10175
  args.limit,
10142
10176
  args.after?.createdAtMs ?? null,
10143
10177
  args.after?.obligationId ?? null,
10144
- args.all ?? false
10178
+ args.all ?? false,
10179
+ args.channelId ?? null
10145
10180
  ]
10146
10181
  );
10147
10182
  return result.rows.map((row) => ({
@@ -12725,22 +12760,17 @@ function buildContext(job, opts) {
12725
12760
  };
12726
12761
  const doResolve = async (pc) => {
12727
12762
  if (resolved) return;
12763
+ if (job.pendingPayment?.resolve === doResolve) {
12764
+ job.status = job.pendingPrompts.size > 0 ? "awaiting-input" : "processing";
12765
+ const persist = opts.onPaymentResume ?? opts.onYield;
12766
+ if (job.status === "awaiting-input" && persist) await persist();
12767
+ if (resolved) return;
12768
+ job.pendingPayment = null;
12769
+ }
12728
12770
  resolved = true;
12729
12771
  cleanup();
12730
12772
  creditedToHandlerMsats += amountMsats;
12731
12773
  creditedToHandlerMicro += pendingFiat?.amountMicro ?? 0;
12732
- if (job.pendingPayment?.resolve === doResolve) {
12733
- job.pendingPayment = null;
12734
- job.status = job.pendingPrompts.size > 0 ? "awaiting-input" : "processing";
12735
- if (job.status === "awaiting-input" && opts.onYield) {
12736
- try {
12737
- await opts.onYield();
12738
- } catch (err) {
12739
- reject(err);
12740
- return;
12741
- }
12742
- }
12743
- }
12744
12774
  resolve(pc);
12745
12775
  };
12746
12776
  const doReject = (err) => {
@@ -13266,6 +13296,9 @@ var JobManager = class {
13266
13296
  // A prompt response and a payment NOTIFY must not mistake each other for a
13267
13297
  // different machine's successful claim.
13268
13298
  inputResumeTails = /* @__PURE__ */ new WeakMap();
13299
+ // A failed partial-wait snapshot leaves our durable claim in processing.
13300
+ // Retain its continuation so a later input can finish it without a new claim.
13301
+ claimedPaymentResumes = /* @__PURE__ */ new WeakMap();
13269
13302
  sessionId = randomBytes4(3).toString("hex");
13270
13303
  nextJobId = 1;
13271
13304
  accumulatorMonitor;
@@ -13672,7 +13705,10 @@ var JobManager = class {
13672
13705
  }
13673
13706
  async queueInputResume(job, resume) {
13674
13707
  const previous = this.inputResumeTails.get(job) ?? Promise.resolve();
13675
- const next = previous.catch(() => void 0).then(resume);
13708
+ const next = previous.catch(() => void 0).then(async () => {
13709
+ await this.claimedPaymentResumes.get(job)?.();
13710
+ return resume();
13711
+ });
13676
13712
  this.inputResumeTails.set(job, next);
13677
13713
  try {
13678
13714
  return await next;
@@ -13684,6 +13720,19 @@ var JobManager = class {
13684
13720
  await job.messageAppenderTail;
13685
13721
  return isStreamableJobStore(this.jobStore) ? this.jobStore.claimForProcessing(job.id) : true;
13686
13722
  }
13723
+ async resumeClaimedPayment(job, resume) {
13724
+ const retry = async () => {
13725
+ try {
13726
+ if (!isTerminal(job.status) && !await this.adoptDurableTerminal(job)) await resume();
13727
+ this.claimedPaymentResumes.delete(job);
13728
+ } catch (error) {
13729
+ if (!(error instanceof JobSnapshotPersistenceError)) this.claimedPaymentResumes.delete(job);
13730
+ throw error;
13731
+ }
13732
+ };
13733
+ this.claimedPaymentResumes.set(job, retry);
13734
+ await retry();
13735
+ }
13687
13736
  async dispatchMessageNow(job, type, content) {
13688
13737
  const ctx = job.sdkCtx;
13689
13738
  const cap = this.getCapability(job.capability);
@@ -13712,10 +13761,13 @@ var JobManager = class {
13712
13761
  return;
13713
13762
  }
13714
13763
  if (job.pendingPayment !== pp || isTerminal(job.status)) return;
13715
- job.pendingPayment = null;
13716
- job.status = job.pendingPrompts.size > 0 ? "awaiting-input" : "processing";
13717
- if (job.status === "awaiting-input") await this.persistJob(job);
13718
- await pp.resolve(pc);
13764
+ await this.resumeClaimedPayment(job, async () => {
13765
+ job.status = job.pendingPrompts.size > 0 ? "awaiting-input" : "processing";
13766
+ if (job.status === "awaiting-input") await retryJobSnapshot(() => this.persistJob(job));
13767
+ if (job.pendingPayment !== pp || isTerminal(job.status)) return;
13768
+ job.pendingPayment = null;
13769
+ await pp.resolve(pc);
13770
+ });
13719
13771
  } else if (cap?.onPayment && ctx) {
13720
13772
  await cap.onPayment(ctx, pc);
13721
13773
  }
@@ -13768,10 +13820,14 @@ var JobManager = class {
13768
13820
  record.seq = Math.max(record.seq, highSeq);
13769
13821
  }
13770
13822
  }
13771
- if (this.opts.dvmId && isTerminalCostRecoveryStore(this.jobStore)) {
13772
- await this.jobStore.saveForDvm(record, this.opts.dvmId);
13773
- } else {
13774
- await this.jobStore.save(record);
13823
+ try {
13824
+ if (this.opts.dvmId && isTerminalCostRecoveryStore(this.jobStore)) {
13825
+ await this.jobStore.saveForDvm(record, this.opts.dvmId);
13826
+ } else {
13827
+ await this.jobStore.save(record);
13828
+ }
13829
+ } catch (error) {
13830
+ throw new JobSnapshotPersistenceError(error);
13775
13831
  }
13776
13832
  const saved = await this.jobStore.get(job.id);
13777
13833
  if (saved) {
@@ -15311,6 +15367,7 @@ var JobManager = class {
15311
15367
  fxFetcher: this.opts.fxFetcher,
15312
15368
  onTerminal: () => this.handleJobTerminal(job),
15313
15369
  onYield: () => this.persistJob(job),
15370
+ onPaymentResume: () => retryJobSnapshot(() => this.persistJob(job)),
15314
15371
  subscribeNotifications: isStreamableJobStore(jobStore) ? (cb) => jobStore.subscribeNotifications(job.id, cb) : void 0,
15315
15372
  getCounters: isStreamableJobStore(jobStore) ? () => jobStore.getCounters(job.id) : void 0,
15316
15373
  drawPool: this.opts.creditLedger ? () => this.resolveDrawPool(job) : void 0,
@@ -15321,7 +15378,9 @@ var JobManager = class {
15321
15378
  if (!pending || job.pendingPayment !== pending || isTerminal(job.status)) return false;
15322
15379
  if (!await this.claimLocalInput(job)) return false;
15323
15380
  if (job.pendingPayment !== pending || isTerminal(job.status)) return false;
15324
- await resume?.();
15381
+ await this.resumeClaimedPayment(job, async () => {
15382
+ await resume?.();
15383
+ });
15325
15384
  return true;
15326
15385
  });
15327
15386
  } : void 0,
@@ -16082,6 +16141,23 @@ function resolvePricingMax(raw) {
16082
16141
  if (raw === void 0) return void 0;
16083
16142
  return { amount: roundUsd(parseUsdPrice(raw, "price")), currency: "usd" };
16084
16143
  }
16144
+ var JobSnapshotPersistenceError = class extends Error {
16145
+ constructor(cause) {
16146
+ super("Job snapshot could not be persisted", { cause });
16147
+ this.name = "JobSnapshotPersistenceError";
16148
+ }
16149
+ };
16150
+ async function retryJobSnapshot(persist) {
16151
+ for (let attempt = 0; ; attempt++) {
16152
+ try {
16153
+ await persist();
16154
+ return;
16155
+ } catch (error) {
16156
+ if (!(error instanceof JobSnapshotPersistenceError) || attempt >= 2) throw error;
16157
+ await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
16158
+ }
16159
+ }
16160
+ }
16085
16161
 
16086
16162
  // src/sdk/server/mint-health-tracker.ts
16087
16163
  var DEFAULT_TICK_INTERVAL_MS2 = 5 * 60 * 1e3;
@@ -17129,6 +17205,8 @@ function shortSha(sha) {
17129
17205
  }
17130
17206
 
17131
17207
  // src/sdk/server/admin-credit.ts
17208
+ import { sha256 } from "@noble/hashes/sha2.js";
17209
+ import { bytesToHex } from "@noble/hashes/utils.js";
17132
17210
  import {
17133
17211
  Amount as Amount3,
17134
17212
  CheckStateEnum as CheckStateEnum3,
@@ -17136,6 +17214,7 @@ import {
17136
17214
  getTokenMetadata as getTokenMetadata2
17137
17215
  } from "@cashu/cashu-ts";
17138
17216
  function installAdminCreditRoutes(app, opts) {
17217
+ app.post("/admin/credit/refund-draw", (c) => handleRefundDraw(c, opts));
17139
17218
  app.get("/admin/credit/pending-drains", (c) => handlePendingDrains(c, opts));
17140
17219
  app.get("/admin/credit/bitcoin-liability", (c) => handleBitcoinLiability(c, opts));
17141
17220
  app.post("/admin/credit/park-drain", (c) => handleParkDrain(c, opts));
@@ -17156,9 +17235,68 @@ function installAdminCreditRoutes(app, opts) {
17156
17235
  app.post("/admin/credit/write-off-tempo-close-report", (c) => handleTempoCloseReportAction(c, opts, "write-off"));
17157
17236
  app.get("/admin/credit/tempo-drains", (c) => handleTempoDrains(c, opts));
17158
17237
  app.get("/admin/credit/tempo-losses", (c) => handleTempoLosses(c, opts));
17238
+ app.post("/admin/credit/book-tempo-drain", (c) => handleTempoDrainDecision(c, opts, "book_refunded"));
17239
+ app.post("/admin/credit/release-tempo-drain", (c) => handleTempoDrainDecision(c, opts, "release"));
17159
17240
  app.post("/admin/credit/reconcile-tempo-drain", (c) => handleReconcileTempo(c, opts));
17160
17241
  app.post("/admin/credit/write-off-tempo-drain", (c) => handleWriteOffTempo(c, opts));
17161
17242
  }
17243
+ async function handleRefundDraw(c, opts) {
17244
+ const auth = await requireAuth(c, opts);
17245
+ if ("response" in auth) return auth.response;
17246
+ const body = await c.req.json().catch(() => null);
17247
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
17248
+ return c.json({ error: "invalid_body", message: "Request body must be a JSON object." }, 400);
17249
+ }
17250
+ const value = body;
17251
+ const nonempty = (item) => typeof item === "string" && item.length > 0;
17252
+ const byJob = nonempty(value.jobId) && value.creditId === void 0 && value.drawId === void 0;
17253
+ const byDraw = value.jobId === void 0 && nonempty(value.creditId) && nonempty(value.drawId);
17254
+ if (!byJob && !byDraw || !nonempty(value.refundId) || value.amountMicro !== void 0 && (typeof value.amountMicro !== "number" || !Number.isSafeInteger(value.amountMicro) || value.amountMicro <= 0) || value.reason !== void 0 && (typeof value.reason !== "string" || value.reason.length > 280)) {
17255
+ return c.json({ error: "invalid_body", message: "Provide jobId or creditId + drawId, refundId, an optional positive safe integer amountMicro, and an optional reason of at most 280 characters." }, 400);
17256
+ }
17257
+ if (!opts.ledger.durable || !opts.enqueueCreditDrawRefund) {
17258
+ return c.json({ error: "credit_refund_unavailable", hint: "Configure the persistent credit store and platform refund reporter, then retry with the original refund ID." }, 503);
17259
+ }
17260
+ try {
17261
+ const result = await opts.ledger.refundDraw({
17262
+ ...byJob ? { jobId: value.jobId } : { creditId: value.creditId, drawId: value.drawId },
17263
+ refundId: value.refundId,
17264
+ ...value.amountMicro === void 0 ? {} : { amountMicro: value.amountMicro },
17265
+ ...value.reason === void 0 ? {} : { reason: value.reason },
17266
+ outbox: { dvmId: opts.dvmId, enqueue: opts.enqueueCreditDrawRefund },
17267
+ ...opts.receiptIssuer ? { trustedReceiptPubkey: opts.receiptIssuer.pubkey } : {},
17268
+ ...opts.now ? { nowMs: opts.now() } : {}
17269
+ });
17270
+ return c.json({
17271
+ status: "refunded",
17272
+ refund_id: result.refundId,
17273
+ credit_id: result.creditId,
17274
+ draw_id: result.drawId,
17275
+ job_id: result.jobId,
17276
+ amount_micro: result.amountMicro,
17277
+ currency: result.currency,
17278
+ ledger_seq: result.ledgerSeq,
17279
+ refunded_at: result.refundedAt,
17280
+ balance_after_micro: result.balanceAfterMicro,
17281
+ replayed: result.replayed
17282
+ });
17283
+ } catch (error) {
17284
+ if (!(error instanceof CreditLedgerError)) throw error;
17285
+ const hints = {
17286
+ draw_not_found: "Check the job ID or credit/draw IDs. If they are correct, this DVM may lack trusted ownership evidence; ask an operator to inspect that evidence. Retrying alone cannot establish ownership.",
17287
+ draw_not_completed: "Wait for the job to complete. Failed or cancelled jobs release their pending hold automatically.",
17288
+ refund_exceeds_draw: "Refund no more than the remaining unrefunded charge. Omit amountMicro to refund that remainder.",
17289
+ credit_not_open: "The original credit is closed or expired. Ask an operator to inspect the original payment before arranging a refund.",
17290
+ credit_refund_channel_unsupported: "This channel returns unused collateral through its normal exit. Ask an operator about refunding completed spending.",
17291
+ credit_refund_basis_unavailable: "The original payment value cannot be proven automatically. Ask an operator to inspect independent original-payment evidence before arranging a repair. Retrying alone cannot establish that evidence.",
17292
+ credit_refund_state_unavailable: "The original credit reopening order cannot be proven automatically. Ask an operator to inspect independent funding and drain evidence before arranging a repair. Retrying alone cannot establish that evidence.",
17293
+ refund_conflict: "Repeat the original arguments, or deliberately choose a new refund ID for a different refund."
17294
+ };
17295
+ const hint = hints[error.code];
17296
+ if (!hint) throw error;
17297
+ return c.json({ error: error.code, hint }, error.code === "draw_not_found" ? 404 : error.code === "refund_conflict" ? 409 : 422);
17298
+ }
17299
+ }
17162
17300
  async function handleX402ExactSettlements(c, opts) {
17163
17301
  const auth = await requireAuth(c, opts);
17164
17302
  if ("response" in auth) return auth.response;
@@ -17277,6 +17415,9 @@ async function handleTempoLosses(c, opts) {
17277
17415
  currency: loss.currency,
17278
17416
  former_balance_micro: loss.formerBalanceMicro,
17279
17417
  settled_on_chain_native: loss.settledOnChainNative.toString(),
17418
+ refunded_to_payer_native: loss.refundedToPayerNative?.toString() ?? null,
17419
+ loss_native: loss.lossNative?.toString() ?? null,
17420
+ loss_basis: loss.lossBasis ?? null,
17280
17421
  highest_voucher_native: loss.highestVoucherNative.toString(),
17281
17422
  consumed_service_micro: loss.consumedServiceMicro,
17282
17423
  consumed_native: loss.consumedNative.toString(),
@@ -18263,13 +18404,18 @@ async function handleTempoDrains(c, opts) {
18263
18404
  ...after ? { after } : {},
18264
18405
  createdBeforeMs: nowMs - minAge,
18265
18406
  rail: "tempo",
18266
- includeResolved: c.req.query("include_resolved") === "true"
18407
+ includeResolved: c.req.query("include_resolved") === "true",
18408
+ includeWrittenOff: true
18267
18409
  });
18268
18410
  const wantsEvidence = c.req.query("evidence") !== "false";
18269
18411
  const rows2 = [];
18270
18412
  for (const drain of drains) {
18271
18413
  const evidence = wantsEvidence && drain.channelId ? await classifyTempoCloseEvidence(rail.rail, drain.channelId) : void 0;
18272
- rows2.push(tempoDrainBody(drain, evidence));
18414
+ const closeReports = drain.channelId && opts.hostedSettlementReports ? await opts.hostedSettlementReports.store.listTempoCloseReports({ limit: 50, all: true, channelId: drain.channelId }) : [];
18415
+ rows2.push({
18416
+ ...tempoDrainBody(drain, evidence),
18417
+ close_reports: closeReports.map((row) => ({ obligation_id: row.id, status: row.status, written_off: row.writtenOff }))
18418
+ });
18273
18419
  }
18274
18420
  const last = drains.length === limit ? drains[drains.length - 1] : void 0;
18275
18421
  return c.json({
@@ -18283,6 +18429,90 @@ async function handleTempoDrains(c, opts) {
18283
18429
  } : {}
18284
18430
  });
18285
18431
  }
18432
+ async function handleTempoDrainDecision(c, opts, decision) {
18433
+ const auth = await requireAuth(c, opts);
18434
+ if ("response" in auth) return auth.response;
18435
+ const body = await c.req.json().catch(() => null);
18436
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
18437
+ return c.json({ error: "invalid_body", message: "Request body must be a JSON object." }, 400);
18438
+ }
18439
+ const value = body;
18440
+ const text = (item, max) => typeof item === "string" && item.trim().length > 0 && item.trim().length <= max;
18441
+ if (Object.keys(value).some((key) => !["credit_id", "drain_id", "reason", "evidence"].includes(key)) || typeof value.credit_id !== "string" || value.credit_id.length === 0 || typeof value.drain_id !== "string" || value.drain_id.length === 0 || !text(value.reason, 2e3) || value.evidence !== void 0 && !text(value.evidence, 4e3)) {
18442
+ return c.json({ error: "invalid_body", message: "Provide credit_id, drain_id, a reason of 1\u20132000 characters, and optional evidence of 1\u20134000 characters." }, 400);
18443
+ }
18444
+ if (!opts.ledger.durable || !opts.ledger.decideTempoDrain) {
18445
+ return c.json({ error: "tempo_drain_decision_unavailable", message: "A durable Tempo decision store is required." }, 503);
18446
+ }
18447
+ const args = {
18448
+ creditId: value.credit_id,
18449
+ drainId: value.drain_id,
18450
+ decision,
18451
+ reason: value.reason.trim(),
18452
+ ...value.evidence === void 0 ? {} : { evidence: value.evidence.trim() },
18453
+ nowMs: opts.now ? opts.now() : Date.now()
18454
+ };
18455
+ if (decision === "book_refunded" && opts.requireDrainReport && !opts.enqueueCreditDrain) {
18456
+ const existing = await opts.ledger.getDrain(args);
18457
+ if (existing && !existing.operatorDecision && (existing.status === "pending" || existing.status === "exit_required")) {
18458
+ return c.json({ error: "tempo_drain_decision_unavailable", message: "Hosted booking requires a transactional drain reporter. Restore its configuration, then retry the original decision." }, 503);
18459
+ }
18460
+ }
18461
+ const client = await opts.db.connect();
18462
+ let result;
18463
+ try {
18464
+ await client.query("BEGIN");
18465
+ let decided;
18466
+ const transition = async () => {
18467
+ decided = await opts.ledger.decideTempoDrain({ ...args, tx: client });
18468
+ return decided;
18469
+ };
18470
+ if (decision === "book_refunded") {
18471
+ await commitTerminalDrain({ db: opts.db, dvmId: opts.dvmId, enqueueCreditDrain: opts.enqueueCreditDrain }, transition, client);
18472
+ } else {
18473
+ await transition();
18474
+ }
18475
+ result = decided;
18476
+ await client.query("COMMIT");
18477
+ } catch (error) {
18478
+ await client.query("ROLLBACK").catch(() => void 0);
18479
+ if (error instanceof CreditLedgerError) {
18480
+ return c.json(
18481
+ { error: error.code, message: error.message },
18482
+ error.code === "drain_not_found" || error.code === "credit_not_found" ? 404 : ["drain_decision_conflict", "tempo_drain_close_pending", "tempo_drain_refund_paid", "tempo_drain_refund_unknown"].includes(error.code) ? 409 : 422
18483
+ );
18484
+ }
18485
+ throw error;
18486
+ } finally {
18487
+ client.release();
18488
+ }
18489
+ if (!result.replayed) {
18490
+ diagnosticConsole.warn({
18491
+ dataset: "tempo",
18492
+ event: decision === "book_refunded" ? "tempo_drain_booked_as_refunded" : "tempo_drain_released",
18493
+ credit_id: result.drain.creditId,
18494
+ drain_id: result.drain.drainId,
18495
+ channel_id: result.drain.channelId,
18496
+ drain_reference: `sha256:${bytesToHex(sha256(new TextEncoder().encode(JSON.stringify([result.drain.creditId, result.drain.drainId]))))}`,
18497
+ amount_micro: result.drain.amountMicro,
18498
+ amount_native: result.drain.drainedNative?.toString() ?? null,
18499
+ currency: result.drain.currency
18500
+ });
18501
+ }
18502
+ return c.json({
18503
+ status: result.drain.status,
18504
+ credit_id: result.drain.creditId,
18505
+ drain_id: result.drain.drainId,
18506
+ channel_id: result.drain.channelId,
18507
+ amount_micro: result.drain.amountMicro,
18508
+ amount_native: result.drain.drainedNative,
18509
+ currency: result.drain.currency,
18510
+ decision: result.drain.operatorDecision,
18511
+ balance_micro: result.credit.balanceMicro,
18512
+ spendable_micro: result.spendableMicro,
18513
+ replayed: result.replayed
18514
+ });
18515
+ }
18286
18516
  async function handleReconcileTempo(c, opts) {
18287
18517
  const auth = await requireAuth(c, opts);
18288
18518
  if ("response" in auth) return auth.response;
@@ -18296,6 +18526,12 @@ async function handleReconcileTempo(c, opts) {
18296
18526
  if (record.status === "sent") return tempoReconciledBody(c, record, channelId, true);
18297
18527
  const evidence = await classifyTempoCloseEvidence(rail.rail, channelId);
18298
18528
  if (evidence.chain !== "found") return tempoEvidenceRefusal(c, evidence);
18529
+ try {
18530
+ await opts.ledger.recordTempoDrainRefund?.({ creditId: record.creditId, drainId: record.drainId, refundedNative: evidence.refundedToPayer });
18531
+ } catch (error) {
18532
+ diagnosticConsole.warn({ dataset: "tempo", event: "tempo_drain_refund_evidence_write_failed", amount_micro: record.amountMicro });
18533
+ throw error;
18534
+ }
18299
18535
  const client = await opts.db.connect();
18300
18536
  let booked;
18301
18537
  try {
@@ -18784,6 +19020,10 @@ function tempoDrainBody(drain, evidence) {
18784
19020
  created_at: drain.createdAt,
18785
19021
  write_off_note: drain.writeOffNote,
18786
19022
  written_off_at: drain.writtenOffAt,
19023
+ decision: drain.operatorDecision ?? null,
19024
+ proven_refund_native: drain.provenRefundNative ?? null,
19025
+ refund_payment_proven: drain.refundPaymentProven ?? false,
19026
+ held_reason: drain.status === "pending" || drain.status === "exit_required" ? drain.holdReason ?? (evidence?.chain === "found" && drain.drainedNative !== null && BigInt(evidence.refundedToPayer) < BigInt(drain.drainedNative) ? "refund_short" : drain.status === "exit_required" ? "exit_required" : "refund_unproven") : null,
18787
19027
  ...evidence ? { chain: evidence } : {}
18788
19028
  };
18789
19029
  }
@@ -20129,7 +20369,7 @@ async function createDVMServer(descriptor, opts) {
20129
20369
  }
20130
20370
  }
20131
20371
  if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
20132
- const { RevenueReporter } = await import("./revenue-reporter-EV3K5L6B.js");
20372
+ const { RevenueReporter } = await import("./revenue-reporter-HD4GMXGD.js");
20133
20373
  const reporter = new RevenueReporter(
20134
20374
  opts.db,
20135
20375
  platformUrl,
@@ -20139,7 +20379,7 @@ async function createDVMServer(descriptor, opts) {
20139
20379
  revenueReporter = reporter;
20140
20380
  await reporter.init();
20141
20381
  reporter.startRetryLoop();
20142
- const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-HVJ6WUF3.js");
20382
+ const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-NXWQVKXW.js");
20143
20383
  payoutReporter = new PayoutReporter({
20144
20384
  dvmId,
20145
20385
  db: opts.db,
@@ -20167,6 +20407,7 @@ async function createDVMServer(descriptor, opts) {
20167
20407
  const effectiveOnRevenueSkippedNoRail = opts.onRevenueSkippedNoRail ?? (activeRevenueReporter ? (info) => activeRevenueReporter.reportRevenueSkippedNoRail(info) : void 0);
20168
20408
  const enqueueCreditDeposit = !opts.onCreditFunded && activeRevenueReporter ? activeRevenueReporter.enqueueDeposit.bind(activeRevenueReporter) : void 0;
20169
20409
  const enqueueCreditDrain = activeRevenueReporter ? activeRevenueReporter.enqueueCreditDrain.bind(activeRevenueReporter) : void 0;
20410
+ const enqueueCreditDrawRefund = activeRevenueReporter ? activeRevenueReporter.enqueueCreditDrawRefund.bind(activeRevenueReporter) : void 0;
20170
20411
  const enqueueCreditDrawRelease = activeRevenueReporter ? activeRevenueReporter.enqueueCreditDrawRelease.bind(activeRevenueReporter) : void 0;
20171
20412
  unsubscribeTempoSettlementReadiness = opts.tempoSettlementReadiness?.subscribe({
20172
20413
  onBalance: (observation) => activeRevenueReporter && dvmId ? activeRevenueReporter.reportTempoSettlementBalance({
@@ -20322,6 +20563,7 @@ async function createDVMServer(descriptor, opts) {
20322
20563
  } : void 0;
20323
20564
  server = new DVMServer(descriptor, {
20324
20565
  ...opts,
20566
+ platformUrl,
20325
20567
  hostingControlInstance: hostingControl,
20326
20568
  hostedSettlementReports,
20327
20569
  hostingClosureSupport,
@@ -20336,6 +20578,7 @@ async function createDVMServer(descriptor, opts) {
20336
20578
  enqueueCreditDeposit,
20337
20579
  enqueueCreditDrain,
20338
20580
  enqueueCreditDrawRelease,
20581
+ enqueueCreditDrawRefund,
20339
20582
  x402BatchSettlement,
20340
20583
  x402BatchSettlementFactory,
20341
20584
  x402ExactSettlement,
@@ -20791,8 +21034,10 @@ var DVMServer = class {
20791
21034
  receiptIssuer: this.receiptIssuer,
20792
21035
  creditTtlMs: () => this.creditTtlMs(),
20793
21036
  creditCurrency: () => this.creditCurrency(),
21037
+ enqueueCreditDrawRefund: this.opts.enqueueCreditDrawRefund,
20794
21038
  enqueueCreditDeposit: this.opts.enqueueCreditDeposit,
20795
21039
  enqueueCreditDrain: this.opts.enqueueCreditDrain,
21040
+ requireDrainReport: !!this.opts.platformUrl,
20796
21041
  lightningReceive: this.opts.lightningReceive,
20797
21042
  // x402 repair queue rides the same surface, and resolves
20798
21043
  // the rail the same lazy way the verify path does — constructing the
@@ -22927,8 +23172,14 @@ var DVMServer = class {
22927
23172
  await jm.markInboundVerified(activeJob.id, inboundSeq2);
22928
23173
  }
22929
23174
  jm.startIdleTimer(activeJob);
22930
- await jm.dispatchMessage(activeJob, body2.type, body2.content);
22931
- await jm.persistJob(activeJob);
23175
+ try {
23176
+ await jm.dispatchMessage(activeJob, body2.type, body2.content);
23177
+ await this.persistMessageSnapshot(jm, activeJob, body2.type);
23178
+ } catch (error) {
23179
+ const response = this.postPaymentSnapshotFailure(c, error, activeJob, body2.type, inboundSeq2, unappliedCredit2);
23180
+ if (response) return response;
23181
+ throw error;
23182
+ }
22932
23183
  const lastMsg2 = activeJob.messages[activeJob.messages.length - 1];
22933
23184
  return c.json({ seq: lastMsg2.seq, ...unappliedCredit2 && { credit: unappliedCredit2 } }, 201);
22934
23185
  }
@@ -23011,20 +23262,48 @@ var DVMServer = class {
23011
23262
  return c.json({ seq: lastMsg2.seq, ...creditBlock }, 201);
23012
23263
  }
23013
23264
  jm.startIdleTimer(job);
23014
- if (isStreamableJobStore(jm.store)) {
23015
- const store = jm.store;
23016
- const lockResult = await store.tryReactivationLock(job.id, async () => {
23265
+ try {
23266
+ if (isStreamableJobStore(jm.store)) {
23267
+ const store = jm.store;
23268
+ const lockResult = await store.tryReactivationLock(job.id, async () => {
23269
+ await this.runReactivation(jm, job, body, record.status);
23270
+ });
23271
+ if (!lockResult.acquired) {
23272
+ this.teardownReactivationScaffold(jm, job);
23273
+ }
23274
+ } else {
23017
23275
  await this.runReactivation(jm, job, body, record.status);
23018
- });
23019
- if (!lockResult.acquired) {
23020
- this.teardownReactivationScaffold(jm, job);
23021
23276
  }
23022
- } else {
23023
- await this.runReactivation(jm, job, body, record.status);
23277
+ } catch (error) {
23278
+ const response = this.postPaymentSnapshotFailure(c, error, job, body.type, inboundSeq, unappliedCredit);
23279
+ if (response) return response;
23280
+ throw error;
23024
23281
  }
23025
23282
  const lastMsg = job.messages[job.messages.length - 1];
23026
23283
  return c.json({ seq: lastMsg.seq, ...creditBlock }, 201);
23027
23284
  }
23285
+ async persistMessageSnapshot(jm, job, type) {
23286
+ if (type === "payment") {
23287
+ await retryJobSnapshot(() => jm.persistJob(job));
23288
+ } else {
23289
+ await jm.persistJob(job);
23290
+ }
23291
+ }
23292
+ postPaymentSnapshotFailure(c, error, job, type, inboundSeq, unappliedCredit) {
23293
+ if (!(error instanceof JobSnapshotPersistenceError) || type !== "payment" || inboundSeq === null || !isStreamableJobStore(this.jobManager.store)) return void 0;
23294
+ diagnosticConsole.error({ level: "job_snapshot_unavailable", job_id: job.id, seq: inboundSeq });
23295
+ return c.json({
23296
+ error: "job_snapshot_unavailable",
23297
+ message: "Payment was recorded but the job snapshot could not be persisted.",
23298
+ display: "Your payment was recorded. The service could not save the latest job progress.",
23299
+ hint: "Do not send another payment. Read the job status and messages; if the worker stopped, resume with a non-payment message.",
23300
+ retryable: false,
23301
+ payment_status: "recorded",
23302
+ job_id: job.id,
23303
+ seq: inboundSeq,
23304
+ ...unappliedCredit && { credit: unappliedCredit }
23305
+ }, 503);
23306
+ }
23028
23307
  /**
23029
23308
  * Run the handler or dispatch the inbound after Tx B + Tx C have already
23030
23309
  * committed the credit. Status decides the behaviour: `awaiting-input`
@@ -23050,7 +23329,7 @@ var DVMServer = class {
23050
23329
  });
23051
23330
  } else {
23052
23331
  await jm.dispatchMessage(job, body.type, body.content);
23053
- await jm.persistJob(job);
23332
+ await this.persistMessageSnapshot(jm, job, body.type);
23054
23333
  }
23055
23334
  }
23056
23335
  /**