@dvmkit/sdk 0.10.1 → 0.12.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-STJBBVNL.js → chunk-2VGMZ7VF.js} +487 -71
  2. package/dist/{chunk-3Q6FTZDB.js → chunk-3YFFZLW7.js} +2 -2
  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-JNECYURT.js → chunk-D4FFS3IS.js} +1 -1
  7. package/dist/{chunk-VTHYQCDB.js → chunk-E7ZBCT5U.js} +1 -1
  8. package/dist/{chunk-MMGSYMIC.js → chunk-EP7OVWI5.js} +1 -1
  9. package/dist/{chunk-LDIPVPKI.js → chunk-FFRTAQUW.js} +1 -1
  10. package/dist/chunk-H3GGFEE5.js +57 -0
  11. package/dist/{chunk-M3T5JYDJ.js → chunk-HNXMWHLU.js} +1 -1
  12. package/dist/{chunk-UKD3R2KU.js → chunk-HW2UCOOC.js} +4 -55
  13. package/dist/{chunk-FFXXGCVG.js → chunk-ITRSITFP.js} +1 -1
  14. package/dist/{chunk-MU3IE434.js → chunk-JLT2YIBS.js} +267 -52
  15. package/dist/{chunk-KFCEXCZM.js → chunk-KJZVTWMX.js} +1 -1
  16. package/dist/{chunk-W3VGCNG6.js → chunk-LDKV4UHI.js} +1 -1
  17. package/dist/{chunk-AJ6UHXUA.js → chunk-M4NFYEEY.js} +1 -1
  18. package/dist/{chunk-MB6R4XPZ.js → chunk-NS2OMIU7.js} +15 -3
  19. package/dist/{chunk-YOSZIWC3.js → chunk-POYUX2NN.js} +149 -11
  20. package/dist/{chunk-VUGEEMLT.js → chunk-RPUFWIT4.js} +11 -89
  21. package/dist/{chunk-SYBFMHXQ.js → chunk-S7PIFHE4.js} +590 -67
  22. package/dist/{chunk-2OGBZPPJ.js → chunk-TWKQFIOW.js} +21 -3
  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-a1yRdmhI.d.ts} +1 -1
  27. package/dist/{credit-ledger-4PZNAMDT.js → credit-ledger-MF23UKRJ.js} +6 -3
  28. package/dist/{credit-menu-BWsVqMh-.d.ts → credit-menu-B2T-O9oY.d.ts} +11 -4
  29. package/dist/{fx-BM5h4o5S.d.ts → fx-DLhscRUh.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 +8 -6
  33. package/dist/internal/caller.js +27 -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-B8Ijr5V6.d.ts} +3 -1
  37. package/dist/{lightning-backend-9oky_wJi.d.ts → lightning-backend-DmwMSgaM.d.ts} +1 -1
  38. package/dist/memory-credit-ledger-ERD764PS.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-H45PRBLA.js → postgres-job-store-ZVMAN6L2.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 +30 -12
  47. package/dist/server/index.js +32 -30
  48. package/dist/{step-cache--7yA5Ihr.d.ts → step-cache-DQqpwXa8.d.ts} +206 -47
  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-D5kk2nIT.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-POYUX2NN.js";
21
21
  import {
22
22
  PostgresJobStore
23
- } from "./chunk-2OGBZPPJ.js";
24
- import {
25
- parseStoreValue
26
- } from "./chunk-YG7G4DPZ.js";
23
+ } from "./chunk-TWKQFIOW.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-2VGMZ7VF.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) {
@@ -991,6 +995,7 @@ function attachCreditMenu(body, menu) {
991
995
  }
992
996
  function toCreditTerms(menu) {
993
997
  return {
998
+ ...menu.balance_payment && { balance_payment: true },
994
999
  min_micro: menu.min_micro,
995
1000
  max_micro: menu.max_micro,
996
1001
  ttl_ms: menu.ttl_ms,
@@ -3884,7 +3889,7 @@ async function verifyUpfrontPaymentInScope(opts) {
3884
3889
  x402Requirements
3885
3890
  });
3886
3891
  }
3887
- const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
3892
+ const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
3888
3893
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
3889
3894
  x402Payment,
3890
3895
  x402Config,
@@ -4908,7 +4913,7 @@ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
4908
4913
  snapshot
4909
4914
  });
4910
4915
  }
4911
- const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
4916
+ const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
4912
4917
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
4913
4918
  body.content.x402_payment,
4914
4919
  opts.x402Config,
