@dvmkit/sdk 0.19.0 → 0.19.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.
@@ -49,6 +49,7 @@ var MemoryJobStore = class {
49
49
  data = /* @__PURE__ */ new Map();
50
50
  messages = /* @__PURE__ */ new Map();
51
51
  nextSeq = /* @__PURE__ */ new Map();
52
+ outgoingAppends = /* @__PURE__ */ new Map();
52
53
  messageSubscribers = /* @__PURE__ */ new Map();
53
54
  notifySubscribers = /* @__PURE__ */ new Map();
54
55
  reactivationLocks = /* @__PURE__ */ new Set();
@@ -136,6 +137,7 @@ var MemoryJobStore = class {
136
137
  delete(id) {
137
138
  this.contentHolds.delete(id);
138
139
  this.data.delete(id);
140
+ this.outgoingAppends.delete(id);
139
141
  this.messages.delete(id);
140
142
  this.nextSeq.delete(id);
141
143
  this.receiptSeqs.delete(id);
@@ -205,6 +207,8 @@ var MemoryJobStore = class {
205
207
  if (!record || record.redactedAt !== void 0) {
206
208
  return Promise.reject(new Error(`appendOutgoing: job ${jobId} not found`));
207
209
  }
210
+ const prior = opts?.appendId ? this.outgoingAppends.get(jobId)?.get(opts.appendId) : void 0;
211
+ if (prior !== void 0) return Promise.resolve(prior);
208
212
  const seq = this.allocateSeq(jobId);
209
213
  const fullMessage = { ...message, seq };
210
214
  if (opts?.awaitingInput && !isTerminal(record.status)) {
@@ -240,6 +244,11 @@ var MemoryJobStore = class {
240
244
  }
241
245
  if (!isTerminal(record.status)) record.lastActivityAt = Date.now();
242
246
  if (seq > record.seq) record.seq = seq;
247
+ if (opts?.appendId) {
248
+ let appends = this.outgoingAppends.get(jobId);
249
+ if (!appends) this.outgoingAppends.set(jobId, appends = /* @__PURE__ */ new Map());
250
+ appends.set(opts.appendId, seq);
251
+ }
243
252
  this.notify(jobId);
244
253
  return Promise.resolve(seq);
245
254
  }
@@ -356,6 +365,9 @@ var MemoryJobStore = class {
356
365
  cancelJob(jobId, reason) {
357
366
  return this.terminateJob(jobId, reason, "cancelled", null);
358
367
  }
368
+ failJob(jobId, reason) {
369
+ return this.terminateJob(jobId, reason, "failed", null);
370
+ }
359
371
  getCounters(jobId) {
360
372
  const record = this.data.get(jobId);
361
373
  if (!record) return Promise.resolve(void 0);
@@ -435,13 +447,11 @@ var MemoryJobStore = class {
435
447
  if (!record) return Promise.resolve(false);
436
448
  const eligible = (record.status === "processing" || record.status === "working" || record.status === "awaiting-input") && (expectedActivityBefore === null || record.lastActivityAt <= expectedActivityBefore);
437
449
  if (!eligible) return Promise.resolve(false);
438
- let terminalMessages = record.messages;
450
+ const messages = this.messagesFor(jobId).filter((entry) => entry.status === "verified").map((entry) => entry.message);
451
+ const terminalMessages = messages.length ? messages : record.messages;
439
452
  if (expectedActivityBefore !== null) {
440
- const messages = this.messagesFor(jobId).filter((entry) => entry.status === "verified").map((entry) => entry.message);
441
- const history = messages.length ? messages : record.messages;
442
- terminalMessages = history;
443
- const deadline = callerDeadline({ ...record, messages: history });
444
- const awaiting = deadline !== void 0 || record.status === "awaiting-input" && !hasCallerDeadline(history);
453
+ const deadline = callerDeadline({ ...record, messages: terminalMessages });
454
+ const awaiting = deadline !== void 0 || record.status === "awaiting-input" && !hasCallerDeadline(terminalMessages);
445
455
  if (terminalStatus === "cancelled" !== awaiting || deadline !== void 0 && Date.now() < deadline) return Promise.resolve(false);
446
456
  }
447
457
  const seq = this.allocateSeq(jobId);
@@ -453,7 +463,7 @@ var MemoryJobStore = class {
453
463
  content: { reason }
454
464
  };
455
465
  this.messagesFor(jobId).push({ message: cancelMsg, status: "verified" });
456
- record.endedBy = expectedActivityBefore === null || terminalStatus === "cancelled" ? "caller" : "provider";
466
+ record.endedBy = terminalStatus === "cancelled" ? "caller" : "provider";
457
467
  applyTiming(record, structuredClone(record), Date.now(), terminalStatus);
458
468
  record.status = terminalStatus;
459
469
  record.summary = record.summary ?? reason;
@@ -345,6 +345,15 @@ var PostgresJobStore = class {
345
345
  ALTER TABLE job_messages ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'verified';
346
346
  ALTER TABLE job_messages ADD COLUMN IF NOT EXISTS created_at_ms BIGINT;
347
347
 
348
+ -- Survives terminal message cleanup so a lost COMMIT acknowledgement can
349
+ -- be retried without allocating another sequence or applying counters twice.
350
+ CREATE TABLE IF NOT EXISTS job_outgoing_appends (
351
+ job_id TEXT NOT NULL REFERENCES jobs(id) ON DELETE CASCADE,
352
+ append_id TEXT NOT NULL,
353
+ seq INTEGER NOT NULL,
354
+ PRIMARY KEY (job_id, append_id)
355
+ );
356
+
348
357
  -- per-DVM monotonic receipt counter. Single row by
349
358
  -- construction (the CHECK pins the key), and per-DVM by construction
350
359
  -- too \u2014 the SDK runs one database per DVM (see postgres-kv-store.ts).
@@ -791,7 +800,11 @@ var PostgresJobStore = class {
791
800
  if (this.hostingCleanupFence) {
792
801
  throw new Error("Hosted job deletion cannot discard obligation evidence");
793
802
  }
794
- await this.pool.query("DELETE FROM jobs WHERE id = $1", [id]);
803
+ await this.pool.query(
804
+ `WITH deleted AS (DELETE FROM jobs WHERE id=$1 RETURNING id)
805
+ DELETE FROM job_outgoing_appends WHERE job_id IN (SELECT id FROM deleted)`,
806
+ [id]
807
+ );
795
808
  await this.deleteResolvedMessages(id).catch(() => void 0);
796
809
  }
797
810
  async listJobsForRetention(args) {
@@ -945,6 +958,18 @@ var PostgresJobStore = class {
945
958
  const client = await this.pool.connect();
946
959
  try {
947
960
  await client.query("BEGIN");
961
+ if (opts?.appendId) {
962
+ const owner = await client.query("SELECT id FROM jobs WHERE id=$1 AND redacted_at IS NULL FOR UPDATE", [jobId]);
963
+ if (!owner.rows.length) throw new Error(`appendOutgoing: job ${jobId} not found`);
964
+ const prior = await client.query(
965
+ "SELECT seq FROM job_outgoing_appends WHERE job_id=$1 AND append_id=$2",
966
+ [jobId, opts.appendId]
967
+ );
968
+ if (prior.rows[0]) {
969
+ await client.query("COMMIT");
970
+ return prior.rows[0].seq;
971
+ }
972
+ }
948
973
  const { rows } = await client.query(
949
974
  `UPDATE jobs SET next_seq = next_seq + 1, last_activity_at = CASE WHEN status IN ('completed','failed','cancelled') THEN last_activity_at ELSE $2 END,
950
975
  awaiting_input_at_ms = CASE WHEN $3::boolean AND status NOT IN ('completed', 'failed', 'cancelled')
@@ -1024,6 +1049,12 @@ var PostgresJobStore = class {
1024
1049
  ]
1025
1050
  );
1026
1051
  }
1052
+ if (opts?.appendId) {
1053
+ await client.query(
1054
+ "INSERT INTO job_outgoing_appends (job_id, append_id, seq) VALUES ($1,$2,$3)",
1055
+ [jobId, opts.appendId, seq]
1056
+ );
1057
+ }
1027
1058
  await client.query("SELECT pg_notify('job_messages', $1)", [jobId]);
1028
1059
  await client.query("COMMIT");
1029
1060
  return seq;
@@ -1239,6 +1270,9 @@ var PostgresJobStore = class {
1239
1270
  cancelJob(jobId, reason) {
1240
1271
  return this.terminateJob(jobId, reason, "cancelled", null, "caller");
1241
1272
  }
1273
+ failJob(jobId, reason) {
1274
+ return this.terminateJob(jobId, reason, "failed", null, "provider");
1275
+ }
1242
1276
  cancelJobForHosting(jobId, reason) {
1243
1277
  return this.terminateJob(jobId, reason, "cancelled", null, "provider");
1244
1278
  }
@@ -1429,17 +1463,15 @@ var PostgresJobStore = class {
1429
1463
  await client.query("ROLLBACK");
1430
1464
  return false;
1431
1465
  }
1432
- let terminalMessages;
1466
+ const record = rowToRecord(rows[0]);
1467
+ const { rows: messages } = await client.query(
1468
+ "SELECT data FROM job_messages WHERE job_id=$1 AND status='verified' ORDER BY seq",
1469
+ [jobId]
1470
+ );
1471
+ const terminalMessages = messages.length ? messages.map((row) => row.data) : record.messages;
1433
1472
  if (staleOnly) {
1434
- const record = rowToRecord(rows[0]);
1435
- const { rows: messages } = await client.query(
1436
- "SELECT data FROM job_messages WHERE job_id=$1 AND status='verified' ORDER BY seq",
1437
- [jobId]
1438
- );
1439
- const history = messages.length ? messages.map((row) => row.data) : record.messages;
1440
- terminalMessages = history;
1441
- const deadline = callerDeadline({ ...record, messages: history });
1442
- const awaiting = deadline !== void 0 || record.status === "awaiting-input" && !hasCallerDeadline(history);
1473
+ const deadline = callerDeadline({ ...record, messages: terminalMessages });
1474
+ const awaiting = deadline !== void 0 || record.status === "awaiting-input" && !hasCallerDeadline(terminalMessages);
1443
1475
  if (terminalStatus === "cancelled" !== awaiting || deadline !== void 0 && Date.now() < deadline) {
1444
1476
  await client.query("ROLLBACK");
1445
1477
  return false;
@@ -1467,7 +1499,7 @@ var PostgresJobStore = class {
1467
1499
  awaiting_input_at_ms = NULL,
1468
1500
  status = $6,
1469
1501
  summary = COALESCE(summary, $2),
1470
- messages = ${staleOnly ? "$3::jsonb" : "COALESCE(messages, '[]'::jsonb) || $3::jsonb"},
1502
+ messages = $3::jsonb,
1471
1503
  seq = $4,
1472
1504
  next_seq = $4 + 1,
1473
1505
  last_activity_at = $5
@@ -1475,7 +1507,7 @@ var PostgresJobStore = class {
1475
1507
  [
1476
1508
  jobId,
1477
1509
  reason,
1478
- JSON.stringify([...terminalMessages ?? [], cancelMsg]),
1510
+ JSON.stringify([...terminalMessages, cancelMsg]),
1479
1511
  nextSeq,
1480
1512
  now,
1481
1513
  terminalStatus,
@@ -20,7 +20,7 @@ import {
20
20
  } from "./chunk-DJRTYBEE.js";
21
21
  import {
22
22
  PostgresJobStore
23
- } from "./chunk-A6EY4K46.js";
23
+ } from "./chunk-QCZXKUS2.js";
24
24
  import {
25
25
  CASHU_MIN_LOCKTIME_SECONDS,
26
26
  DEFAULT_CASHU_LOCK_WINDOW_SECONDS,
@@ -42,7 +42,7 @@ import {
42
42
  isJobRetentionStore,
43
43
  isReceiptIssuingStore,
44
44
  isStreamableJobStore
45
- } from "./chunk-Y6JJDOGO.js";
45
+ } from "./chunk-6RJAE332.js";
46
46
  import {
47
47
  ContentHoldError,
48
48
  JobCancelledError,
@@ -14604,6 +14604,7 @@ var ORPHAN_SWEEP_CLAIMS = /* @__PURE__ */ new WeakSet();
14604
14604
  var ATTACH_RAILS = ["cashu", "x402", "tempo"];
14605
14605
  var CREDIT_INBOUND_ATTEMPTS = 3;
14606
14606
  var CREDIT_INBOUND_BACKOFF_MS = 50;
14607
+ var TERMINAL_PERSIST_RETRY_MS = 3e4;
14607
14608
  var TERMINAL_COST_HANDOFF_RETRY_INITIAL_MS = 100;
14608
14609
  var TERMINAL_COST_HANDOFF_RETRY_MAX_MS = 3e4;
14609
14610
  var CREDIT_INBOUND_READ_ATTEMPTS = 2;
@@ -14618,6 +14619,11 @@ var JobManager = class {
14618
14619
  idleTimers = /* @__PURE__ */ new Map();
14619
14620
  cleanupTimers = /* @__PURE__ */ new Map();
14620
14621
  terminalReadRepairs = /* @__PURE__ */ new Map();
14622
+ terminalSnapshots = /* @__PURE__ */ new WeakMap();
14623
+ terminalFailures = /* @__PURE__ */ new WeakMap();
14624
+ terminalPersistenceRetries = /* @__PURE__ */ new Map();
14625
+ terminalPersistenceStopped = false;
14626
+ outgoingQueues = /* @__PURE__ */ new WeakMap();
14621
14627
  /** Cost revisions held only by a terminal handler owner until its durable merge succeeds. */
14622
14628
  terminalCostHandoffRetryTimers = /* @__PURE__ */ new Map();
14623
14629
  /** Per-job NOTIFY unsubscribe handles for the durable-status watch. */
@@ -15229,10 +15235,82 @@ var JobManager = class {
15229
15235
  * subscriber's NOTIFY-driven fetch can see it).
15230
15236
  */
15231
15237
  async persistJob(job) {
15238
+ if (!isTerminal(job.status)) return this.persistSnapshot(job);
15239
+ let work = this.terminalSnapshots.get(job);
15240
+ if (!work) {
15241
+ work = this.persistTerminalSnapshot(job).then(() => {
15242
+ clearTimeout(this.terminalPersistenceRetries.get(job.id));
15243
+ this.terminalPersistenceRetries.delete(job.id);
15244
+ }).catch((error) => {
15245
+ if (this.terminalFailures.has(job)) this.scheduleTerminalPersistenceRetry(job);
15246
+ throw error;
15247
+ }).finally(() => this.terminalSnapshots.delete(job));
15248
+ this.terminalSnapshots.set(job, work);
15249
+ }
15250
+ return work;
15251
+ }
15252
+ scheduleTerminalPersistenceRetry(job) {
15253
+ if (this.terminalPersistenceStopped || this.terminalPersistenceRetries.has(job.id)) return;
15254
+ const timer = setTimeout(() => {
15255
+ this.terminalPersistenceRetries.delete(job.id);
15256
+ job.terminalWork = this.handleJobTerminal(job).catch(() => {
15257
+ this.scheduleTerminalPersistenceRetry(job);
15258
+ });
15259
+ }, 1e3);
15260
+ timer.unref?.();
15261
+ this.terminalPersistenceRetries.set(job.id, timer);
15262
+ }
15263
+ async persistTerminalSnapshot(job) {
15264
+ const deadline = performance.now() + TERMINAL_PERSIST_RETRY_MS;
15265
+ await job.messageAppenderTail;
15266
+ while (!this.terminalFailures.has(job)) {
15267
+ try {
15268
+ if (await this.adoptDurableTerminal(job)) return;
15269
+ await this.flushOutgoing(job);
15270
+ await this.persistSnapshot(job, true);
15271
+ return;
15272
+ } catch {
15273
+ if (performance.now() >= deadline) break;
15274
+ await new Promise((resolve) => setTimeout(resolve, Math.min(500, deadline - performance.now())));
15275
+ }
15276
+ }
15277
+ const reason = this.terminalFailures.get(job) ?? (job.status === "completed" ? "terminal_persistence_failed" : job.messages.findLast((m) => m.type === "cancel")?.content?.reason ?? job.summary ?? "terminal_persistence_failed");
15278
+ this.terminalFailures.set(job, reason);
15279
+ const durable = await this.jobStore.get(job.id);
15280
+ if (durable && isTerminal(durable.status)) {
15281
+ const adopted = await this.adoptDurableTerminal(job);
15282
+ if (!adopted && durable.status !== "completed") await this.tryPersistAdoptedTerminalCost(job, 1);
15283
+ return;
15284
+ }
15285
+ job.status = "failed";
15286
+ job.summary = reason;
15287
+ this.outgoingQueues.delete(job);
15288
+ const store = this.jobStore;
15289
+ if (isStreamableJobStore(store) && store.failJob) {
15290
+ await store.failJob(job.id, reason);
15291
+ const saved = await store.get(job.id);
15292
+ if (!saved || !isTerminal(saved.status)) throw new JobSnapshotPersistenceError("failure not committed");
15293
+ job.status = saved.status;
15294
+ job.summary = saved.summary;
15295
+ job.messages = saved.messages;
15296
+ job.seq = saved.seq;
15297
+ job.endedBy = saved.endedBy;
15298
+ job.terminalAtMs = saved.terminalAtMs;
15299
+ job.callerWaitMs = saved.callerWaitMs;
15300
+ job.timingKnown = saved.timingKnown;
15301
+ await this.tryPersistAdoptedTerminalCost(job, 1);
15302
+ } else {
15303
+ job.messages = job.messages.filter((m) => m.type !== "complete");
15304
+ localMessage(job, "provider", "cancel", { reason });
15305
+ await this.persistSnapshot(job, true);
15306
+ }
15307
+ }
15308
+ async persistSnapshot(job, terminalReady = false) {
15232
15309
  if (job.messageAppenderTail) {
15233
15310
  await job.messageAppenderTail.catch(() => void 0);
15234
15311
  }
15235
15312
  if (await this.adoptDurableTerminal(job)) return;
15313
+ if (isTerminal(job.status) && !terminalReady) return this.persistJob(job);
15236
15314
  const record = toJobRecord(job);
15237
15315
  if (isTerminal(job.status) && isStreamableJobStore(this.jobStore)) {
15238
15316
  const durable = await this.jobStore.getMessages(job.id, 0, { includeHeld: true });
@@ -15501,44 +15579,37 @@ var JobManager = class {
15501
15579
  */
15502
15580
  wireMessageAppender(job) {
15503
15581
  if (!isStreamableJobStore(this.jobStore)) return;
15504
- const store = this.jobStore;
15505
15582
  const jobId = job.id;
15506
15583
  job.messageAppender = (msg, opts) => {
15584
+ let queue = this.outgoingQueues.get(job);
15585
+ if (!queue) this.outgoingQueues.set(job, queue = []);
15586
+ queue.push({ msg: structuredClone(msg), opts: { ...opts, appendId: randomBytes4(16).toString("hex") } });
15507
15587
  const prev = job.messageAppenderTail ?? Promise.resolve();
15508
- const next = prev.then(() => {
15509
- const pending = job.pendingPayment;
15510
- return store.appendOutgoing(
15511
- jobId,
15512
- {
15513
- from: msg.from,
15514
- type: msg.type,
15515
- timestamp: msg.timestamp,
15516
- content: msg.content
15517
- },
15518
- opts
15519
- ).then((stored) => {
15520
- if (msg.type === "payment-request" && pending && job.pendingPayment === pending) {
15521
- this.paymentAskSeqs.set(pending, stored);
15522
- }
15523
- }).catch((_err) => {
15524
- diagnosticConsole.error(
15525
- {
15526
- level: "job_message_append_failed",
15527
- jobId,
15528
- // In-memory counter, NOT the authoritative DB-allocated seq:
15529
- // `appendOutgoing` allocates the real seq via `UPDATE ...
15530
- // RETURNING` inside the transaction that just failed, so no DB
15531
- // seq was assigned for this message.
15532
- localSeq: msg.seq,
15533
- note: void 0,
15534
- error: void 0
15535
- }
15536
- );
15537
- });
15588
+ job.messageAppenderTail = prev.then(() => this.flushOutgoing(job)).catch(() => {
15589
+ diagnosticConsole.error({ level: "job_message_append_failed", jobId, localSeq: msg.seq });
15538
15590
  });
15539
- job.messageAppenderTail = next;
15540
15591
  };
15541
15592
  }
15593
+ async flushOutgoing(job) {
15594
+ const store = this.jobStore;
15595
+ if (!isStreamableJobStore(store)) return;
15596
+ const queue = this.outgoingQueues.get(job);
15597
+ while (queue?.length) {
15598
+ const entry = queue[0];
15599
+ if (entry.pending === void 0) entry.pending = job.pendingPayment;
15600
+ const { msg, opts, pending } = entry;
15601
+ const stored = await store.appendOutgoing(job.id, {
15602
+ from: msg.from,
15603
+ type: msg.type,
15604
+ timestamp: msg.timestamp,
15605
+ content: msg.content
15606
+ }, opts);
15607
+ if (msg.type === "payment-request" && pending && job.pendingPayment === pending) {
15608
+ this.paymentAskSeqs.set(pending, stored);
15609
+ }
15610
+ queue.shift();
15611
+ }
15612
+ }
15542
15613
  startIdleTimer(job, deadline = callerDeadline(job, this.descriptor.idleTimeout * 1e3)) {
15543
15614
  this.clearIdleTimer(job.id);
15544
15615
  if (deadline === void 0) return;
@@ -17157,7 +17228,8 @@ var JobManager = class {
17157
17228
  try {
17158
17229
  const seq = await store.claimReceiptSeq(record.id);
17159
17230
  const receipt = issuer.issue(record, seq, void 0, credit);
17160
- const saved = await store.saveReceipt(record.id, receipt) ?? receipt;
17231
+ const saved = await store.saveReceipt(record.id, receipt);
17232
+ if (!saved) return void 0;
17161
17233
  await this.observePaidCompletion(record);
17162
17234
  return saved;
17163
17235
  } catch (_err) {
@@ -17196,6 +17268,17 @@ var JobManager = class {
17196
17268
  async resolveCreditDraw(record) {
17197
17269
  const ledger = this.opts.creditLedger;
17198
17270
  if (!ledger || !record.creditId || !record.drawId) return void 0;
17271
+ const args = this.drawResolutionArgs(record.creditId, record.drawId);
17272
+ const existing = await ledger.getDraw(args);
17273
+ if (existing?.status === "settled" && this.settlesDraw(record.status)) {
17274
+ return {
17275
+ credit_id: existing.creditId,
17276
+ draw_id: existing.drawId,
17277
+ amount: existing.amountMicro,
17278
+ balance_after: existing.balanceAfterMicro,
17279
+ ledger_seq: existing.ledgerSeq
17280
+ };
17281
+ }
17199
17282
  const deferredIntent = deferredX402IntentId(record.creditId);
17200
17283
  if (deferredIntent) {
17201
17284
  const deferred = this.opts.deferredX402;
@@ -17214,7 +17297,6 @@ var JobManager = class {
17214
17297
  return void 0;
17215
17298
  }
17216
17299
  }
17217
- const args = this.drawResolutionArgs(record.creditId, record.drawId);
17218
17300
  try {
17219
17301
  const res = this.settlesDraw(record.status) ? await ledger.settle(args) : await ledger.release(args);
17220
17302
  return {
@@ -17400,7 +17482,7 @@ var JobManager = class {
17400
17482
  this.clearIdleTimer(job.id);
17401
17483
  if (await this.adoptDurableTerminal(job)) return;
17402
17484
  const durable = await this.jobStore.get(job.id);
17403
- if (!durable || !isTerminal(durable.status)) await this.persistJob(job);
17485
+ if (!durable || !isTerminal(durable.status) || this.terminalFailures.has(job)) await this.persistJob(job);
17404
17486
  await this.attachReceipt(job);
17405
17487
  const booked = await this.bookRevenue(job);
17406
17488
  if (!booked) await this.reportJobCost(job);
@@ -17673,6 +17755,7 @@ var JobManager = class {
17673
17755
  this.cleanupTimers.set(job.id, timer);
17674
17756
  }
17675
17757
  shutdown() {
17758
+ this.terminalPersistenceStopped = true;
17676
17759
  for (const [id] of this.idleTimers) {
17677
17760
  this.clearIdleTimer(id);
17678
17761
  }
@@ -17680,6 +17763,8 @@ var JobManager = class {
17680
17763
  clearTimeout(timer);
17681
17764
  this.cleanupTimers.delete(id);
17682
17765
  }
17766
+ for (const timer of this.terminalPersistenceRetries.values()) clearTimeout(timer);
17767
+ this.terminalPersistenceRetries.clear();
17683
17768
  for (const [id, timer] of this.terminalCostHandoffRetryTimers) {
17684
17769
  clearTimeout(timer);
17685
17770
  this.terminalCostHandoffRetryTimers.delete(id);
@@ -24543,11 +24628,12 @@ var DVMServer = class {
24543
24628
  async contentFields(job) {
24544
24629
  const store = this.jobManager.store;
24545
24630
  const responseStatus = job.status;
24631
+ const evidence = isTerminal(job.status) && this.jobManager.receiptsEnabled && !job.receipt ? { receipt_pending: true } : {};
24546
24632
  if (isContentHoldStore(store)) {
24547
24633
  const snapshot = await store.getContentSnapshot(job.id);
24548
- if (snapshot) return { ...contentMetadata({ ...snapshot.job, status: responseStatus }, this.descriptor.jobRetentionDays, snapshot.history.holds) };
24634
+ if (snapshot) return { ...evidence, ...contentMetadata({ ...snapshot.job, status: responseStatus }, this.descriptor.jobRetentionDays, snapshot.history.holds) };
24549
24635
  }
24550
- return isContentHoldStore(store) ? { ...contentMetadata(job, this.descriptor.jobRetentionDays) } : {};
24636
+ return isContentHoldStore(store) ? { ...evidence, ...contentMetadata(job, this.descriptor.jobRetentionDays) } : evidence;
24551
24637
  }
24552
24638
  async messageFields(job, messages) {
24553
24639
  const fields = await this.contentFields(job);
@@ -1,4 +1,4 @@
1
- import { b as JobStore, J as JobRecord } from './job-store-Dy-ogco6.js';
1
+ import { b as JobStore, J as JobRecord } from './job-store-D4-18IKm.js';
2
2
 
3
3
  /** Authenticated caller metadata; omitted fields on older services mean unknown. */
4
4
  interface JobContentMetadata {
@@ -5,7 +5,7 @@ import { Hono, Context } from 'hono';
5
5
  import { Pool } from 'pg';
6
6
  import { d as FxSourceFailure, b as FxRateSnapshot, F as FxFetcher } from './fx-QoVA2oBc.js';
7
7
  import { Store, Challenge } from 'mppx';
8
- import { T as TopUpCapUnenforcedReason, A as AppendOutgoingOptions, J as JobRecord, b as JobStore } from './job-store-Dy-ogco6.js';
8
+ import { T as TopUpCapUnenforcedReason, A as AppendOutgoingOptions, J as JobRecord, b as JobStore } from './job-store-D4-18IKm.js';
9
9
  import { SettleResponse, SupportedResponse } from '@x402/core/types';
10
10
  import { Channel, AutoSettlementConfig } from '@x402/evm/batch-settlement/server';
11
11
  import { FacilitatorClient } from '@x402/core/server';
@@ -3131,6 +3131,11 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
3131
3131
  private readonly idleTimers;
3132
3132
  private readonly cleanupTimers;
3133
3133
  private readonly terminalReadRepairs;
3134
+ private readonly terminalSnapshots;
3135
+ private readonly terminalFailures;
3136
+ private readonly terminalPersistenceRetries;
3137
+ private terminalPersistenceStopped;
3138
+ private readonly outgoingQueues;
3134
3139
  /** Cost revisions held only by a terminal handler owner until its durable merge succeeds. */
3135
3140
  private readonly terminalCostHandoffRetryTimers;
3136
3141
  /** Per-job NOTIFY unsubscribe handles for the durable-status watch. */
@@ -3323,6 +3328,9 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
3323
3328
  * subscriber's NOTIFY-driven fetch can see it).
3324
3329
  */
3325
3330
  persistJob(job: ServerJob): Promise<void>;
3331
+ private scheduleTerminalPersistenceRetry;
3332
+ private persistTerminalSnapshot;
3333
+ private persistSnapshot;
3326
3334
  /**
3327
3335
  * Cross-machine terminal guard. `buildContext`'s guard reads the
3328
3336
  * in-memory `job.status`, so on its own it only protects the machine running
@@ -3407,6 +3415,7 @@ declare class JobManager<State, InputSchema extends ZodLike | undefined> {
3407
3415
  * `artifact`'s NOTIFY silently drops the artifact.
3408
3416
  */
3409
3417
  private wireMessageAppender;
3418
+ private flushOutgoing;
3410
3419
  startIdleTimer(job: ServerJob, deadline?: number | undefined): void;
3411
3420
  clearIdleTimer(id: string): void;
3412
3421
  /** Cancel a job due to idle timeout. Exposed for the reactivation path's idle-expiry guard. */
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { C as Currency, Z as ZodLike, D as DVMConfig, a as DVMDescriptor } from
2
2
  export { A as ApprovalContent, b as ArtifactContent, c as CancelContent, d as CanonicalEnvelope, e as CapabilityRate, f as CashuConfig, g as CreateSignedRequestVerifierOpts, h as CreditConfig, i as CreditView, j as DEFAULT_CREDIT_MAX, k as DEFAULT_CREDIT_MIN, l as DEFAULT_CREDIT_TTL_SECONDS, m as DEFAULT_JOB_RETENTION_DAYS, n as DVMRouteContext, I as IncomingMessage, o as InputType, p as InvalidCurrencyError, J as JobCost, K as KVStore, L as Logger, P as PaymentContent, q as PaymentMethod, r as PriceValue, s as ProgressContent, t as PromptOpts, Q as QuoteConfig, u as QuoteContext, v as QuoteResult, R as ResolvedCashuConfig, w as ResolvedCreditConfig, x as ResponseContent, S as SDKJobContext, y as SDKPaymentRequestOpts, z as SIGNED_REQUEST_AUTH_ID, B as SIGNED_REQUEST_STATEMENT_VERSION, E as SignedRequestAudience, F as SignedRequestDomain, G as SignedRequestError, H as SignedRequestFailure, M as SignedRequestReplayStore, N as SignedRequestSignOpts, O as SignedRequestStatementHeader, T as SignedRequestVerifier, U as UnsupportedCurrencyError, V as UpfrontPayment, W as createSignedRequestVerifier, X as isZodSchema, Y as signedRequestStatementHeader, _ as validateCurrency } from './step-cache-B83Y9T9p.js';
3
3
  export { C as CreateFxFetcherOpts, D as DEFAULT_FX_CURRENCIES, a as DEFAULT_FX_RATE_SOURCE, F as FxFetcher, b as FxRateSnapshot, c as FxRateUnavailableError, d as FxSourceFailure, e as FxSourceFailureCategory, P as PlatformFxSource, f as createFxFetcher, g as fxRateFor, r as resolveFxSourceFromEnv } from './fx-QoVA2oBc.js';
4
4
  export { I as InvalidFxRateError, f as fiatToSatsCeil, a as formatFiat, b as formatUsd, r as roundUsd, s as satsToFiat } from './usd-g9kCw86F.js';
5
- export { J as JobRecord, a as JobStatus, b as JobStore } from './job-store-Dy-ogco6.js';
5
+ export { J as JobRecord, a as JobStatus, b as JobStore } from './job-store-D4-18IKm.js';
6
6
  export { P as PinnedFetch, S as SSRFError, a as SSRFGuardOpts, b as SSRFReason, c as SSRFResolver, d as assertSafeUrl, e as createPinnedFetch } from './ssrf-dMooihtY.js';
7
7
  export { z } from 'zod';
8
8
  import 'hono';
@@ -6,9 +6,9 @@ import { Env, BlankSchema } from 'hono/types';
6
6
  import { Pool } from 'pg';
7
7
  export { Pool } from 'pg';
8
8
  import { g as getWallet } from '../wallet-Dwjs5n_M.js';
9
- import { C as ConsumedCredentialStore, L as LockPubkeyStateLoader } from '../credit-menu-D88XuTLU.js';
10
- export { A as AccumulatorPool, a as AppEnv, b as CreditFundingReport, D as DEFAULT_LOCK_PUBKEY_GRACE_SECONDS, F as FiatDenomination, c as FiatDenominationFailure, d as FundOnlyRequest, I as IncomingPaymentOpts, J as JobCancelledError, e as LockPubkeyPool, f as LockPubkeyState, M as MintHealthTracker, P as PAYMENT_PROOF_KEYS, g as PaymentErrorCode, h as PaymentErrorDetail, i as PaymentInfo, j as PostgresTempoChargeStore, R as ReporterBannerOpts, k as ResolvedFx, l as RevenueBootCheckOpts, S as SDKServerOpts, m as ServerJob, n as ShortPayForfeit, T as TempoChannelReport, o as TempoObserverHealth, p as TempoSessionChannelMismatchError, U as UpfrontPaymentOpts, V as VerifiedIncomingPayment, q as VerifyIncomingSnapshot, X as X402BatchChannelObservation, r as X402FacilitatorHealth, s as X402SettlementEvidenceOutcome, t as X402SettlementRepair, u as X402SettlementRepairRefusal, v as X402SettlementRepaired, w as X402WedgedSettlement, x as X402WedgedSettlementPage, y as X402_BATCH_SETTLEMENT_MAINNET_NETWORK, z as abortJob, B as activePubkeySet, E as applyPaymentInfoToJob, G as assertRevenueReporterReady, H as buildPaymentErrorResponse, K as clearAccumulatorForDvm, N as createDVMServer, O as creditDepositPayload, Q as derivedFundCreditId, W as devModeSkipsPaymentVerification, Y as fromJobRecord, Z as fundedMicroFor, _ as hasPaymentProof, $ as hashLockKey, a0 as implicitCreditId, a1 as initLockPubkeyTable, a2 as initWalletAccumulatorTable, a3 as insertAccumulatorRows, a4 as isDerivedCreditId, a5 as isTerminal, a6 as isYieldMessage, a7 as issueUpfrontChallenges, a8 as loadLockPubkeyState, a9 as msatsToFiatMicro, aa as paymentErrorBody, ab as pinAskFiat, ac as priceFiatMicro, ad as processIncomingPayment, ae as providerMessage, af as pruneRetired, ag as repairX402ExactSettlementEffect, ah as resolveFxSnapshot, ai as resolvePriceFiat, aj as revenueReporterBannerState, ak as rotateLockPubkey, al as seedLockPubkeyState, am as toCreditTerms, an as toJobRecord, ao as unknownRouteNotFound, ap as verifyIncomingPayment, aq as verifyTempoSessionManagementCredential, ar as verifyUpfrontPayment, as as x402RequiredUsdcMicro, at as x402SettledShare } from '../credit-menu-D88XuTLU.js';
11
- export { O as OutgoingMessage, S as StaleJobReapable, i as isStaleJobReapable } from '../job-store-Dy-ogco6.js';
9
+ import { C as ConsumedCredentialStore, L as LockPubkeyStateLoader } from '../credit-menu-BlODG4E7.js';
10
+ export { A as AccumulatorPool, a as AppEnv, b as CreditFundingReport, D as DEFAULT_LOCK_PUBKEY_GRACE_SECONDS, F as FiatDenomination, c as FiatDenominationFailure, d as FundOnlyRequest, I as IncomingPaymentOpts, J as JobCancelledError, e as LockPubkeyPool, f as LockPubkeyState, M as MintHealthTracker, P as PAYMENT_PROOF_KEYS, g as PaymentErrorCode, h as PaymentErrorDetail, i as PaymentInfo, j as PostgresTempoChargeStore, R as ReporterBannerOpts, k as ResolvedFx, l as RevenueBootCheckOpts, S as SDKServerOpts, m as ServerJob, n as ShortPayForfeit, T as TempoChannelReport, o as TempoObserverHealth, p as TempoSessionChannelMismatchError, U as UpfrontPaymentOpts, V as VerifiedIncomingPayment, q as VerifyIncomingSnapshot, X as X402BatchChannelObservation, r as X402FacilitatorHealth, s as X402SettlementEvidenceOutcome, t as X402SettlementRepair, u as X402SettlementRepairRefusal, v as X402SettlementRepaired, w as X402WedgedSettlement, x as X402WedgedSettlementPage, y as X402_BATCH_SETTLEMENT_MAINNET_NETWORK, z as abortJob, B as activePubkeySet, E as applyPaymentInfoToJob, G as assertRevenueReporterReady, H as buildPaymentErrorResponse, K as clearAccumulatorForDvm, N as createDVMServer, O as creditDepositPayload, Q as derivedFundCreditId, W as devModeSkipsPaymentVerification, Y as fromJobRecord, Z as fundedMicroFor, _ as hasPaymentProof, $ as hashLockKey, a0 as implicitCreditId, a1 as initLockPubkeyTable, a2 as initWalletAccumulatorTable, a3 as insertAccumulatorRows, a4 as isDerivedCreditId, a5 as isTerminal, a6 as isYieldMessage, a7 as issueUpfrontChallenges, a8 as loadLockPubkeyState, a9 as msatsToFiatMicro, aa as paymentErrorBody, ab as pinAskFiat, ac as priceFiatMicro, ad as processIncomingPayment, ae as providerMessage, af as pruneRetired, ag as repairX402ExactSettlementEffect, ah as resolveFxSnapshot, ai as resolvePriceFiat, aj as revenueReporterBannerState, ak as rotateLockPubkey, al as seedLockPubkeyState, am as toCreditTerms, an as toJobRecord, ao as unknownRouteNotFound, ap as verifyIncomingPayment, aq as verifyTempoSessionManagementCredential, ar as verifyUpfrontPayment, as as x402RequiredUsdcMicro, at as x402SettledShare } from '../credit-menu-BlODG4E7.js';
11
+ export { O as OutgoingMessage, S as StaleJobReapable, i as isStaleJobReapable } from '../job-store-D4-18IKm.js';
12
12
  import { z } from 'zod';
13
13
  export { f as LockPubkeyStoreLockPubkey, t as lockPubkeyStoreToLockPubkey } from '../lightning-backend-B2wMBR-x.js';
14
14
  import 'mppx';
@@ -61,7 +61,7 @@ import {
61
61
  verifyUpfrontPayment,
62
62
  x402RequiredUsdcMicro,
63
63
  x402SettledShare
64
- } from "../chunk-NLMRNLKA.js";
64
+ } from "../chunk-RPJKEWRR.js";
65
65
  import "../chunk-XRLKGCAW.js";
66
66
  import {
67
67
  PayoutReporter
@@ -71,7 +71,7 @@ import {
71
71
  verifyWithFacilitator
72
72
  } from "../chunk-C65P6MGZ.js";
73
73
  import "../chunk-DJRTYBEE.js";
74
- import "../chunk-A6EY4K46.js";
74
+ import "../chunk-QCZXKUS2.js";
75
75
  import {
76
76
  SIGNED_ENVELOPE_FIELDS,
77
77
  SIGNED_ENVELOPE_TYPES,
@@ -81,7 +81,7 @@ import {
81
81
  import {
82
82
  createInstrumentedFetch,
83
83
  isStaleJobReapable
84
- } from "../chunk-Y6JJDOGO.js";
84
+ } from "../chunk-6RJAE332.js";
85
85
  import {
86
86
  JobCancelledError,
87
87
  abortJob,
@@ -256,6 +256,8 @@ interface PaymentCreditDelta {
256
256
  }
257
257
  /** Atomic side effects applied with an outgoing provider message. */
258
258
  interface AppendOutgoingOptions {
259
+ /** Stable identity for retrying an append whose commit acknowledgement was lost. */
260
+ appendId?: string;
259
261
  /** A suspending SDK prompt/payment yield; event-driven messages leave status unchanged. */
260
262
  awaitingInput?: boolean;
261
263
  pendingPaymentDelta?: number;
@@ -567,6 +569,8 @@ interface StreamableJobStore extends JobStore {
567
569
  * machine won).
568
570
  */
569
571
  cancelJob(jobId: string, reason: string): Promise<boolean>;
572
+ /** Atomically fail a nonterminal job with its final reason and durable message history. */
573
+ failJob?(jobId: string, reason: string): Promise<boolean>;
570
574
  /**
571
575
  * Bump `last_activity_at` to `now` (Unix ms) for the given jobs that are
572
576
  * still in `processing`/`working`. Called on a
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PostgresJobStore
3
- } from "./chunk-A6EY4K46.js";
3
+ } from "./chunk-QCZXKUS2.js";
4
4
  import "./chunk-KVKJ7R5V.js";
5
5
  import "./chunk-BQJIFRAZ.js";
6
6
  import "./chunk-COYKJRMM.js";
@@ -1,15 +1,15 @@
1
1
  import { ai as CashuMode, bT as CreditLedgerQuerier, bU as CreditDrainEnqueue, bV as HostedSettlementReportObligations, w as ResolvedCreditConfig, bW as DVMAuthScheme, F as SignedRequestDomain, bX as CreditLedgerLike, bY as X402RefundSettlementGate, bZ as CreditSnapshot, b_ as CreditDrawRefundArgs, b$ as CreditDrawRefundResult, c0 as DrawResult, c1 as X402SettlementStatus, c2 as X402UnresolvedRefund, c3 as GrownDrawResult, c4 as DrawResolution, c5 as FundingRecord, c6 as CreditFundingEvidence, c7 as CreditInvoiceRecord, c8 as InvoiceSettlement, c9 as BlockedInvoiceCursor, ca as InvoiceReconciliation, cb as InvoiceWriteOff, cc as DrawRecord, cd as StalePendingDrawCursor, ce as TempoCreditLossEvidence, cf as TempoCreditLoss, cg as X402CreditLossEvidence, ch as X402CreditLoss, ci as DrainMethod, cj as DrainRequestResult, ck as BitcoinDepositLiability, bx as FundingLot, cl as CreditDrainRecord, cm as ChannelDrainCursor, cn as DrainWriteOff, co as DrainTransitionResult, cp as DrainReleaseResult, cq as DrainFulfilment, cr as HostingCleanupFence, aC as StepRecord, a4 as JobReceipt, a9 as Message, K as KVStore, M as SignedRequestReplayStore, Z as ZodLike, a as DVMDescriptor } from '../step-cache-B83Y9T9p.js';
2
2
  export { cs as CLIENT_COMPATIBILITY_HEADERS, d as CanonicalEnvelope, ct as ClientCompatibility, cu as ClientCompatibilityEnv, cv as ClientCompatibilityGate, cw as ClientCompatibilityRequirement, cx as ClientSemVer, g as CreateSignedRequestVerifierOpts, cy as CreditFundingBasis, cz as CreditInvoiceStatus, cA as CreditLedger, cB as CreditLedgerError, cC as CreditLedgerErrorCode, cD as CreditLedgerErrorDetails, cE as CreditLedgerPool, cF as CreditStatus, cG as DRAIN_DELIVERY_RESERVE_SATS, cH as DVM_PROTOCOL_VERSION, cI as DrainConflictReason, cJ as DrawRailValue, cK as DrawStatus, cL as HostedTempoCloseReportWriteOff, cM as JobCostReportPayload, cN as JobTerminalReportPayload, cO as PostgresX402ChannelStorage, cP as ReplayStoreBackend, cQ as RevenueSkippedNoRailPayload, cR as RevenueSkippedNoRailReason, z as SIGNED_REQUEST_AUTH_ID, B as SIGNED_REQUEST_STATEMENT_VERSION, cS as Secp256k1AuthOpts, E as SignedRequestAudience, G as SignedRequestError, H as SignedRequestFailure, N as SignedRequestSignOpts, O as SignedRequestStatementHeader, T as SignedRequestVerifier, cT as TempoDrainDecision, cU as TempoDrainDecisionArgs, cV as TempoDrainDecisionResult, cW as TempoDrainHoldReason, V as UpfrontPayment, cX as X402ChannelStorageOpts, cY as X402RelayLockHolder, cZ as X402RelaySubmissionLock, c_ as X402RelaySubmissionLockError, c$ as allocateDrawValue, d0 as clientCompatibilityAttributes, d1 as clientCompatibilityMiddleware, d2 as clientUpgradeRequired, W as createSignedRequestVerifier, d3 as parseClientCapabilities, d4 as parseClientCompatibility, d5 as parseDvmClient, d6 as parseProtocolVersion, d7 as requireClientCompatibility, d8 as retryHostedTempoCloseReport, d9 as secp256k1Auth, da as signedRequestInput, Y as signedRequestStatementHeader, db as writeOffHostedTempoCloseReport } from '../step-cache-B83Y9T9p.js';
3
3
  import { Pool } from 'pg';
4
- import { au as CreditMenu, a as AppEnv, U as UpfrontPaymentOpts, i as PaymentInfo, av as ReceiptIssuer, aw as LightningReceive, ax as TempoSettlementReadiness, ay as DVMHostOpts, az as DVMServeResult, L as LockPubkeyStateLoader, C as ConsumedCredentialStore } from '../credit-menu-D88XuTLU.js';
5
- export { aA as BuildCreditMenuArgs, aB as BuilderIdentity, aC as CREDIT_ENVELOPE_KEYS, aD as CreateX402BatchSettlementServerOpts, aE as CreditEnvelope, aF as CreditEnvelopeError, aG as CreditFundCommitment, aH as CreditTerms, aI as DEFAULT_INVOICE_TTL_SECONDS, aJ as DVMHost, aK as HostingClosureCommand, aL as HostingClosureEvidence, aM as HostingControlCommand, aN as HostingControlConfig, aO as HostingControlStatus, aP as JobManager, aQ as JobManagerOpts, aR as LightningReceiveConfig, aS as MIN_INVOICE_TTL_SECONDS, aT as MemoryConsumedCredentialStore, aU as MemoryConsumedCredentialStoreOpts, aV as MemoryProcessedPaymentStore, aW as MemoryX402ExactSettlementStore, aX as MountOpts, aY as OwnerDisplay, aZ as PaidJobCompletion, a_ as PaymentMode, a$ as PlatformReporterOpts, b0 as PostgresProcessedPaymentStore, b1 as PostgresX402ExactSettlementStore, b2 as PriceFiat, b3 as ProcessedPaymentQuerier, b4 as ProcessedPaymentRail, b5 as ProcessedPaymentRecord, b6 as ProcessedPaymentReplayError, b7 as ProcessedPaymentStore, b8 as X402BatchAcceptance, b9 as X402BatchFunding, ba as X402BatchRefusal, bb as X402BatchSettlementServer, bc as X402ExactAcceptance, bd as X402ExactIntentConflictError, be as X402ExactSettlementAttempt, bf as X402ExactSettlementChainEvidence, bg as X402ExactSettlementEffect, bh as X402ExactSettlementEvidenceMissingError, bi as X402ExactSettlementEvidenceReader, bj as X402ExactSettlementIntent, bk as X402ExactSettlementNotReadyError, bl as X402ExactSettlementServer, bm as X402ExactSettlementServerOpts, bn as X402ExactSettlementStatus, bo as X402ExactSettlementStore, bp as X402SettlementChainEvidence, bq as X402SettlementEvidenceReader, br as X402SettlementSubmissionError, bs as X402_BATCH_AUTO_SETTLEMENT, bt as X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS, bu as X402_BATCH_SETTLEMENT_NETWORK, bv as attachCreditMenu, bw as buildCreditMenu, bx as createDVMHost, by as createX402BatchSettlementServer, bz as creditEnvelopeIgnoreFields, bA as drawSettlementRef, bB as extractCreditEnvelope, bC as fundingCommitment, bD as selectPrimaryCredit, bE as signHostingClosure, bF as signHostingControl, bG as stripCreditEnvelope, bH as toCreditView } from '../credit-menu-D88XuTLU.js';
4
+ import { au as CreditMenu, a as AppEnv, U as UpfrontPaymentOpts, i as PaymentInfo, av as ReceiptIssuer, aw as LightningReceive, ax as TempoSettlementReadiness, ay as DVMHostOpts, az as DVMServeResult, L as LockPubkeyStateLoader, C as ConsumedCredentialStore } from '../credit-menu-BlODG4E7.js';
5
+ export { aA as BuildCreditMenuArgs, aB as BuilderIdentity, aC as CREDIT_ENVELOPE_KEYS, aD as CreateX402BatchSettlementServerOpts, aE as CreditEnvelope, aF as CreditEnvelopeError, aG as CreditFundCommitment, aH as CreditTerms, aI as DEFAULT_INVOICE_TTL_SECONDS, aJ as DVMHost, aK as HostingClosureCommand, aL as HostingClosureEvidence, aM as HostingControlCommand, aN as HostingControlConfig, aO as HostingControlStatus, aP as JobManager, aQ as JobManagerOpts, aR as LightningReceiveConfig, aS as MIN_INVOICE_TTL_SECONDS, aT as MemoryConsumedCredentialStore, aU as MemoryConsumedCredentialStoreOpts, aV as MemoryProcessedPaymentStore, aW as MemoryX402ExactSettlementStore, aX as MountOpts, aY as OwnerDisplay, aZ as PaidJobCompletion, a_ as PaymentMode, a$ as PlatformReporterOpts, b0 as PostgresProcessedPaymentStore, b1 as PostgresX402ExactSettlementStore, b2 as PriceFiat, b3 as ProcessedPaymentQuerier, b4 as ProcessedPaymentRail, b5 as ProcessedPaymentRecord, b6 as ProcessedPaymentReplayError, b7 as ProcessedPaymentStore, b8 as X402BatchAcceptance, b9 as X402BatchFunding, ba as X402BatchRefusal, bb as X402BatchSettlementServer, bc as X402ExactAcceptance, bd as X402ExactIntentConflictError, be as X402ExactSettlementAttempt, bf as X402ExactSettlementChainEvidence, bg as X402ExactSettlementEffect, bh as X402ExactSettlementEvidenceMissingError, bi as X402ExactSettlementEvidenceReader, bj as X402ExactSettlementIntent, bk as X402ExactSettlementNotReadyError, bl as X402ExactSettlementServer, bm as X402ExactSettlementServerOpts, bn as X402ExactSettlementStatus, bo as X402ExactSettlementStore, bp as X402SettlementChainEvidence, bq as X402SettlementEvidenceReader, br as X402SettlementSubmissionError, bs as X402_BATCH_AUTO_SETTLEMENT, bt as X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS, bu as X402_BATCH_SETTLEMENT_NETWORK, bv as attachCreditMenu, bw as buildCreditMenu, bx as createDVMHost, by as createX402BatchSettlementServer, bz as creditEnvelopeIgnoreFields, bA as drawSettlementRef, bB as extractCreditEnvelope, bC as fundingCommitment, bD as selectPrimaryCredit, bE as signHostingClosure, bF as signHostingControl, bG as stripCreditEnvelope, bH as toCreditView } from '../credit-menu-BlODG4E7.js';
6
6
  import { Context, Hono } from 'hono';
7
7
  import { z } from 'zod';
8
8
  import { F as FxFetcher } from '../fx-QoVA2oBc.js';
9
- import { c as StreamableJobStore, R as ReceiptIssuingStore, J as JobRecord, d as RequestIdClaim, e as RequestIdClaimResult, f as JobRetentionCursor, O as OutgoingMessage, A as AppendOutgoingOptions, P as PaymentCreditDelta, V as VerifyAndCreditResult, g as JobCounters, b as JobStore } from '../job-store-Dy-ogco6.js';
10
- export { a as JobStatus, h as isStreamableJobStore } from '../job-store-Dy-ogco6.js';
11
- import { C as ContentHoldHistory, a as ContentHoldMutation } from '../content-retention-sUCPlA1J.js';
12
- export { b as ContentHold, c as ContentHoldError, d as ContentHoldEvent, e as ContentHoldStore, f as ContentHoldsResponse, J as JobContentMetadata, g as baselineContentExpiry, h as contentMetadata, i as isContentHoldStore } from '../content-retention-sUCPlA1J.js';
9
+ import { c as StreamableJobStore, R as ReceiptIssuingStore, J as JobRecord, d as RequestIdClaim, e as RequestIdClaimResult, f as JobRetentionCursor, O as OutgoingMessage, A as AppendOutgoingOptions, P as PaymentCreditDelta, V as VerifyAndCreditResult, g as JobCounters, b as JobStore } from '../job-store-D4-18IKm.js';
10
+ export { a as JobStatus, h as isStreamableJobStore } from '../job-store-D4-18IKm.js';
11
+ import { C as ContentHoldHistory, a as ContentHoldMutation } from '../content-retention-C_TToHCi.js';
12
+ export { b as ContentHold, c as ContentHoldError, d as ContentHoldEvent, e as ContentHoldStore, f as ContentHoldsResponse, J as JobContentMetadata, g as baselineContentExpiry, h as contentMetadata, i as isContentHoldStore } from '../content-retention-C_TToHCi.js';
13
13
  export { P as PinnedFetch, S as SSRFError, a as SSRFGuardOpts, b as SSRFReason, c as SSRFResolver, d as assertSafeUrl, e as createPinnedFetch } from '../ssrf-dMooihtY.js';
14
14
  import { Env, BlankSchema } from 'hono/types';
15
15
  import 'mppx';
@@ -472,6 +472,7 @@ declare class PostgresJobStore implements StreamableJobStore, ReceiptIssuingStor
472
472
  heartbeatActiveJobs(jobIds: string[], now: number): Promise<void>;
473
473
  cancelStaleJob(jobId: string, expectedActivityBefore: number, reason: string, terminalStatus: "failed" | "cancelled"): Promise<boolean>;
474
474
  cancelJob(jobId: string, reason: string): Promise<boolean>;
475
+ failJob(jobId: string, reason: string): Promise<boolean>;
475
476
  cancelJobForHosting(jobId: string, reason: string): Promise<boolean>;
476
477
  getCounters(jobId: string): Promise<JobCounters | undefined>;
477
478
  claimForProcessing(jobId: string): Promise<boolean>;
@@ -82,7 +82,7 @@ import {
82
82
  toCreditView,
83
83
  unknownRouteNotFound,
84
84
  writeOffHostedTempoCloseReport
85
- } from "../chunk-NLMRNLKA.js";
85
+ } from "../chunk-RPJKEWRR.js";
86
86
  import {
87
87
  MemoryProcessedPaymentStore,
88
88
  PostgresProcessedPaymentStore,
@@ -95,7 +95,7 @@ import {
95
95
  } from "../chunk-DJRTYBEE.js";
96
96
  import {
97
97
  PostgresJobStore
98
- } from "../chunk-A6EY4K46.js";
98
+ } from "../chunk-QCZXKUS2.js";
99
99
  import {
100
100
  SignedRequestError,
101
101
  createDefaultReplayStore,
@@ -104,7 +104,7 @@ import {
104
104
  } from "../chunk-MXESSBXF.js";
105
105
  import {
106
106
  isStreamableJobStore
107
- } from "../chunk-Y6JJDOGO.js";
107
+ } from "../chunk-6RJAE332.js";
108
108
  import {
109
109
  ContentHoldError,
110
110
  baselineContentExpiry,
@@ -1193,7 +1193,7 @@ function createDVMHost(opts = {}) {
1193
1193
  });
1194
1194
  applyRetryToPool(pool, "dvm-host");
1195
1195
  pgPool = pool;
1196
- const { PostgresJobStore: PostgresJobStore2 } = await import("../postgres-job-store-E5GXLRB4.js");
1196
+ const { PostgresJobStore: PostgresJobStore2 } = await import("../postgres-job-store-QKN5LGAH.js");
1197
1197
  const pgJobStore = new PostgresJobStore2(pool);
1198
1198
  pendingInits.push(pgJobStore.init(), pgJobStore.initStreaming());
1199
1199
  jobStore = pgJobStore;
@@ -1,6 +1,6 @@
1
- import { C as ContentHoldHistory, a as ContentHoldMutation } from '../content-retention-sUCPlA1J.js';
1
+ import { C as ContentHoldHistory, a as ContentHoldMutation } from '../content-retention-C_TToHCi.js';
2
2
  import { a4 as JobReceipt, a9 as Message, a5 as MessageType, K as KVStore, L as Logger, x as ResponseContent, P as PaymentContent, S as SDKJobContext } from '../step-cache-B83Y9T9p.js';
3
- import { c as StreamableJobStore, R as ReceiptIssuingStore, J as JobRecord, d as RequestIdClaim, e as RequestIdClaimResult, f as JobRetentionCursor, O as OutgoingMessage, A as AppendOutgoingOptions, P as PaymentCreditDelta, V as VerifyAndCreditResult, g as JobCounters } from '../job-store-Dy-ogco6.js';
3
+ import { c as StreamableJobStore, R as ReceiptIssuingStore, J as JobRecord, d as RequestIdClaim, e as RequestIdClaimResult, f as JobRetentionCursor, O as OutgoingMessage, A as AppendOutgoingOptions, P as PaymentCreditDelta, V as VerifyAndCreditResult, g as JobCounters } from '../job-store-D4-18IKm.js';
4
4
  import 'hono';
5
5
  import 'mppx';
6
6
  import '@x402/core/server';
@@ -21,6 +21,7 @@ declare class MemoryJobStore implements StreamableJobStore, ReceiptIssuingStore
21
21
  private data;
22
22
  private messages;
23
23
  private nextSeq;
24
+ private outgoingAppends;
24
25
  private messageSubscribers;
25
26
  private notifySubscribers;
26
27
  private reactivationLocks;
@@ -62,6 +63,7 @@ declare class MemoryJobStore implements StreamableJobStore, ReceiptIssuingStore
62
63
  heartbeatActiveJobs(jobIds: string[], now: number): Promise<void>;
63
64
  cancelStaleJob(jobId: string, expectedActivityBefore: number, reason: string, terminalStatus: "failed" | "cancelled"): Promise<boolean>;
64
65
  cancelJob(jobId: string, reason: string): Promise<boolean>;
66
+ failJob(jobId: string, reason: string): Promise<boolean>;
65
67
  getCounters(jobId: string): Promise<JobCounters | undefined>;
66
68
  claimForProcessing(jobId: string): Promise<boolean>;
67
69
  tryReactivationLock<T>(jobId: string, fn: () => Promise<T>): Promise<{
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  MemoryJobStore,
6
6
  createInstrumentedFetch
7
- } from "../chunk-Y6JJDOGO.js";
7
+ } from "../chunk-6RJAE332.js";
8
8
  import {
9
9
  JobCancelledError
10
10
  } from "../chunk-KVKJ7R5V.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvmkit/sdk",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "SDK for building Digital Vending Machines: pay-per-use HTTPS services AI agents can find, pay, and call with no account or API key",
5
5
  "author": "DVM Technologies Ltd",
6
6
  "keywords": [
@@ -92,7 +92,7 @@
92
92
  "hono": "4.13.14",
93
93
  "jose": "6.2.1",
94
94
  "mppx": "0.8.15",
95
- "nostr-tools": "2.23.3",
95
+ "nostr-tools": "2.25.4",
96
96
  "proper-lockfile": "4.1.2",
97
97
  "undici": "7.30.1",
98
98
  "ws": "8.21.0",