@@ -5339,7 +5344,7 @@ async function processIncomingPaymentInScope(job, body, opts) {
5339
5344
  try {
5340
5345
  const requiredMsats = job.pendingPaymentMsats ?? 0;
5341
5346
  const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
5342
- const { verifyX402Payment } = await import("./x402-EN5LSOVM.js");
5347
+ const { verifyX402Payment } = await import("./x402-RUJ4LUKF.js");
5343
5348
  const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
5344
5349
  body.content.x402_payment,
5345
5350
  opts.x402Config,
@@ -6943,13 +6948,37 @@ async function fundingReceiptFor(ledger, issuer, funding) {
6943
6948
 
6944
6949
  // src/sdk/server/tempo-drain-leg.ts
6945
6950
  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
- });
6951
+ if (!args.tx && args.refundedNative !== void 0 && /^\d{1,78}$/.test(String(args.refundedNative))) {
6952
+ try {
6953
+ if (args.finalization?.outcome === "payer_exit" || args.finalization?.outcome === "payer_exit_unbacked") {
6954
+ if (BigInt(args.refundedNative) > 0n) await args.deps.ledger.recordTempoDrainPayment?.({ creditId: args.creditId, drainId: args.drainId });
6955
+ } else {
6956
+ await args.deps.ledger.recordTempoDrainRefund?.({ creditId: args.creditId, drainId: args.drainId, refundedNative: args.refundedNative });
6957
+ }
6958
+ } catch (error) {
6959
+ loggers.tempo.warn("tempo_drain_refund_evidence_write_failed", {});
6960
+ throw error;
6961
+ }
6962
+ }
6963
+ let settlementEvidence;
6964
+ try {
6965
+ settlementEvidence = refundFloorEvidence({
6966
+ rail: "tempo",
6967
+ refundedNative: args.refundedNative,
6968
+ debitedNative: args.debitedNative,
6969
+ creditId: args.creditId,
6970
+ drainId: args.drainId
6971
+ });
6972
+ } catch (error) {
6973
+ if (error instanceof DrainRefundFloorError && !args.tx) {
6974
+ try {
6975
+ await args.deps.ledger.recordTempoDrainHold?.({ creditId: args.creditId, drainId: args.drainId, reason: "refund_short" });
6976
+ } catch {
6977
+ loggers.tempo.warn("tempo_drain_hold_reason_write_failed", {});
6978
+ }
6979
+ }
6980
+ throw error;
6981
+ }
6953
6982
  return commitTerminalDrain(
6954
6983
  args.deps.drainReport,
6955
6984
  async (innerTx) => {
@@ -7690,6 +7719,11 @@ async function recoverTempoDrain(deps, mpp, record, channelId) {
7690
7719
  }
7691
7720
  if (!result) return null;
7692
7721
  if (!/^0x[0-9a-fA-F]{64}$/.test(result)) {
7722
+ try {
7723
+ await deps.ledger.recordTempoDrainHold?.({ creditId: record.creditId, drainId: record.drainId, reason: "close_evidence_missing" });
7724
+ } catch {
7725
+ loggers.tempo.warn("tempo_drain_hold_reason_write_failed", { amount_micro: record.amountMicro });
7726
+ }
7693
7727
  loggers.tempo.warn("tempo_session_drain_refund_unproven", {
7694
7728
  channel_id: channelId,
7695
7729
  credit_id: record.creditId,
@@ -10130,6 +10164,7 @@ var HostedSettlementReportObligations = class {
10130
10164
  FROM sdk_hosted_settlement_report_obligations target
10131
10165
  WHERE target.dvm_id=$1 AND target.scope=$2 AND target.rail='tempo' AND target.operation='close'
10132
10166
  AND target.state IN ('unmatched','no_report')
10167
+ AND ($7::text IS NULL OR target.channel_id=$7)
10133
10168
  )
10134
10169
  SELECT * FROM candidates
10135
10170
  WHERE ($6::boolean OR (state='unmatched' AND (retryable OR write_off_allowed)))
@@ -10141,7 +10176,8 @@ var HostedSettlementReportObligations = class {
10141
10176
  args.limit,
10142
10177
  args.after?.createdAtMs ?? null,
10143
10178
  args.after?.obligationId ?? null,
10144
- args.all ?? false
10179
+ args.all ?? false,
10180
+ args.channelId ?? null
10145
10181
  ]
10146
10182
  );
10147
10183
  return result.rows.map((row) => ({
@@ -12605,6 +12641,7 @@ function buildContext(job, opts) {
12605
12641
  const paymentRequestContent = {
12606
12642
  amount_msats: amountMsats,
12607
12643
  reason,
12644
+ ...pendingFiat && { credit: { amount_micro: pendingFiat.amountMicro, currency: pendingFiat.currency } },
12608
12645
  ...fiatAmount && { fiat_amount: fiatAmount },
12609
12646
  ...payOpts?.mints && { mints: payOpts.mints },
12610
12647
  ...payOpts?.minLocktimeSeconds && { min_locktime_seconds: payOpts.minLocktimeSeconds },
@@ -12683,8 +12720,16 @@ function buildContext(job, opts) {
12683
12720
  if (job.replayPaymentIndex < payments.length) {
12684
12721
  const payment = payments[job.replayPaymentIndex];
12685
12722
  job.replayPaymentIndex++;
12686
- if (job.reactivationPaymentApplied !== true) {
12687
- throw new Error("[internal_error] payment replay reached without credit");
12723
+ const balance = payment.content.credit;
12724
+ const accepted = balance ? await opts.acceptedBalancePayment?.(payment.seq, pendingFiat?.amountMicro, pendingFiat?.currency) : job.reactivationPaymentApplied === true;
12725
+ if (!accepted) throw new Error("[internal_error] payment replay reached without credit");
12726
+ if (balance) {
12727
+ job.pendingPaymentMsats = void 0;
12728
+ job.pendingPaymentFiatMicro = void 0;
12729
+ job.pendingPaymentFiatCurrency = void 0;
12730
+ job.pendingMppChallengeIds = void 0;
12731
+ job.pendingX402Nonce = void 0;
12732
+ job.pendingX402AmountUsdcMicro = void 0;
12688
12733
  }
12689
12734
  job.status = "processing";
12690
12735
  creditedToHandlerMsats += amountMsats;
@@ -12753,7 +12798,7 @@ function buildContext(job, opts) {
12753
12798
  try {
12754
12799
  const counters = await getCounters();
12755
12800
  if (!counters || resolved) return;
12756
- const satisfied = poolBasis ? counters.paidMsats - paidMsatsAtYield >= amountMsats : counters.paidMsats - creditedToHandlerMsats >= amountMsats;
12801
+ const satisfied = counters.balancePaymentSatisfied === true || (poolBasis ? counters.paidMsats - paidMsatsAtYield >= amountMsats : counters.paidMsats - creditedToHandlerMsats >= amountMsats);
12757
12802
  if (satisfied) {
12758
12803
  const resume = async () => {
12759
12804
  job.paidMsats = counters.paidMsats;
@@ -13257,6 +13302,9 @@ var JobManager = class {
13257
13302
  statusChecksInFlight = /* @__PURE__ */ new Set();
13258
13303
  /** Job ids that were notified mid-re-read and must be re-checked. */
13259
13304
  statusChecksQueued = /* @__PURE__ */ new Set();
13305
+ // Bind live resolvers and recovery scaffolds to the store-allocated ask identity.
13306
+ paymentAskSeqs = /* @__PURE__ */ new WeakMap();
13307
+ reactivationAskSeqs = /* @__PURE__ */ new WeakMap();
13260
13308
  // Every local input resume holds this queue through its durable wait update.
13261
13309
  // A prompt response and a payment NOTIFY must not mistake each other for a
13262
13310
  // different machine's successful claim.
@@ -13683,7 +13731,59 @@ var JobManager = class {
13683
13731
  }
13684
13732
  async claimLocalInput(job) {
13685
13733
  await job.messageAppenderTail;
13686
- return isStreamableJobStore(this.jobStore) ? this.jobStore.claimForProcessing(job.id) : true;
13734
+ const askSeq = job.pendingPayment ? this.paymentAskSeqs.get(job.pendingPayment) : void 0;
13735
+ if (job.pendingPayment && this.opts.db && this.jobStore instanceof PostgresJobStore && askSeq === void 0) return false;
13736
+ return this.claimInputForAsk(job.id, askSeq);
13737
+ }
13738
+ async claimInputForAsk(jobId, askSeq) {
13739
+ if (!isStreamableJobStore(this.jobStore)) return true;
13740
+ if (askSeq === void 0 || !this.opts.db || !(this.jobStore instanceof PostgresJobStore)) return this.jobStore.claimForProcessing(jobId);
13741
+ const tx = await this.opts.db.connect();
13742
+ try {
13743
+ await tx.query("BEGIN");
13744
+ const { rows: [record] } = await tx.query("SELECT status FROM jobs WHERE id=$1 FOR UPDATE", [jobId]);
13745
+ const { rows: [ask] } = await tx.query(
13746
+ `SELECT MAX(seq) AS seq FROM job_messages WHERE job_id=$1 AND status='verified'
13747
+ AND data->>'from'='provider' AND data->>'type'='payment-request'`,
13748
+ [jobId]
13749
+ );
13750
+ if (record?.status !== "awaiting-input" || Number(ask?.seq) !== askSeq) {
13751
+ await tx.query("ROLLBACK");
13752
+ return false;
13753
+ }
13754
+ await tx.query(
13755
+ `UPDATE jobs SET status='processing', last_activity_at=$2,
13756
+ caller_wait_ms=caller_wait_ms+GREATEST(0,$2-COALESCE(awaiting_input_at_ms,last_activity_at)),
13757
+ awaiting_input_at_ms=NULL WHERE id=$1`,
13758
+ [jobId, Date.now()]
13759
+ );
13760
+ await tx.query("COMMIT");
13761
+ return true;
13762
+ } catch (error) {
13763
+ await tx.query("ROLLBACK").catch(() => void 0);
13764
+ throw error;
13765
+ } finally {
13766
+ tx.release();
13767
+ }
13768
+ }
13769
+ async adoptPaymentMetadata(job, pending) {
13770
+ const record = await this.jobStore.get(job.id);
13771
+ if (!record || job.pendingPayment !== pending || isTerminal(job.status) || isTerminal(record.status)) return false;
13772
+ job.paidMsats = record.paidMsats;
13773
+ job.pendingPaymentMsats = record.pendingPaymentMsats;
13774
+ job.creditId = record.creditId;
13775
+ job.drawId = record.drawId;
13776
+ job.paymentRail = record.paymentRail;
13777
+ job.paymentMint = record.paymentMint;
13778
+ job.paymentTxHash = record.paymentTxHash;
13779
+ job.nativeAmount = record.nativeAmount;
13780
+ job.nativeAsset = record.nativeAsset;
13781
+ job.pendingPaymentFiatMicro = record.pendingPaymentFiatMicro;
13782
+ job.pendingPaymentFiatCurrency = record.pendingPaymentFiatCurrency;
13783
+ job.pendingMppChallengeIds = record.pendingMppChallengeIds;
13784
+ job.pendingX402Nonce = record.pendingX402Nonce;
13785
+ job.pendingX402AmountUsdcMicro = record.pendingX402AmountUsdcMicro;
13786
+ return true;
13687
13787
  }
13688
13788
  async resumeClaimedPayment(job, resume) {
13689
13789
  const retry = async () => {
@@ -14048,8 +14148,9 @@ var JobManager = class {
14048
14148
  const jobId = job.id;
14049
14149
  job.messageAppender = (msg, opts) => {
14050
14150
  const prev = job.messageAppenderTail ?? Promise.resolve();
14051
- const next = prev.then(
14052
- () => store.appendOutgoing(
14151
+ const next = prev.then(() => {
14152
+ const pending = job.pendingPayment;
14153
+ return store.appendOutgoing(
14053
14154
  jobId,
14054
14155
  {
14055
14156
  from: msg.from,
@@ -14058,7 +14159,11 @@ var JobManager = class {
14058
14159
  content: msg.content
14059
14160
  },
14060
14161
  opts
14061
- ).catch((_err) => {
14162
+ ).then((stored) => {
14163
+ if (msg.type === "payment-request" && pending && job.pendingPayment === pending) {
14164
+ this.paymentAskSeqs.set(pending, stored);
14165
+ }
14166
+ }).catch((_err) => {
14062
14167
  diagnosticConsole.error(
14063
14168
  {
14064
14169
  level: "job_message_append_failed",
@@ -14072,8 +14177,8 @@ var JobManager = class {
14072
14177
  error: void 0
14073
14178
  }
14074
14179
  );
14075
- })
14076
- );
14180
+ });
14181
+ });
14077
14182
  job.messageAppenderTail = next;
14078
14183
  };
14079
14184
  }
@@ -14804,10 +14909,13 @@ var JobManager = class {
14804
14909
  async claimAwaitingInputForReactivation(jobId) {
14805
14910
  if (!isStreamableJobStore(this.jobStore)) return true;
14806
14911
  const store = this.jobStore;
14912
+ const scaffold = this._activeJobs.get(jobId);
14913
+ const askSeq = scaffold ? this.reactivationAskSeqs.get(scaffold) : void 0;
14807
14914
  const deadline = this.monotonicNowMs() + this.reactivationClaimGraceMs;
14808
14915
  for (; ; ) {
14809
14916
  if (this.monotonicNowMs() >= deadline) {
14810
- return store.claimForProcessing(jobId);
14917
+ if (scaffold && this._activeJobs.get(jobId) !== scaffold) return false;
14918
+ return this.claimInputForAsk(jobId, askSeq);
14811
14919
  }
14812
14920
  const record = await store.get(jobId);
14813
14921
  if (record?.status !== "awaiting-input") return false;
@@ -14917,6 +15025,8 @@ var JobManager = class {
14917
15025
  }
14918
15026
  }
14919
15027
  const job = fromJobRecord(record);
15028
+ const ask = record.messages.findLast((message) => message.from === "provider" && message.type === "payment-request");
15029
+ if (ask) this.reactivationAskSeqs.set(job, ask.seq);
14920
15030
  this._activeJobs.set(job.id, job);
14921
15031
  this.wireMessageAppender(job);
14922
15032
  this.watchDurableStatus(job);
@@ -15334,7 +15444,23 @@ var JobManager = class {
15334
15444
  onYield: () => this.persistJob(job),
15335
15445
  onPaymentResume: () => retryJobSnapshot(() => this.persistJob(job)),
15336
15446
  subscribeNotifications: isStreamableJobStore(jobStore) ? (cb) => jobStore.subscribeNotifications(job.id, cb) : void 0,
15337
- getCounters: isStreamableJobStore(jobStore) ? () => jobStore.getCounters(job.id) : void 0,
15447
+ acceptedBalancePayment: this.opts.db && jobStore instanceof PostgresJobStore ? async (seq, amountMicro, currency) => {
15448
+ const { rowCount } = await this.opts.db.query(
15449
+ `SELECT 1 FROM credit_balance_payments p
15450
+ JOIN job_messages m ON m.job_id=p.job_id AND m.seq=(p.response->>'seq')::bigint AND m.status='verified'
15451
+ JOIN job_messages a ON a.job_id=p.job_id AND a.seq=p.request_seq AND a.status='verified'
15452
+ WHERE p.job_id=$1 AND m.seq=$2 AND a.data->>'type'='payment-request'
15453
+ AND (m.data->'content'->'credit'->>'request_seq')::bigint=p.request_seq
15454
+ AND a.data->'content'->'credit'->>'amount_micro'=$3
15455
+ AND a.data->'content'->'credit'->>'currency'=$4`,
15456
+ [job.id, seq, amountMicro === void 0 ? null : String(amountMicro), currency ?? null]
15457
+ );
15458
+ return rowCount === 1;
15459
+ } : void 0,
15460
+ getCounters: isStreamableJobStore(jobStore) ? async () => {
15461
+ await job.messageAppenderTail;
15462
+ return jobStore.getCounters(job.id);
15463
+ } : void 0,
15338
15464
  drawPool: this.opts.creditLedger ? () => this.resolveDrawPool(job) : void 0,
15339
15465
  pricingCurrency: this.pricingCurrency,
15340
15466
  claimForProcessing: isStreamableJobStore(jobStore) ? (resume) => {
@@ -15343,10 +15469,13 @@ var JobManager = class {
15343
15469
  if (!pending || job.pendingPayment !== pending || isTerminal(job.status)) return false;
15344
15470
  if (!await this.claimLocalInput(job)) return false;
15345
15471
  if (job.pendingPayment !== pending || isTerminal(job.status)) return false;
15472
+ let resumed = false;
15346
15473
  await this.resumeClaimedPayment(job, async () => {
15474
+ if (!await this.adoptPaymentMetadata(job, pending)) return;
15347
15475
  await resume?.();
15476
+ resumed = true;
15348
15477
  });
15349
- return true;
15478
+ return resumed;
15350
15479
  });
15351
15480
  } : void 0,
15352
15481
  onSuperseded: () => {
@@ -15379,6 +15508,12 @@ var JobManager = class {
15379
15508
  * fall back on is a slice of the credit's rail value at a ratio pinned
15380
15509
  * whenever that credit was funded, so it cannot safely fund a new ask.
15381
15510
  */
15511
+ async hasAcceptedBalancePayment(jobId) {
15512
+ return this.jobStore instanceof PostgresJobStore && !!this.opts.db && !!(await this.opts.db.query(
15513
+ "SELECT 1 FROM credit_balance_payments WHERE job_id=$1 LIMIT 1",
15514
+ [jobId]
15515
+ )).rowCount;
15516
+ }
15382
15517
  async resolveDrawPool(job) {
15383
15518
  const ledger = this.opts.creditLedger;
15384
15519
  if (!ledger || !job.creditId || !job.drawId) return { basis: "rail" };
@@ -15398,8 +15533,10 @@ var JobManager = class {
15398
15533
  return { basis: "unpriced" };
15399
15534
  }
15400
15535
  const { rail, drawMsats, creditCurrency } = draw.railValue;
15401
- if (!rail || drawMsats <= 0)
15402
- return { basis: "rail", creditCurrency: creditCurrency ?? void 0 };
15536
+ if (!rail || drawMsats <= 0) {
15537
+ const acceptedBalance = await this.hasAcceptedBalancePayment(job.id);
15538
+ if (!acceptedBalance) return { basis: "rail", creditCurrency: creditCurrency ?? void 0 };
15539
+ }
15403
15540
  if (!creditCurrency) {
15404
15541
  this.warnDrawPool(job, "credit_currency_missing");
15405
15542
  return { basis: "unpriced" };
@@ -15839,7 +15976,7 @@ var JobManager = class {
15839
15976
  const onJobCompleted = this.opts.onJobCompleted;
15840
15977
  const onRevenueSkippedNoRail = this.opts.onRevenueSkippedNoRail;
15841
15978
  if (!onJobCompleted && !onRevenueSkippedNoRail) return false;
15842
- if (job.status !== "completed" || job.paidMsats <= 0) return false;
15979
+ if (job.status !== "completed") return false;
15843
15980
  let credit = {};
15844
15981
  let creditRail;
15845
15982
  let drawAmountMicro;
@@ -15876,6 +16013,7 @@ var JobManager = class {
15876
16013
  settledAt: draw.resolvedAt ?? void 0
15877
16014
  };
15878
16015
  }
16016
+ if (job.paidMsats <= 0 && !(drawAmountMicro !== void 0 && drawAmountMicro > 0 && typeof credit.creditCurrency === "string" && creditRail && isFundingRail(creditRail))) return false;
15879
16017
  if (!job.paymentRail) {
15880
16018
  if (job.creditId && job.drawId) {
15881
16019
  const creditCurrency = typeof credit.creditCurrency === "string" ? credit.creditCurrency : void 0;
@@ -17151,13 +17289,167 @@ function withWallClockDeadline(promise, timeoutMs) {
17151
17289
  });
17152
17290
  }
17153
17291
 
17292
+ // src/sdk/server/balance-payment.ts
17293
+ import { createHash as createHash9 } from "crypto";
17294
+ import { z as z2 } from "zod";
17295
+ var BALANCE_PAYMENT_SCHEMA = z2.object({
17296
+ credit_id: z2.string().min(1).max(256),
17297
+ draw_id: z2.string().min(1).max(256),
17298
+ payment_id: z2.string().min(1).max(256),
17299
+ request_seq: z2.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER),
17300
+ amount_micro: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
17301
+ currency: z2.string().regex(/^[a-z]{3}$/),
17302
+ pubkey: z2.string(),
17303
+ signature: z2.string(),
17304
+ timestamp: z2.number(),
17305
+ nonce: z2.string()
17306
+ });
17307
+ var BalancePaymentError = class extends Error {
17308
+ constructor(status, body) {
17309
+ super(String(body.error));
17310
+ this.status = status;
17311
+ this.body = body;
17312
+ }
17313
+ status;
17314
+ body;
17315
+ };
17316
+ function conflict(error = "credit_payment_conflict") {
17317
+ throw new BalancePaymentError(409, { error });
17318
+ }
17319
+ async function commitBalancePayment(args) {
17320
+ args.auth.recheckSignature(BALANCE_PAYMENT_SCHEMA, args.raw, args.domain);
17321
+ const auth = BALANCE_PAYMENT_SCHEMA.parse(args.raw);
17322
+ const { pubkey, credit_id, draw_id, payment_id, request_seq, amount_micro, currency } = auth;
17323
+ const fingerprint = createHash9("sha256").update(JSON.stringify([
17324
+ args.domain,
17325
+ args.jobId,
17326
+ pubkey,
17327
+ credit_id,
17328
+ draw_id,
17329
+ payment_id,
17330
+ request_seq,
17331
+ amount_micro,
17332
+ currency
17333
+ ])).digest("hex");
17334
+ const tx = await args.pool.connect();
17335
+ try {
17336
+ await tx.query("BEGIN");
17337
+ await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`balance-payment:${payment_id}`]);
17338
+ const prior = await tx.query(
17339
+ "SELECT fingerprint, response FROM credit_balance_payments WHERE payment_id=$1",
17340
+ [payment_id]
17341
+ );
17342
+ if (prior.rows[0]) {
17343
+ if (prior.rows[0].fingerprint !== fingerprint) conflict();
17344
+ await tx.query("COMMIT");
17345
+ return { response: prior.rows[0].response, replayed: true };
17346
+ }
17347
+ if (args.allowFresh === false) conflict("balance_payment_unavailable");
17348
+ args.auth.checkAuth(BALANCE_PAYMENT_SCHEMA, args.raw, args.domain);
17349
+ const jobs = await tx.query("SELECT * FROM jobs WHERE id=$1 FOR UPDATE", [args.jobId]);
17350
+ const job = jobs.rows[0];
17351
+ if (!job || job.dvm_id !== null && job.dvm_id !== args.dvmId || job.requester_pubkey !== pubkey) conflict("credit_payment_owner_mismatch");
17352
+ if (["completed", "failed", "cancelled"].includes(job.status)) conflict("job_not_active");
17353
+ const asks = await tx.query(
17354
+ `SELECT data FROM job_messages WHERE job_id=$1 AND data->>'type'='payment-request'
17355
+ AND data->>'from'='provider' AND status='verified' ORDER BY seq DESC LIMIT 1`,
17356
+ [args.jobId]
17357
+ );
17358
+ const ask = asks.rows[0]?.data;
17359
+ if (!ask || ask.seq !== request_seq || !ask.content.credit || ask.content.credit.amount_micro !== amount_micro || ask.content.credit.currency !== currency || Number(job.pending_payment_fiat_micro) !== amount_micro || job.pending_payment_fiat_currency !== currency || Number(job.pending_payment_msats) <= 0 || job.asked_topup_micro === null || Number(job.asked_topup_micro) < amount_micro) {
17360
+ conflict("credit_payment_ask_mismatch");
17361
+ }
17362
+ if ((job.credit_id !== null || job.draw_id !== null) && (job.credit_id !== credit_id || job.draw_id !== draw_id)) conflict();
17363
+ const { rows: [credit] } = await tx.query(
17364
+ "SELECT caller_pubkey,currency FROM credits WHERE credit_id=$1",
17365
+ [credit_id]
17366
+ );
17367
+ if (!credit || credit.caller_pubkey !== pubkey) conflict("credit_payment_owner_mismatch");
17368
+ if (credit.currency !== currency) conflict("credit_payment_currency_mismatch");
17369
+ const seq = Number(job.next_seq);
17370
+ const message = { seq, from: "requester", type: "payment", timestamp: Math.floor(Date.now() / 1e3), content: { credit: args.raw } };
17371
+ await tx.query(`INSERT INTO job_messages (job_id,seq,data,status,created_at_ms)
17372
+ VALUES ($1,$2,$3,'pending-verification',$4)`, [args.jobId, seq, JSON.stringify(message), Date.now()]);
17373
+ if (job.credit_id === null) {
17374
+ const existing = await tx.query("SELECT 1 FROM credit_draws WHERE credit_id=$1 AND draw_id=$2", [credit_id, draw_id]);
17375
+ if (existing.rowCount) conflict();
17376
+ await args.ledger.draw({
17377
+ creditId: credit_id,
17378
+ drawId: draw_id,
17379
+ amountMicro: 0,
17380
+ jobId: args.jobId,
17381
+ hostingPaymentSeq: seq,
17382
+ tx
17383
+ });
17384
+ }
17385
+ const grown = await args.ledger.growDraw({
17386
+ creditId: credit_id,
17387
+ drawId: draw_id,
17388
+ jobId: args.jobId,
17389
+ spendExistingBalance: true,
17390
+ hostingPaymentSeq: seq,
17391
+ addAmountMicro: amount_micro,
17392
+ capMicro: Number(job.asked_topup_micro),
17393
+ tx
17394
+ });
17395
+ if (grown.addedAmountMicro !== amount_micro) conflict("credit_payment_ask_mismatch");
17396
+ const basis = grown.railValue;
17397
+ const nativeAsset = basis.nativeAsset;
17398
+ await tx.query(
17399
+ `UPDATE jobs SET next_seq=next_seq+1,
17400
+ paid_msats=paid_msats+$2, pending_payment_msats=NULL,
17401
+ pending_payment_fiat_micro=NULL, pending_payment_fiat_currency=NULL,
17402
+ pending_mpp_challenge_ids=NULL, pending_x402_nonce=NULL, pending_x402_amount_usdc_micro=NULL,
17403
+ credit_id=$3, draw_id=$4, payment_rail=$5, payment_mint=$6, payment_tx_hash=$7,
17404
+ native_amount=$8, native_asset=$9, last_activity_at=$10 WHERE id=$1`,
17405
+ [
17406
+ args.jobId,
17407
+ grown.addedRailValue.drawMsats,
17408
+ credit_id,
17409
+ draw_id,
17410
+ basis.rail,
17411
+ basis.mint,
17412
+ drawSettlementRef(args.dvmId, credit_id, draw_id),
17413
+ basis.drawNative,
17414
+ nativeAsset,
17415
+ Date.now()
17416
+ ]
17417
+ );
17418
+ await tx.query("UPDATE job_messages SET status='verified' WHERE job_id=$1 AND seq=$2", [args.jobId, seq]);
17419
+ const response = { seq };
17420
+ await tx.query(
17421
+ "INSERT INTO credit_balance_payments(payment_id,fingerprint,response,job_id,request_seq) VALUES($1,$2,$3,$4,$5)",
17422
+ [payment_id, fingerprint, JSON.stringify(response), args.jobId, request_seq]
17423
+ );
17424
+ await tx.query("SELECT pg_notify('job_messages', $1)", [args.jobId]);
17425
+ await tx.query("COMMIT");
17426
+ return { response, replayed: false };
17427
+ } catch (error) {
17428
+ await tx.query("ROLLBACK").catch(() => void 0);
17429
+ if (error instanceof CreditLedgerError && error.code === "insufficient_credit") {
17430
+ const remaining = error.details.availableMicro ?? 0;
17431
+ throw new BalancePaymentError(402, {
17432
+ error: "insufficient_credit",
17433
+ remaining_micro: remaining,
17434
+ shortfall_micro: Math.max(0, auth.amount_micro - remaining)
17435
+ });
17436
+ }
17437
+ if (error instanceof CreditLedgerError) {
17438
+ throw new BalancePaymentError(409, { error: error.code });
17439
+ }
17440
+ throw error;
17441
+ } finally {
17442
+ tx.release();
17443
+ }
17444
+ }
17445
+
17154
17446
  // src/sdk/server/app.ts
17155
- import { createHash as createHash10, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "crypto";
17447
+ import { createHash as createHash11, randomBytes as randomBytes5, randomUUID as randomUUID6 } from "crypto";
17156
17448
  import { Hono } from "hono";
17157
17449
  import { getCookie } from "hono/cookie";
17158
17450
  import { cors } from "hono/cors";
17159
17451
  import { streamSSE } from "hono/streaming";
17160
- import { toJSONSchema, z as z2 } from "zod";
17452
+ import { toJSONSchema, z as z3 } from "zod";
17161
17453
 
17162
17454
  // src/lib/git-sha.ts
17163
17455
  var DIRTY_SUFFIX = "-dirty";
@@ -17170,6 +17462,8 @@ function shortSha(sha) {
17170
17462
  }
17171
17463
 
17172
17464
  // src/sdk/server/admin-credit.ts
17465
+ import { sha256 } from "@noble/hashes/sha2.js";
17466
+ import { bytesToHex } from "@noble/hashes/utils.js";
17173
17467
  import {
17174
17468
  Amount as Amount3,
17175
17469
  CheckStateEnum as CheckStateEnum3,
@@ -17177,6 +17471,7 @@ import {
17177
17471
  getTokenMetadata as getTokenMetadata2
17178
17472
  } from "@cashu/cashu-ts";
17179
17473
  function installAdminCreditRoutes(app, opts) {
17474
+ app.post("/admin/credit/refund-draw", (c) => handleRefundDraw(c, opts));
17180
17475
  app.get("/admin/credit/pending-drains", (c) => handlePendingDrains(c, opts));
17181
17476
  app.get("/admin/credit/bitcoin-liability", (c) => handleBitcoinLiability(c, opts));
17182
17477
  app.post("/admin/credit/park-drain", (c) => handleParkDrain(c, opts));
@@ -17197,9 +17492,68 @@ function installAdminCreditRoutes(app, opts) {
17197
17492
  app.post("/admin/credit/write-off-tempo-close-report", (c) => handleTempoCloseReportAction(c, opts, "write-off"));
17198
17493
  app.get("/admin/credit/tempo-drains", (c) => handleTempoDrains(c, opts));
17199
17494
  app.get("/admin/credit/tempo-losses", (c) => handleTempoLosses(c, opts));
17495
+ app.post("/admin/credit/book-tempo-drain", (c) => handleTempoDrainDecision(c, opts, "book_refunded"));
17496
+ app.post("/admin/credit/release-tempo-drain", (c) => handleTempoDrainDecision(c, opts, "release"));
17200
17497
  app.post("/admin/credit/reconcile-tempo-drain", (c) => handleReconcileTempo(c, opts));
17201
17498
  app.post("/admin/credit/write-off-tempo-drain", (c) => handleWriteOffTempo(c, opts));
17202
17499
  }
17500
+ async function handleRefundDraw(c, opts) {
17501
+ const auth = await requireAuth(c, opts);
17502
+ if ("response" in auth) return auth.response;
17503
+ const body = await c.req.json().catch(() => null);
17504
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
17505
+ return c.json({ error: "invalid_body", message: "Request body must be a JSON object." }, 400);
17506
+ }
17507
+ const value = body;
17508
+ const nonempty = (item) => typeof item === "string" && item.length > 0;
17509
+ const byJob = nonempty(value.jobId) && value.creditId === void 0 && value.drawId === void 0;
17510
+ const byDraw = value.jobId === void 0 && nonempty(value.creditId) && nonempty(value.drawId);
17511
+ 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)) {
17512
+ 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);
17513
+ }
17514
+ if (!opts.ledger.durable || !opts.enqueueCreditDrawRefund) {
17515
+ 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);
17516
+ }
17517
+ try {
17518
+ const result = await opts.ledger.refundDraw({
17519
+ ...byJob ? { jobId: value.jobId } : { creditId: value.creditId, drawId: value.drawId },
17520
+ refundId: value.refundId,
17521
+ ...value.amountMicro === void 0 ? {} : { amountMicro: value.amountMicro },
17522
+ ...value.reason === void 0 ? {} : { reason: value.reason },
17523
+ outbox: { dvmId: opts.dvmId, enqueue: opts.enqueueCreditDrawRefund },
17524
+ ...opts.receiptIssuer ? { trustedReceiptPubkey: opts.receiptIssuer.pubkey } : {},
17525
+ ...opts.now ? { nowMs: opts.now() } : {}
17526
+ });
17527
+ return c.json({
17528
+ status: "refunded",
17529
+ refund_id: result.refundId,
17530
+ credit_id: result.creditId,
17531
+ draw_id: result.drawId,
17532
+ job_id: result.jobId,
17533
+ amount_micro: result.amountMicro,
17534
+ currency: result.currency,
17535
+ ledger_seq: result.ledgerSeq,
17536
+ refunded_at: result.refundedAt,
17537
+ balance_after_micro: result.balanceAfterMicro,
17538
+ replayed: result.replayed
17539
+ });
17540
+ } catch (error) {
17541
+ if (!(error instanceof CreditLedgerError)) throw error;
17542
+ const hints = {
17543
+ 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.",
17544
+ draw_not_completed: "Wait for the job to complete. Failed or cancelled jobs release their pending hold automatically.",
17545
+ refund_exceeds_draw: "Refund no more than the remaining unrefunded charge. Omit amountMicro to refund that remainder.",
17546
+ credit_not_open: "The original credit is closed or expired. Ask an operator to inspect the original payment before arranging a refund.",
17547
+ credit_refund_channel_unsupported: "This channel returns unused collateral through its normal exit. Ask an operator about refunding completed spending.",
17548
+ 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.",
17549
+ 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.",
17550
+ refund_conflict: "Repeat the original arguments, or deliberately choose a new refund ID for a different refund."
17551
+ };
17552
+ const hint = hints[error.code];
17553
+ if (!hint) throw error;
17554
+ return c.json({ error: error.code, hint }, error.code === "draw_not_found" ? 404 : error.code === "refund_conflict" ? 409 : 422);
17555
+ }
17556
+ }
17203
17557
  async function handleX402ExactSettlements(c, opts) {
17204
17558
  const auth = await requireAuth(c, opts);
17205
17559
  if ("response" in auth) return auth.response;
@@ -17318,6 +17672,9 @@ async function handleTempoLosses(c, opts) {
17318
17672
  currency: loss.currency,
17319
17673
  former_balance_micro: loss.formerBalanceMicro,
17320
17674
  settled_on_chain_native: loss.settledOnChainNative.toString(),
17675
+ refunded_to_payer_native: loss.refundedToPayerNative?.toString() ?? null,
17676
+ loss_native: loss.lossNative?.toString() ?? null,
17677
+ loss_basis: loss.lossBasis ?? null,
17321
17678
  highest_voucher_native: loss.highestVoucherNative.toString(),
17322
17679
  consumed_service_micro: loss.consumedServiceMicro,
17323
17680
  consumed_native: loss.consumedNative.toString(),
@@ -18304,13 +18661,18 @@ async function handleTempoDrains(c, opts) {
18304
18661
  ...after ? { after } : {},
18305
18662
  createdBeforeMs: nowMs - minAge,
18306
18663
  rail: "tempo",
18307
- includeResolved: c.req.query("include_resolved") === "true"
18664
+ includeResolved: c.req.query("include_resolved") === "true",
18665
+ includeWrittenOff: true
18308
18666
  });
18309
18667
  const wantsEvidence = c.req.query("evidence") !== "false";
18310
18668
  const rows2 = [];
18311
18669
  for (const drain of drains) {
18312
18670
  const evidence = wantsEvidence && drain.channelId ? await classifyTempoCloseEvidence(rail.rail, drain.channelId) : void 0;
18313
- rows2.push(tempoDrainBody(drain, evidence));
18671
+ const closeReports = drain.channelId && opts.hostedSettlementReports ? await opts.hostedSettlementReports.store.listTempoCloseReports({ limit: 50, all: true, channelId: drain.channelId }) : [];
18672
+ rows2.push({
18673
+ ...tempoDrainBody(drain, evidence),
18674
+ close_reports: closeReports.map((row) => ({ obligation_id: row.id, status: row.status, written_off: row.writtenOff }))
18675
+ });
18314
18676
  }
18315
18677
  const last = drains.length === limit ? drains[drains.length - 1] : void 0;
18316
18678
  return c.json({
@@ -18324,6 +18686,90 @@ async function handleTempoDrains(c, opts) {
18324
18686
  } : {}
18325
18687
  });
18326
18688
  }
18689
+ async function handleTempoDrainDecision(c, opts, decision) {
18690
+ const auth = await requireAuth(c, opts);
18691
+ if ("response" in auth) return auth.response;
18692
+ const body = await c.req.json().catch(() => null);
18693
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
18694
+ return c.json({ error: "invalid_body", message: "Request body must be a JSON object." }, 400);
18695
+ }
18696
+ const value = body;
18697
+ const text = (item, max) => typeof item === "string" && item.trim().length > 0 && item.trim().length <= max;
18698
+ 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)) {
18699
+ 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);
18700
+ }
18701
+ if (!opts.ledger.durable || !opts.ledger.decideTempoDrain) {
18702
+ return c.json({ error: "tempo_drain_decision_unavailable", message: "A durable Tempo decision store is required." }, 503);
18703
+ }
18704
+ const args = {
18705
+ creditId: value.credit_id,
18706
+ drainId: value.drain_id,
18707
+ decision,
18708
+ reason: value.reason.trim(),
18709
+ ...value.evidence === void 0 ? {} : { evidence: value.evidence.trim() },
18710
+ nowMs: opts.now ? opts.now() : Date.now()
18711
+ };
18712
+ if (decision === "book_refunded" && opts.requireDrainReport && !opts.enqueueCreditDrain) {
18713
+ const existing = await opts.ledger.getDrain(args);
18714
+ if (existing && !existing.operatorDecision && (existing.status === "pending" || existing.status === "exit_required")) {
18715
+ 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);
18716
+ }
18717
+ }
18718
+ const client = await opts.db.connect();
18719
+ let result;
18720
+ try {
18721
+ await client.query("BEGIN");
18722
+ let decided;
18723
+ const transition = async () => {
18724
+ decided = await opts.ledger.decideTempoDrain({ ...args, tx: client });
18725
+ return decided;
18726
+ };
18727
+ if (decision === "book_refunded") {
18728
+ await commitTerminalDrain({ db: opts.db, dvmId: opts.dvmId, enqueueCreditDrain: opts.enqueueCreditDrain }, transition, client);
18729
+ } else {
18730
+ await transition();
18731
+ }
18732
+ result = decided;
18733
+ await client.query("COMMIT");
18734
+ } catch (error) {
18735
+ await client.query("ROLLBACK").catch(() => void 0);
18736
+ if (error instanceof CreditLedgerError) {
18737
+ return c.json(
18738
+ { error: error.code, message: error.message },
18739
+ 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
18740
+ );
18741
+ }
18742
+ throw error;
18743
+ } finally {
18744
+ client.release();
18745
+ }
18746
+ if (!result.replayed) {
18747
+ diagnosticConsole.warn({
18748
+ dataset: "tempo",
18749
+ event: decision === "book_refunded" ? "tempo_drain_booked_as_refunded" : "tempo_drain_released",
18750
+ credit_id: result.drain.creditId,
18751
+ drain_id: result.drain.drainId,
18752
+ channel_id: result.drain.channelId,
18753
+ drain_reference: `sha256:${bytesToHex(sha256(new TextEncoder().encode(JSON.stringify([result.drain.creditId, result.drain.drainId]))))}`,
18754
+ amount_micro: result.drain.amountMicro,
18755
+ amount_native: result.drain.drainedNative?.toString() ?? null,
18756
+ currency: result.drain.currency
18757
+ });
18758
+ }
18759
+ return c.json({
18760
+ status: result.drain.status,
18761
+ credit_id: result.drain.creditId,
18762
+ drain_id: result.drain.drainId,
18763
+ channel_id: result.drain.channelId,
18764
+ amount_micro: result.drain.amountMicro,
18765
+ amount_native: result.drain.drainedNative,
18766
+ currency: result.drain.currency,
18767
+ decision: result.drain.operatorDecision,
18768
+ balance_micro: result.credit.balanceMicro,
18769
+ spendable_micro: result.spendableMicro,
18770
+ replayed: result.replayed
18771
+ });
18772
+ }
18327
18773
  async function handleReconcileTempo(c, opts) {
18328
18774
  const auth = await requireAuth(c, opts);
18329
18775
  if ("response" in auth) return auth.response;
@@ -18337,6 +18783,12 @@ async function handleReconcileTempo(c, opts) {
18337
18783
  if (record.status === "sent") return tempoReconciledBody(c, record, channelId, true);
18338
18784
  const evidence = await classifyTempoCloseEvidence(rail.rail, channelId);
18339
18785
  if (evidence.chain !== "found") return tempoEvidenceRefusal(c, evidence);
18786
+ try {
18787
+ await opts.ledger.recordTempoDrainRefund?.({ creditId: record.creditId, drainId: record.drainId, refundedNative: evidence.refundedToPayer });
18788
+ } catch (error) {
18789
+ diagnosticConsole.warn({ dataset: "tempo", event: "tempo_drain_refund_evidence_write_failed", amount_micro: record.amountMicro });
18790
+ throw error;
18791
+ }
18340
18792
  const client = await opts.db.connect();
18341
18793
  let booked;
18342
18794
  try {
@@ -18825,6 +19277,10 @@ function tempoDrainBody(drain, evidence) {
18825
19277
  created_at: drain.createdAt,
18826
19278
  write_off_note: drain.writeOffNote,
18827
19279
  written_off_at: drain.writtenOffAt,
19280
+ decision: drain.operatorDecision ?? null,
19281
+ proven_refund_native: drain.provenRefundNative ?? null,
19282
+ refund_payment_proven: drain.refundPaymentProven ?? false,
19283
+ 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,
18828
19284
  ...evidence ? { chain: evidence } : {}
18829
19285
  };
18830
19286
  }
@@ -20039,7 +20495,7 @@ initSchemaForm();
20039
20495
  }
20040
20496
 
20041
20497
  // src/sdk/server/request-fingerprint.ts
20042
- import { createHash as createHash9 } from "crypto";
20498
+ import { createHash as createHash10 } from "crypto";
20043
20499
  function requestFingerprint(req, opts) {
20044
20500
  const canonical = canonicalize({
20045
20501
  capability: req.capability,
@@ -20047,7 +20503,7 @@ function requestFingerprint(req, opts) {
20047
20503
  params: req.params ?? {},
20048
20504
  data: opts.stripAuthEnvelope ? stripEnvelope(req.data) : req.data
20049
20505
  });
20050
- return createHash9("sha256").update(canonical).digest("hex");
20506
+ return createHash10("sha256").update(canonical).digest("hex");
20051
20507
  }
20052
20508
  var ENVELOPE_KEYS = /* @__PURE__ */ new Set(["pubkey", "signature", "timestamp", "nonce", "auth_statement"]);
20053
20509
  function stripEnvelope(data) {
@@ -20170,7 +20626,7 @@ async function createDVMServer(descriptor, opts) {
20170
20626
  }
20171
20627
  }
20172
20628
  if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
20173
- const { RevenueReporter } = await import("./revenue-reporter-EV3K5L6B.js");
20629
+ const { RevenueReporter } = await import("./revenue-reporter-HD4GMXGD.js");
20174
20630
  const reporter = new RevenueReporter(
20175
20631
  opts.db,
20176
20632
  platformUrl,
@@ -20180,7 +20636,7 @@ async function createDVMServer(descriptor, opts) {
20180
20636
  revenueReporter = reporter;
20181
20637
  await reporter.init();
20182
20638
  reporter.startRetryLoop();
20183
- const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-HVJ6WUF3.js");
20639
+ const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-NXWQVKXW.js");
20184
20640
  payoutReporter = new PayoutReporter({
20185
20641
  dvmId,
20186
20642
  db: opts.db,
@@ -20208,6 +20664,7 @@ async function createDVMServer(descriptor, opts) {
20208
20664
  const effectiveOnRevenueSkippedNoRail = opts.onRevenueSkippedNoRail ?? (activeRevenueReporter ? (info) => activeRevenueReporter.reportRevenueSkippedNoRail(info) : void 0);
20209
20665
  const enqueueCreditDeposit = !opts.onCreditFunded && activeRevenueReporter ? activeRevenueReporter.enqueueDeposit.bind(activeRevenueReporter) : void 0;
20210
20666
  const enqueueCreditDrain = activeRevenueReporter ? activeRevenueReporter.enqueueCreditDrain.bind(activeRevenueReporter) : void 0;
20667
+ const enqueueCreditDrawRefund = activeRevenueReporter ? activeRevenueReporter.enqueueCreditDrawRefund.bind(activeRevenueReporter) : void 0;
20211
20668
  const enqueueCreditDrawRelease = activeRevenueReporter ? activeRevenueReporter.enqueueCreditDrawRelease.bind(activeRevenueReporter) : void 0;
20212
20669
  unsubscribeTempoSettlementReadiness = opts.tempoSettlementReadiness?.subscribe({
20213
20670
  onBalance: (observation) => activeRevenueReporter && dvmId ? activeRevenueReporter.reportTempoSettlementBalance({
@@ -20363,6 +20820,7 @@ async function createDVMServer(descriptor, opts) {
20363
20820
  } : void 0;
20364
20821
  server = new DVMServer(descriptor, {
20365
20822
  ...opts,
20823
+ platformUrl,
20366
20824
  hostingControlInstance: hostingControl,
20367
20825
  hostedSettlementReports,
20368
20826
  hostingClosureSupport,
@@ -20377,6 +20835,7 @@ async function createDVMServer(descriptor, opts) {
20377
20835
  enqueueCreditDeposit,
20378
20836
  enqueueCreditDrain,
20379
20837
  enqueueCreditDrawRelease,
20838
+ enqueueCreditDrawRefund,
20380
20839
  x402BatchSettlement,
20381
20840
  x402BatchSettlementFactory,
20382
20841
  x402ExactSettlement,
@@ -20832,8 +21291,10 @@ var DVMServer = class {
20832
21291
  receiptIssuer: this.receiptIssuer,
20833
21292
  creditTtlMs: () => this.creditTtlMs(),
20834
21293
  creditCurrency: () => this.creditCurrency(),
21294
+ enqueueCreditDrawRefund: this.opts.enqueueCreditDrawRefund,
20835
21295
  enqueueCreditDeposit: this.opts.enqueueCreditDeposit,
20836
21296
  enqueueCreditDrain: this.opts.enqueueCreditDrain,
21297
+ requireDrainReport: !!this.opts.platformUrl,
20837
21298
  lightningReceive: this.opts.lightningReceive,
20838
21299
  // x402 repair queue rides the same surface, and resolves
20839
21300
  // the rail the same lazy way the verify path does — constructing the
@@ -21088,7 +21549,7 @@ var DVMServer = class {
21088
21549
  const dvmId = this.opts.dvmId ?? `dev:${this.descriptor.name}`;
21089
21550
  return {
21090
21551
  dvm_id: dvmId,
21091
- builder_pubkey: createHash10("sha256").update(`dvmkit-dev:${dvmId}`).digest("hex")
21552
+ builder_pubkey: createHash11("sha256").update(`dvmkit-dev:${dvmId}`).digest("hex")
21092
21553
  };
21093
21554
  }
21094
21555
  throw new AuthAudienceError();
@@ -21369,6 +21830,9 @@ var DVMServer = class {
21369
21830
  creditTtlMs: this.descriptor.credit ? this.descriptor.credit.ttlSeconds * 1e3 : void 0
21370
21831
  };
21371
21832
  }
21833
+ balancePaymentSupported(includeDisabled = false) {
21834
+ return (includeDisabled || this.opts.env.DVMKIT_BALANCE_PAYMENTS !== "false") && !!this.descriptor.credit && !!this.opts.db && this.creditLedger instanceof CreditLedger && this.jobManager.store instanceof PostgresJobStore && !!this.authAudience && !!this.descriptor.auth;
21835
+ }
21372
21836
  /**
21373
21837
  * Assemble this DVM's funding menu for a response denominated in
21374
21838
  * `currency`. All the refusal logic — including the credits-spec
@@ -21379,7 +21843,7 @@ var DVMServer = class {
21379
21843
  async creditMenu(currency, callerPubkey) {
21380
21844
  await this.ensureTempoSessionHealth();
21381
21845
  const mppSplit = this.creditMppSplit();
21382
- return buildCreditMenu({
21846
+ const menu = await buildCreditMenu({
21383
21847
  runtime: "container",
21384
21848
  config: this.descriptor.credit,
21385
21849
  currency,
@@ -21398,6 +21862,8 @@ var DVMServer = class {
21398
21862
  devMode: this.opts.devMode,
21399
21863
  callerPubkey
21400
21864
  });
21865
+ if (menu && this.balancePaymentSupported()) menu.balance_payment = true;
21866
+ return menu;
21401
21867
  }
21402
21868
  async handleCredit(c) {
21403
21869
  let hostingFundingAdmissionId;
@@ -21436,7 +21902,7 @@ var DVMServer = class {
21436
21902
  const paymentAuthorization = ctx.req.header("Authorization");
21437
21903
  const mppArtifact = paymentAuthorization ? (await import("mppx")).Credential.extractPaymentScheme(paymentAuthorization) : void 0;
21438
21904
  const attachedPayment = ctx.req.header("X-Cashu") ?? ctx.req.header("PAYMENT-SIGNATURE") ?? ctx.req.header("X-PAYMENT") ?? mppArtifact;
21439
- const paymentArtifactHash = attachedPayment ? createHash10("sha256").update(attachedPayment).digest("hex") : void 0;
21905
+ const paymentArtifactHash = attachedPayment ? createHash11("sha256").update(attachedPayment).digest("hex") : void 0;
21440
21906
  hostingFundingArtifactHash = paymentArtifactHash;
21441
21907
  if (!await control.admit(admissionId, workId, {
21442
21908
  creditId,
@@ -21739,7 +22205,7 @@ var DVMServer = class {
21739
22205
  );
21740
22206
  } catch (err) {
21741
22207
  const schemaUrl = infoSchemaUrl(c.req.url, this.publicOrigin);
21742
- const { message, hint } = err instanceof z2.ZodError ? humanizeZodError(err, { schemaUrl }) : {
22208
+ const { message, hint } = err instanceof z3.ZodError ? humanizeZodError(err, { schemaUrl }) : {
21743
22209
  message: "Quote-input validation failed",
21744
22210
  hint: `Inspect ${schemaUrl} for the expected input schema.`
21745
22211
  };
@@ -22145,7 +22611,7 @@ var DVMServer = class {
22145
22611
  const authorization = c.req.header("Authorization");
22146
22612
  const mppArtifact = authorization ? (await import("mppx")).Credential.extractPaymentScheme(authorization) : void 0;
22147
22613
  const attachedPayment = c.req.header("X-Cashu") ?? c.req.header("PAYMENT-SIGNATURE") ?? c.req.header("X-PAYMENT") ?? mppArtifact;
22148
- const hostingPaymentArtifactHash = attachedPayment ? createHash10("sha256").update(attachedPayment).digest("hex") : void 0;
22614
+ const hostingPaymentArtifactHash = attachedPayment ? createHash11("sha256").update(attachedPayment).digest("hex") : void 0;
22149
22615
  if (!resumeOnly && this.opts.hostingControlInstance && hostingPaymentArtifactHash) {
22150
22616
  const priorId = await this.opts.hostingControlInstance.acceptedJobByPaymentArtifactHash(
22151
22617
  hostingPaymentArtifactHash
@@ -22443,7 +22909,7 @@ var DVMServer = class {
22443
22909
  }
22444
22910
  const isAnonymous = requesterId === "anonymous" || requesterId === "dev-anonymous";
22445
22911
  const jobToken = isAnonymous ? randomBytes5(32).toString("hex") : void 0;
22446
- const requesterTokenHash = jobToken ? createHash10("sha256").update(jobToken).digest("hex") : void 0;
22912
+ const requesterTokenHash = jobToken ? createHash11("sha256").update(jobToken).digest("hex") : void 0;
22447
22913
  const job = jm.createJob(
22448
22914
  normalisedBody,
22449
22915
  requesterId,
@@ -22939,6 +23405,53 @@ var DVMServer = class {
22939
23405
  const id = c.req.param("id") ?? "";
22940
23406
  const jm = this.jobManager;
22941
23407
  const activeJob = jm.activeJobs.get(id);
23408
+ const wire = await c.req.raw.clone().json().catch(() => void 0);
23409
+ if (wire?.type === "payment" && wire.content && typeof wire.content === "object" && "credit" in wire.content) {
23410
+ const record2 = await jm.store.get(id);
23411
+ if (!record2 || !isJobOwner(c, record2)) return c.json({ error: "Job not found" }, 404);
23412
+ if (Object.keys(wire.content).some((key) => key !== "credit")) {
23413
+ return c.json({ error: "invalid_message", message: "Credit authorization cannot be combined with a rail payment" }, 400);
23414
+ }
23415
+ if (!this.balancePaymentSupported(true) || !this.opts.db || !this.descriptor.auth || !(this.creditLedger instanceof CreditLedger)) {
23416
+ return c.json({ error: "balance_payment_unavailable" }, 409);
23417
+ }
23418
+ try {
23419
+ const result = await commitBalancePayment({
23420
+ pool: this.opts.db,
23421
+ ledger: this.creditLedger,
23422
+ auth: this.descriptor.auth,
23423
+ domain: this.signedRequestDomain(c, null),
23424
+ jobId: id,
23425
+ dvmId: this.opts.dvmId ?? "",
23426
+ raw: wire.content.credit,
23427
+ allowFresh: this.opts.env.DVMKIT_BALANCE_PAYMENTS !== "false"
23428
+ });
23429
+ try {
23430
+ await this.opts.db.query("SELECT pg_notify('job_messages', $1)", [id]);
23431
+ if (!activeJob) {
23432
+ const fresh = await jm.store.get(id);
23433
+ if (fresh?.status === "awaiting-input" && isStreamableJobStore(jm.store)) {
23434
+ fresh.messages = await jm.store.getMessages(id, 0);
23435
+ fresh.seq = Math.max(fresh.seq, ...fresh.messages.map((message) => message.seq));
23436
+ const ask = fresh.messages.findLast((message) => message.from === "provider" && message.type === "payment-request");
23437
+ const authorized = wire.content.credit;
23438
+ if (ask?.seq === authorized.request_seq && !jm.activeJobs.has(id)) {
23439
+ const { job: job2 } = jm.reactivateJob(fresh);
23440
+ const lock = await jm.store.tryReactivationLock(id, () => this.runReactivation(jm, job2, { type: "payment", content: wire.content }, fresh.status));
23441
+ if (!lock.acquired) this.teardownReactivationScaffold(jm, job2);
23442
+ }
23443
+ }
23444
+ }
23445
+ } catch {
23446
+ diagnosticConsole.error({ level: "balance_payment_resume_unavailable", job_id: id });
23447
+ }
23448
+ return c.json(result.response, 201);
23449
+ } catch (error) {
23450
+ if (error instanceof SignedRequestError) return buildAuthErrorResponse(c, error, this.publicOrigin);
23451
+ if (error instanceof BalancePaymentError) return c.json(error.body, error.status);
23452
+ throw error;
23453
+ }
23454
+ }
22942
23455
  if (activeJob) {
22943
23456
  if (!isJobOwner(c, activeJob)) return c.json({ error: "Job not found" }, 404);
22944
23457
  if (isTerminal(activeJob.status)) {
@@ -23008,6 +23521,10 @@ var DVMServer = class {
23008
23521
  }
23009
23522
  const contentErr = validateMessageContent(body.type, body.content, this.opts.devMode === true);
23010
23523
  if (contentErr) return c.json({ error: "invalid_message", message: contentErr }, 400);
23524
+ if (isStreamableJobStore(jm.store)) {
23525
+ record.messages = await jm.store.getMessages(id, 0);
23526
+ record.seq = Math.max(record.seq, ...record.messages.map((message) => message.seq));
23527
+ }
23011
23528
  let job;
23012
23529
  try {
23013
23530
  ({ job } = jm.reactivateJob(record));
@@ -23108,6 +23625,10 @@ var DVMServer = class {
23108
23625
  */
23109
23626
  async runReactivation(jm, job, body, priorStatus) {
23110
23627
  if (priorStatus === "awaiting-input") {
23628
+ if (jm.activeJobs.get(job.id) !== job) {
23629
+ this.teardownReactivationScaffold(jm, job);
23630
+ return;
23631
+ }
23111
23632
  const claimed = await jm.claimAwaitingInputForReactivation(job.id);
23112
23633
  if (!claimed) {
23113
23634
  this.teardownReactivationScaffold(jm, job);
@@ -23137,9 +23658,11 @@ var DVMServer = class {
23137
23658
  * must not persist our stale snapshot; clearing the appender prevents that.
23138
23659
  */
23139
23660
  teardownReactivationScaffold(jm, job) {
23140
- jm.clearIdleTimer(job.id);
23141
- jm.unwatchDurableStatus(job.id);
23142
- jm.activeJobs.delete(job.id);
23661
+ if (jm.activeJobs.get(job.id) === job) {
23662
+ jm.clearIdleTimer(job.id);
23663
+ jm.unwatchDurableStatus(job.id);
23664
+ jm.activeJobs.delete(job.id);
23665
+ }
23143
23666
  job.messageAppender = void 0;
23144
23667
  }
23145
23668
  async handleDeleteJob(c) {
@@ -23189,7 +23712,7 @@ function isJobOwner(c, job) {
23189
23712
  if (job.requesterTokenHash) {
23190
23713
  const token = c.req.header("X-Job-Token");
23191
23714
  if (!token) return false;
23192
- const provided = createHash10("sha256").update(token).digest("hex");
23715
+ const provided = createHash11("sha256").update(token).digest("hex");
23193
23716
  return timingSafeEqualHex(provided, job.requesterTokenHash);
23194
23717
  }
23195
23718
  return c.get("requesterId") === job.requesterId;