@dvmkit/sdk 0.1.2-rc.7 → 0.1.4-rc.7

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 (45) hide show
  1. package/README.md +121 -8
  2. package/dist/chunk-BIFLRKMO.js +87 -0
  3. package/dist/chunk-BQ2NMWKE.js +160 -0
  4. package/dist/{chunk-LDTWX7JW.js → chunk-BTZY7VPH.js} +13 -1
  5. package/dist/{chunk-FJDCFHW5.js → chunk-C6JHBLMW.js} +3 -81
  6. package/dist/{chunk-EXHBXA4U.js → chunk-DBCLBYHP.js} +13 -1
  7. package/dist/chunk-E4EVGPDX.js +391 -0
  8. package/dist/chunk-EDDYHZ6W.js +1010 -0
  9. package/dist/{chunk-2ABMGUDS.js → chunk-EVBK675R.js} +88 -10
  10. package/dist/{chunk-P4RUVDU7.js → chunk-H2MEFVH6.js} +12 -141
  11. package/dist/chunk-KXZUCCEY.js +142 -0
  12. package/dist/{chunk-LWUR4CGG.js → chunk-MLRCSJYX.js} +11 -3
  13. package/dist/{chunk-N4VTG3KH.js → chunk-QK3VJNCK.js} +930 -3011
  14. package/dist/chunk-RW5LP57K.js +44 -0
  15. package/dist/{chunk-6JZIX5WW.js → chunk-SSSZUVWM.js} +178 -8
  16. package/dist/{chunk-TKA6ZP4M.js → chunk-U6M3ATSG.js} +56 -426
  17. package/dist/chunk-VRQDX5P4.js +1742 -0
  18. package/dist/{chunk-JGGI65I3.js → chunk-Z4BNLUZF.js} +1 -150
  19. package/dist/{credit-ledger-ED6JXKVD.js → credit-ledger-2DFQHNLB.js} +2 -2
  20. package/dist/{credit-menu-BM4qCD5U.d.ts → credit-menu-C1ezIFlJ.d.ts} +1699 -1941
  21. package/dist/{fx-C-liI3oY.d.ts → fx-C6dl2LVI.d.ts} +1 -1
  22. package/dist/index.d.ts +7 -6
  23. package/dist/index.js +8 -4
  24. package/dist/internal/caller.d.ts +10441 -0
  25. package/dist/internal/caller.js +10078 -0
  26. package/dist/internal/index.d.ts +2 -10816
  27. package/dist/internal/index.js +2 -10130
  28. package/dist/internal/server.d.ts +404 -0
  29. package/dist/internal/server.js +202 -0
  30. package/dist/job-store-DHnW4Cg_.d.ts +591 -0
  31. package/dist/lightning-backend-Ci1nogk_.d.ts +367 -0
  32. package/dist/{memory-credit-ledger-XJ5VQEVP.js → memory-credit-ledger-OP24Z2KO.js} +3 -3
  33. package/dist/{postgres-job-store-J5F4GUWU.js → postgres-job-store-3RAXMNSY.js} +1 -1
  34. package/dist/{revenue-reporter-JIKUPXOK.js → revenue-reporter-ASZ7SHHH.js} +1 -1
  35. package/dist/server/index.d.ts +41 -11
  36. package/dist/server/index.js +93 -69
  37. package/dist/{job-store-C53VQ5uu.d.ts → step-cache-BLPZNizw.d.ts} +176 -585
  38. package/dist/{tempo-session-store-DALMRIWN.js → tempo-session-store-2JNOKJGX.js} +2 -2
  39. package/dist/testing/index.d.ts +25 -3
  40. package/dist/testing/index.js +36 -3
  41. package/dist/{usd-gLcJB1ps.d.ts → usd-BgOfZlk6.d.ts} +1 -1
  42. package/dist/wallet-CJC8lwxx.d.ts +29 -0
  43. package/dist/{x402-FTG2GRAQ.js → x402-T2C5MX3T.js} +6 -3
  44. package/package.json +11 -5
  45. package/dist/{chunk-RU7SXHLO.js → chunk-UP2F5RRT.js} +3 -3
@@ -0,0 +1,591 @@
1
+ import { ProofLike } from '@cashu/cashu-ts';
2
+ import { a1 as Message, ah as FundingMethod, X as FundingReceipt, bO as CreditSnapshot, Y as JobReceipt, ax as StepRecord, a2 as MessageType } from './step-cache-BLPZNizw.js';
3
+
4
+ /** Job status values that can be persisted. */
5
+ type JobStatus = "processing" | "completed" | "failed" | "awaiting-input" | "cancelled" | "working";
6
+ /** Serializable snapshot of a job's persistent state. */
7
+ interface JobRecord {
8
+ id: string;
9
+ tags: string[];
10
+ /**
11
+ * Capability name this job dispatches to (internal-review). Populated from the
12
+ * `POST /v1/job` body's `capability` field at submission time; persisted
13
+ * so reactivation on another machine routes to the same handler.
14
+ */
15
+ capability: string;
16
+ input: string;
17
+ params: Record<string, string>;
18
+ requesterId: string;
19
+ /**
20
+ * SHA-256 hex of the per-job opaque token issued at creation time for
21
+ * anonymous requesters (internal-review). When set, all `/v1/job/:id/*` reads/writes
22
+ * gate on the caller presenting the matching `X-Job-Token` header — this is
23
+ * the mechanism that prevents cross-caller transcript leaks for free DVMs
24
+ * where every caller otherwise resolves to `requesterId === "anonymous"`.
25
+ * Undefined for authenticated jobs (bearer / signed-request), which gate on
26
+ * `requesterId` instead.
27
+ */
28
+ requesterTokenHash?: string;
29
+ /**
30
+ * The `job_token` itself, in the clear (internal-review). Kept so a caller whose
31
+ * paid submit timed out can replay it and be handed back the very token the
32
+ * lost response carried — the alternative (minting a fresh one) would be
33
+ * rejected by whichever machine is still running the job, since its
34
+ * `activeJobs` copy holds the original hash.
35
+ *
36
+ * The cost is that DB read access now also confers job *control* (cancel,
37
+ * feed input), not just read access. Accepted: the same database holds the
38
+ * accumulator's Cashu proofs, which are bearer money — a reader who gets
39
+ * that far has strictly better targets. `requesterTokenHash` remains the
40
+ * only thing the ownership check reads.
41
+ */
42
+ requesterToken?: string;
43
+ /**
44
+ * Fingerprint of the submission that created this job (internal-review) — see
45
+ * `requestFingerprint`. The replay gate: a retried paid submit is handed the
46
+ * original job only when it reproduces this.
47
+ */
48
+ requestFingerprint?: string;
49
+ /**
50
+ * secp256k1 x-only pubkey of the caller that signed the submission, on
51
+ * DVMs with descriptor-level auth (internal-review). Second half of the replay
52
+ * gate — the signature envelope is re-minted on every resume attempt, so
53
+ * the identity behind it is pinned here instead.
54
+ */
55
+ requesterPubkey?: string;
56
+ /** Caller-generated public recovery id bound to this request fingerprint. */
57
+ requestId?: string;
58
+ /** HTTP path whose live auth gate accepted the persisted caller proof. */
59
+ authRequestPath?: string;
60
+ status: JobStatus;
61
+ summary?: string;
62
+ messages: Message[];
63
+ seq: number;
64
+ paidMsats: number;
65
+ paymentMint?: string;
66
+ /** Rail of the most recent successful credit (cashu / x402 / mpp). */
67
+ paymentRail?: FundingMethod;
68
+ /**
69
+ * Settlement reference threaded into the platform revenue ledger
70
+ * (`revenue_events.tx_hash`). EVM tx hash for x402, mppx `challenge.id`
71
+ * for mpp (internal-review); cashu accumulator path sets this to the
72
+ * `X-Cashu-Request-Id` UUID (internal-review). Persisted so it survives durable
73
+ * replay.
74
+ */
75
+ paymentTxHash?: string;
76
+ /** Chain transaction returned to callers recovering x402 or Tempo acceptance. */
77
+ paymentTransactionHash?: string;
78
+ /** Rail-native upfront amount (sats / USDC microunits). internal-review. */
79
+ nativeAmount?: number;
80
+ /** Native asset tag — paired with `nativeAmount`. internal-review. */
81
+ nativeAsset?: "sats" | "usdc" | "usdc.e" | "usd-cents";
82
+ /** Cashu flow discriminator written into `revenue_events.metadata.cashu_flow` (internal-review). */
83
+ cashuFlow?: "p2pk_accumulator";
84
+ /**
85
+ * What this job cost the **builder** to serve, in 1e-6 of
86
+ * {@link JobRecord.costCurrency} (internal-review). Mirror of
87
+ * `ServerJob.costAmountMicro`; see that field. Absent means unreported,
88
+ * which the platform treats differently from a declared zero.
89
+ */
90
+ costAmountMicro?: number;
91
+ /** Currency {@link JobRecord.costAmountMicro} is 1e-6 of (lowercase ISO-4217). */
92
+ costCurrency?: string;
93
+ /**
94
+ * Number of `ctx.cost()` declarations incorporated into the current cost
95
+ * state. Lets the platform deterministically replace a stale persisted
96
+ * prefix with the handler owner's later cumulative value.
97
+ */
98
+ costRevision?: number;
99
+ /**
100
+ * Credit the upfront payment funded/drew (internal-review). The terminal funnel
101
+ * settles the draw on success and releases it on failure/cancel, and the
102
+ * receipt countersigns the `ReceiptCredit` block from it.
103
+ */
104
+ creditId?: string;
105
+ /** The draw placed for this job on `creditId` (internal-review). */
106
+ drawId?: string;
107
+ /** Funding-time evidence returned on explicit fund-and-draw submissions. */
108
+ fundingReceipt?: FundingReceipt;
109
+ fundingCredit?: CreditSnapshot;
110
+ receivedProofs: ProofLike[];
111
+ pendingPaymentMsats?: number;
112
+ /**
113
+ * Per-job MPP credential binding (internal-review). Challenge ids issued in the
114
+ * most-recent `requestPayment` yield; the SDK rejects mid-job credentials
115
+ * whose `challenge.id` isn't in this set.
116
+ *
117
+ * Keeps its `mpp` spelling past internal-review's rail rename (internal-review). The ids
118
+ * are mppx challenge ids — objects of the MPP envelope the Tempo rail rides,
119
+ * which is not itself a rail — and this is server-internal anti-replay state
120
+ * that is never serialised to a caller. It persists as
121
+ * `jobs.pending_mpp_challenge_ids` / `isolate_jobs.pending_mpp_challenge_ids`.
122
+ */
123
+ pendingMppChallengeIds?: string[];
124
+ /**
125
+ * Per-job x402 credential binding (internal-review). 0x-prefixed 32-byte hex nonce
126
+ * issued in the most-recent `requestPayment` yield; the SDK rejects mid-job
127
+ * x402 payments whose decoded `authorization.nonce` differs from this value.
128
+ */
129
+ pendingX402Nonce?: string;
130
+ /**
131
+ * Exact USDC microunit amount advertised with `pendingX402Nonce`. Verification
132
+ * reuses this value so an FX refresh between request and payment cannot change
133
+ * the facilitator requirements.
134
+ */
135
+ pendingX402AmountUsdcMicro?: string;
136
+ /**
137
+ * The outstanding `requestPayment` ask denominated in fiat micro-units, pinned
138
+ * when the payment-request was emitted (internal-review) — the mid-job analogue of
139
+ * `resolvePriceFiat` pinning the quote. The ledger is fiat-denominated, so the
140
+ * top-up's `fund` + `growDraw` need a figure that doesn't move with the BTC/USD
141
+ * rate between the ask and the payment. Absent when the ask couldn't be priced
142
+ * in fiat (cold-start fx outage), which degrades that top-up to ledger-less.
143
+ */
144
+ pendingPaymentFiatMicro?: number;
145
+ /** Currency of {@link pendingPaymentFiatMicro} (lowercase ISO-4217). */
146
+ pendingPaymentFiatCurrency?: string;
147
+ /**
148
+ * What this job has **cumulatively** asked for mid-job, in fiat micro-units
149
+ * of {@link askedTopUpCurrency} (internal-review) — the ceiling `growDraw` caps the
150
+ * job's draw growth at. Monotonic: written by the same Tx A that emits each
151
+ * payment-request, and never decremented, which is exactly what
152
+ * `pendingPaymentFiatMicro` is not (Tx C drains it as payments land). A cap
153
+ * derived from the outstanding figure would let a duplicate payment arriving
154
+ * in the window before Tx C see a full ask that the winner had already
155
+ * bought.
156
+ *
157
+ * `-1` is a sticky "not expressible" sentinel — after internal-review the one door
158
+ * left to it is an ask the rate provider was down for. Undefined means no
159
+ * mid-job ask has been recorded (including a row written before this
160
+ * existed). Either way the cap is skipped and growth is unbounded, as it was
161
+ * before: a guessed ceiling on a money path is worse than none.
162
+ */
163
+ askedTopUpMicro?: number;
164
+ /**
165
+ * Currency of {@link askedTopUpMicro} (lowercase ISO-4217) — and, from
166
+ * internal-review, the job's own ask denomination: first-write-wins, and read back
167
+ * by `ctx.requestPayment` so every later ask pins in it too. That is what
168
+ * makes a mid-job currency switch unexpressible rather than merely unlikely.
169
+ */
170
+ askedTopUpCurrency?: string;
171
+ /**
172
+ * Why this job's draw growth is **not** capped, when it isn't (internal-review) —
173
+ * the durable half of the `-1` sentinel, so an operator can list the affected
174
+ * jobs after the fact rather than reconstruct them from log lines. Sticky
175
+ * first-writer-wins, like the sentinel itself. Null on every job whose ceiling
176
+ * is enforceable, which is the overwhelming majority.
177
+ */
178
+ topUpCapUnenforcedReason?: TopUpCapUnenforcedReason;
179
+ /** Price the job was charged at (msats). Used to detect overpayment for change/melt gating. */
180
+ requiredMsats?: number;
181
+ /**
182
+ * The DVM-signed proof of this job's outcome (internal-review). Written once at the
183
+ * terminal transition via `saveReceipt` and never rewritten — `save` leaves
184
+ * the column alone so a stale in-memory snapshot can't clear it. Absent on
185
+ * non-terminal jobs, and on every job when the DVM has no receipt key wired.
186
+ */
187
+ receipt?: JobReceipt;
188
+ stepCache: StepRecord[];
189
+ state: unknown;
190
+ createdAt: number;
191
+ lastActivityAt: number;
192
+ /** When caller-controlled content was removed by the retention sweep. */
193
+ redactedAt?: number;
194
+ }
195
+ /**
196
+ * Counter snapshot used by the NOTIFY-driven resolver and the
197
+ * `verifyAndCredit` flow (internal-review). Carries just the handler-visible state
198
+ * needed to decide whether a pending payment yield can resolve, plus (internal-review)
199
+ * the terminal reason so a machine adopting another machine's cancel can hand
200
+ * the caller's own words to `ctx.signal.reason` and the `onCancel` hook.
201
+ */
202
+ interface JobCounters {
203
+ status: JobStatus;
204
+ paidMsats: number;
205
+ pendingPaymentMsats?: number;
206
+ /**
207
+ * `jobs.summary`. Set to the cancel/fail reason by `cancelJob` /
208
+ * `cancelStaleJob`, and to the completion summary by a handler's
209
+ * `ctx.complete(summary)`. Undefined when nothing recorded one.
210
+ */
211
+ summary?: string;
212
+ }
213
+ /**
214
+ * Credit delta written inside the Tx C transaction (internal-review) when an
215
+ * inbound payment verifies. Fields mirror what the rails produce in
216
+ * `payment.ts`; `paidMsatsDelta` is the only required key.
217
+ */
218
+ interface PaymentCreditDelta {
219
+ paidMsatsDelta: number;
220
+ /** When true, set `pending_payment_msats = NULL`; otherwise decrement by the delta. */
221
+ clearPending?: boolean;
222
+ paymentMint?: string;
223
+ paymentRail?: FundingMethod;
224
+ paymentTxHash?: string;
225
+ nativeAmount?: number;
226
+ nativeAsset?: "sats" | "usdc" | "usdc.e" | "usd-cents";
227
+ cashuFlow?: "p2pk_accumulator";
228
+ /** When true, accumulate `nativeAmount` onto the existing same-rail value; otherwise overwrite. */
229
+ accumulateNative?: boolean;
230
+ /** Clear the pending nonce + advertised amount after an x402 credit succeeds. */
231
+ clearPendingX402Binding?: boolean;
232
+ /**
233
+ * Credit this payment funded (internal-review). Only ever set when the mid-job
234
+ * top-up minted a *fresh* implicit credit — a free-then-paid job whose
235
+ * upfront leg placed no draw. Written set-if-null: a job already bound to a
236
+ * credit keeps that binding, because the top-up grew its existing draw
237
+ * rather than opening a second one.
238
+ */
239
+ creditId?: string;
240
+ /** The draw opened alongside {@link creditId}. Same set-if-null rule. */
241
+ drawId?: string;
242
+ }
243
+ /** Atomic side effects applied with an outgoing provider message. */
244
+ interface AppendOutgoingOptions {
245
+ pendingPaymentDelta?: number;
246
+ pendingMppChallengeIds?: string[];
247
+ pendingX402Nonce?: string;
248
+ pendingX402AmountUsdcMicro?: string;
249
+ /**
250
+ * Fiat envelope pinned for this ask (internal-review). Accumulates alongside
251
+ * `pendingPaymentDelta` when the currency matches what's already recorded,
252
+ * and replaces it when it doesn't — the same shape `pending_payment_msats`
253
+ * uses, so stacked asks stay summable.
254
+ */
255
+ pendingPaymentFiatMicro?: number;
256
+ pendingPaymentFiatCurrency?: string;
257
+ }
258
+ /**
259
+ * Why a job's cumulative ask total can't bound its draw growth (internal-review).
260
+ *
261
+ * - `ask_unpriceable` — an ask the rate provider was down for, or one whose
262
+ * fiat figure isn't a safe integer. The only door left after internal-review, and
263
+ * deliberately still open: failing the charge to protect a ledger comparison
264
+ * would break a payment that doesn't need the rate to be quoted or collected.
265
+ * - `ask_currency_switch` — two asks on one job in different denominations.
266
+ * Unreachable through `ctx.requestPayment`, which pins every ask in the job's
267
+ * own sticky denomination; kept as the backstop for a record written by an
268
+ * older build, and because the accumulator must not silently sum two units.
269
+ */
270
+ type TopUpCapUnenforcedReason = "ask_unpriceable" | "ask_currency_switch";
271
+ /**
272
+ * Result of a Tx C `verifyAndCredit` call. `alreadyVerified` is set when the
273
+ * inbound row was previously marked verified (idempotency guard); the credit
274
+ * is skipped in that case.
275
+ */
276
+ interface VerifyAndCreditResult {
277
+ counters: JobCounters;
278
+ alreadyVerified: boolean;
279
+ /**
280
+ * The credit the job row holds **after** this call — the authoritative
281
+ * answer, read back rather than assumed (internal-review).
282
+ *
283
+ * {@link PaymentCreditDelta.creditId} is written set-if-null, so a caller
284
+ * that passes one cannot know whether it won the bind: on a free-then-paid
285
+ * job two concurrent top-ups each mint their own implicit credit and only
286
+ * one lands. Reading it back is what lets `processPayment` mirror the row
287
+ * truthfully and name the loser's deposit to the caller instead of silently
288
+ * recording a binding the row rejected.
289
+ */
290
+ creditId?: string;
291
+ /** The draw the job row holds after this call. Same read-back rule. */
292
+ drawId?: string;
293
+ }
294
+ /** Secret-free identity for atomically reserving one public request id. */
295
+ interface RequestIdClaim {
296
+ /** Caller-generated id carried inside the signed request body. */
297
+ requestId: string;
298
+ /** Verified x-only secp256k1 caller public key. */
299
+ requesterPubkey: string;
300
+ /** Server-resolved requester identity used by normal job ownership checks. */
301
+ requesterId: string;
302
+ /** Fingerprint of the request body with only the expiring auth fields removed. */
303
+ requestFingerprint: string;
304
+ /** Job id allocated before payment and reused by every exact recovery attempt. */
305
+ jobId: string;
306
+ /** Server-generated owner token for the live serialization lock. */
307
+ claimToken: string;
308
+ }
309
+ /** Result of acquiring the live lock for a durable public request-id binding. */
310
+ interface RequestIdClaimResult {
311
+ /** Stable job id first assigned to this caller/request id. */
312
+ jobId: string;
313
+ /** True when the durable identity binding existed before this lock acquisition. */
314
+ replayed: boolean;
315
+ }
316
+ /** Persistent backing store for job records. */
317
+ interface JobStore {
318
+ /** Retrieve a job record by ID. Returns undefined if not found. */
319
+ get(id: string): Promise<JobRecord | undefined>;
320
+ /**
321
+ * Retrieve the job a settlement reference paid for (internal-review). On the cashu
322
+ * accumulator path `paymentTxHash` is the caller's `X-Cashu-Request-Id`, so
323
+ * this is what makes a retried paid submit idempotent: the replay finds the
324
+ * job its (already-spent) payment created instead of a bare 409.
325
+ */
326
+ findJobByPaymentTxHash(txHash: string): Promise<JobRecord | undefined>;
327
+ /** Read a signed caller's job by public recovery id, when the store supports it. */
328
+ findJobByRequestId?(requestId: string, requesterPubkey: string): Promise<JobRecord | undefined>;
329
+ /**
330
+ * Atomically bind and lock a caller/request id before pricing, payment
331
+ * verification, credit draw, or handler work. Returns undefined when
332
+ * another live request holds it or its durable binding has different
333
+ * provenance. A process death releases the live lock but not the binding.
334
+ */
335
+ claimRequestId?(claim: RequestIdClaim): Promise<RequestIdClaimResult | undefined>;
336
+ /**
337
+ * Lock an existing exact caller/request binding without creating one.
338
+ * Used only by the explicit recovery route, which must prove the original
339
+ * submit reached the server before it can inspect durable acceptance state.
340
+ */
341
+ resumeRequestId?(claim: RequestIdClaim): Promise<RequestIdClaimResult | undefined>;
342
+ /**
343
+ * Release the live serialization lock after this route finishes. The opaque
344
+ * owner token prevents a delayed request from releasing a newer holder; the
345
+ * durable caller/fingerprint/job binding remains retained.
346
+ */
347
+ releaseRequestIdClaim?(claim: RequestIdClaim): Promise<void>;
348
+ /** Upsert a job record. */
349
+ save(record: JobRecord): Promise<void>;
350
+ /** Delete a job record. */
351
+ delete(id: string): Promise<void>;
352
+ }
353
+ /** Total-order cursor for terminal job retention scans. */
354
+ interface JobRetentionCursor {
355
+ lastActivityAt: number;
356
+ id: string;
357
+ }
358
+ /**
359
+ * JobStore extension for signed job receipts (internal-review). Implemented by both
360
+ * container-tier stores (`MemoryJobStore`, `PostgresJobStore`); the isolate
361
+ * tier's `IsolateJobStore` deliberately doesn't, which is what keeps the
362
+ * out-of-scope isolate gap a compile-time fact rather than a runtime surprise.
363
+ */
364
+ interface ReceiptIssuingStore {
365
+ /**
366
+ * Allocate this job's receipt sequence number, or return the one it already
367
+ * holds. Idempotent per job and monotonic per DVM: two machines racing to
368
+ * issue the same job's receipt get the same number, and the loser burns no
369
+ * sequence.
370
+ *
371
+ * Gaps in the issued sequence are the completeness signal that exposes
372
+ * receipt suppression, so a read-then-write counter (which can hand two jobs
373
+ * the same number under concurrency) is not acceptable — the allocation has
374
+ * to be atomic against every other machine.
375
+ */
376
+ claimReceiptSeq(jobId: string): Promise<number>;
377
+ /**
378
+ * Persist a signed receipt iff the job doesn't already have one, returning
379
+ * whichever receipt is stored afterwards. Write-once: a racing second issuer
380
+ * gets the winner's bytes back, so every reader of the job — sync response,
381
+ * poll, later re-read — sees byte-identical JSON.
382
+ *
383
+ * Returns `undefined` when the job row is gone.
384
+ */
385
+ saveReceipt(jobId: string, receipt: JobReceipt): Promise<JobReceipt | undefined>;
386
+ }
387
+ /**
388
+ * JobStore extension for cross-machine message streaming + the internal-review
389
+ * transactional model (Tx A / Tx B / Tx C).
390
+ *
391
+ * Tx A — outgoing message + immediate counter consequence (`appendOutgoing`).
392
+ * Single tx: server-allocate seq, append `job_messages` row with
393
+ * `status='verified'`, bump `pending_payment_msats` for payment-request
394
+ * messages, and `pg_notify`.
395
+ *
396
+ * Tx B — incoming message marked `pending-verification` (`recordInbound`).
397
+ * Single tx: server-allocate seq, append `job_messages` row with
398
+ * `status='pending-verification'`, and `pg_notify`. External verification
399
+ * (Cashu mint, x402 facilitator, MPP server) runs OUTSIDE the tx.
400
+ *
401
+ * Tx C — verification outcome + credit (`verifyAndCredit`). Single tx:
402
+ * flip the `job_messages.status` to `'verified'`, bump `paid_msats` /
403
+ * clear `pending_payment_msats` per the credit delta, and `pg_notify`.
404
+ * Idempotent — a repeat call returns `alreadyVerified: true` without
405
+ * re-crediting.
406
+ */
407
+ interface StreamableJobStore extends JobStore {
408
+ /**
409
+ * Tx A — append an outgoing message and apply its immediate counter
410
+ * consequence. Returns the server-allocated seq. The caller's `message`
411
+ * carries no `seq` (it's allocated inside the tx). For payment-request
412
+ * messages, pass the amount via `pendingPaymentDelta` so the bump and
413
+ * the message land in the same tx.
414
+ */
415
+ appendOutgoing(jobId: string, message: OutgoingMessage, opts?: AppendOutgoingOptions): Promise<number>;
416
+ /**
417
+ * Tx B — record an inbound message durably with `pending-verification`
418
+ * status. Returns the server-allocated seq.
419
+ */
420
+ recordInbound(jobId: string, message: OutgoingMessage): Promise<number>;
421
+ /**
422
+ * Tx C — flip an inbound message from `pending-verification` to either
423
+ * `verified` (with a credit delta when it's a payment) or `failed-verify`
424
+ * (when verification rejected). Idempotent: a repeat call with a row
425
+ * already in `verified`/`failed-verify` returns `alreadyVerified: true`
426
+ * and skips the credit.
427
+ *
428
+ * Pass `credit: null` for non-payment messages (response/cancel/approval).
429
+ */
430
+ verifyAndCredit(jobId: string, seq: number, credit: PaymentCreditDelta | null): Promise<VerifyAndCreditResult>;
431
+ /**
432
+ * Read back what Tx C committed for an inbound message, without opening a
433
+ * transaction of its own (internal-review).
434
+ *
435
+ * A `verifyAndCredit` that throws is ambiguous: the error may have been
436
+ * raised at or after its COMMIT, in which case the write is durable and the
437
+ * caller has simply lost the acknowledgement. Retrying resolves that — until
438
+ * the retries run out, and the last attempt's error is as ambiguous as any
439
+ * other's. This is the read that settles it: `undefined` means Tx C has not
440
+ * committed for this seq, anything else is the outcome it committed.
441
+ *
442
+ * Implementations MUST answer without a transaction (no `BEGIN`/`COMMIT`) and
443
+ * MUST NOT write, because the caller reaches for it precisely when writes are
444
+ * failing. `alreadyVerified` is always `true` on the returned value — the
445
+ * credit landed in an earlier call, not this one.
446
+ */
447
+ getVerifiedInbound(jobId: string, seq: number): Promise<VerifyAndCreditResult | undefined>;
448
+ /**
449
+ * Mark an inbound message as failed-verify without crediting. Used when
450
+ * external verification rejects the payment (Cashu mint says SPENT, x402
451
+ * facilitator returns invalid, mppx HMAC mismatch).
452
+ */
453
+ markInboundFailed(jobId: string, seq: number, reason: string): Promise<void>;
454
+ /** Read the counters used by the resolver (status / paid / pending). */
455
+ getCounters(jobId: string): Promise<JobCounters | undefined>;
456
+ /**
457
+ * Single-execution claim (internal-review). Atomically flip a job from
458
+ * `awaiting-input` to `processing`, returning `true` only for the caller
459
+ * that won the transition. After a mid-job payment lands cross-machine, the
460
+ * live original handler (woken via NOTIFY) and a reactivation replay on the
461
+ * paying machine both race to drive the job; the CAS winner proceeds and the
462
+ * loser stands down, so a healthy worker never double-executes (double
463
+ * substrate spend, clobbered artifact). Also bumps `last_activity_at` so the
464
+ * claimant is covered by the processing watchdog. A no-op returning `false`
465
+ * for a job that is no longer `awaiting-input` (already claimed / terminal).
466
+ */
467
+ claimForProcessing(jobId: string): Promise<boolean>;
468
+ /**
469
+ * Run `fn` while holding a tx-scoped advisory lock keyed on the job id.
470
+ * Resolves with `{ acquired: false }` when the lock is already held by
471
+ * another transaction — the caller decides what to do (typically: skip
472
+ * reactivation, the other holder will handle it). The lock releases on
473
+ * commit/rollback of the wrapping tx.
474
+ */
475
+ tryReactivationLock<T>(jobId: string, fn: () => Promise<T>): Promise<{
476
+ acquired: true;
477
+ result: T;
478
+ } | {
479
+ acquired: false;
480
+ }>;
481
+ /**
482
+ * Subscribe to messages for a job. Delivers existing messages (seq > afterSeq)
483
+ * from the `job_messages` table first, then forwards new ones via NOTIFY.
484
+ * Returns an unsubscribe function.
485
+ *
486
+ * Only messages with `status='verified'` are delivered — pending and
487
+ * failed-verify rows are internal to the verification flow.
488
+ */
489
+ subscribeMessages(jobId: string, afterSeq: number, onMessage: (msg: Message) => void): Promise<() => void>;
490
+ /**
491
+ * Subscribe to raw NOTIFY events for a job. Fires once per `pg_notify`
492
+ * delivery; no message decoding. Used by `Context.requestPayment` to
493
+ * re-read counters when cross-machine credit lands (internal-review).
494
+ */
495
+ subscribeNotifications(jobId: string, onNotify: () => void): Promise<() => void>;
496
+ /** Read messages from the `job_messages` table for a job. */
497
+ getMessages(jobId: string, afterSeq: number): Promise<Message[]>;
498
+ /**
499
+ * Find stale non-terminal jobs (internal-review / internal-review). Two status-aware
500
+ * cutoffs (both Unix ms): `processing`/`working` rows older than
501
+ * `processingThresholdMs` (worker-liveness watchdog — the SDK heartbeats
502
+ * live workers, so a frozen `last_activity_at` here means the worker died),
503
+ * and `awaiting-input` rows older than `awaitingThresholdMs` (the
504
+ * caller-input idle timeout). Pass a negative cutoff to disable that arm.
505
+ * Returns at most `limit` records, oldest first. Consumed exclusively by the
506
+ * `JobManager` stale-job sweeper; not safe to feed into handler dispatch.
507
+ *
508
+ * `capabilities` scopes the query to jobs the calling JobManager owns —
509
+ * multi-mount hosts where two descriptors share a DB must not steal
510
+ * each other's stuck rows (the activeJobs teardown only fires on the
511
+ * descriptor that actually hosted the zombie). Pass `null` only from
512
+ * single-descriptor deployments.
513
+ */
514
+ findStaleJobs(processingThresholdMs: number, awaitingThresholdMs: number, limit: number, capabilities: string[] | null): Promise<JobRecord[]>;
515
+ /**
516
+ * Atomically transition a stale job to `terminalStatus` (`failed` for a
517
+ * dead worker, `cancelled` for an idle caller-wait) and append a final
518
+ * `cancel` message in the same transaction (internal-review / internal-review). The
519
+ * `expectedActivityBefore` clause is an optimistic CAS — the flip only
520
+ * lands when the job is still non-terminal AND `last_activity_at <=
521
+ * expectedActivityBefore`. Returns true when this caller claimed the
522
+ * transition, false when another machine got there first OR the job's
523
+ * activity bumped (recovered). Mirrors `save`'s terminal cleanup by
524
+ * deleting incremental `job_messages` rows once the snapshot is final.
525
+ */
526
+ cancelStaleJob(jobId: string, expectedActivityBefore: number, reason: string, terminalStatus: "failed" | "cancelled"): Promise<boolean>;
527
+ /**
528
+ * Atomically cancel a job from a machine that isn't running its handler
529
+ * (internal-review). Same transaction shape as {@link cancelStaleJob} minus the
530
+ * activity cutoff — the CAS guards only on the job still being non-terminal,
531
+ * so this replaces the read-then-`save` the DELETE route used to do (a TOCTOU
532
+ * against a concurrent terminal write).
533
+ *
534
+ * The `cancel` message and the terminal status commit together and `pg_notify`
535
+ * fires inside the tx, so the machine actually running the handler wakes on
536
+ * its per-job notification subscription, adopts the terminal status, and
537
+ * aborts `ctx.signal` within a NOTIFY round-trip instead of at the handler's
538
+ * next store write. Returns false when the job was already terminal (another
539
+ * machine won).
540
+ */
541
+ cancelJob(jobId: string, reason: string): Promise<boolean>;
542
+ /**
543
+ * Bump `last_activity_at` to `now` (Unix ms) for the given jobs that are
544
+ * still in `processing`/`working` (internal-review worker heartbeat). Called on a
545
+ * tick by the `JobManager` for its locally-active jobs so a live worker's
546
+ * row stays fresh and the processing watchdog only fires when the process
547
+ * has actually died. A no-op for rows that have since gone terminal or
548
+ * `awaiting-input` (the SQL `WHERE` guards the status).
549
+ */
550
+ heartbeatActiveJobs(jobIds: string[], now: number): Promise<void>;
551
+ /** Acquire the dedicated LISTEN connection. Call once at startup. */
552
+ initStreaming(): Promise<void>;
553
+ /** Release the dedicated LISTEN connection. Call on shutdown. */
554
+ shutdownStreaming(): Promise<void>;
555
+ }
556
+ /**
557
+ * Message shape passed to `appendOutgoing` / `recordInbound`. The store
558
+ * allocates `seq` server-side inside the tx, so the caller doesn't supply it.
559
+ */
560
+ interface OutgoingMessage {
561
+ from: "provider" | "requester";
562
+ type: MessageType;
563
+ timestamp: number;
564
+ content: Record<string, unknown> | object;
565
+ }
566
+ /**
567
+ * Runtime type guard for StreamableJobStore. Duck-typed, so it has to name every
568
+ * member the callers reach for — a store written against an earlier version of
569
+ * the interface would otherwise pass the guard and TypeError inside the route
570
+ * that calls the member it's missing.
571
+ */
572
+ declare function isStreamableJobStore(store: JobStore): store is StreamableJobStore;
573
+ /**
574
+ * The stale-job reaper surface (internal-review) — the two methods a sweeper needs to
575
+ * find and atomically reap worker-stranded jobs. A strict subset of
576
+ * `StreamableJobStore`: `MemoryJobStore` and `PostgresJobStore` satisfy it via
577
+ * the full streamable interface, and the platform's `IsolateJobStore` (a plain
578
+ * single-machine `JobStore`, not streamable) implements just these two so the
579
+ * `IsolateJobManager` reaper can sweep the `isolate_jobs` table without taking
580
+ * on the streaming/LISTEN machinery it doesn't need.
581
+ */
582
+ interface StaleJobReapable {
583
+ /** See {@link StreamableJobStore.findStaleJobs}. */
584
+ findStaleJobs(processingThresholdMs: number, awaitingThresholdMs: number, limit: number, capabilities: string[] | null): Promise<JobRecord[]>;
585
+ /** See {@link StreamableJobStore.cancelStaleJob}. */
586
+ cancelStaleJob(jobId: string, expectedActivityBefore: number, reason: string, terminalStatus: "failed" | "cancelled"): Promise<boolean>;
587
+ }
588
+ /** Runtime type guard for StaleJobReapable. */
589
+ declare function isStaleJobReapable(store: JobStore): store is JobStore & StaleJobReapable;
590
+
591
+ export { type AppendOutgoingOptions as A, type JobRecord as J, type OutgoingMessage as O, type PaymentCreditDelta as P, type ReceiptIssuingStore as R, type StaleJobReapable as S, type TopUpCapUnenforcedReason as T, type VerifyAndCreditResult as V, type JobStatus as a, type JobStore as b, type StreamableJobStore as c, type RequestIdClaim as d, type RequestIdClaimResult as e, type JobRetentionCursor as f, type JobCounters as g, isStreamableJobStore as h, isStaleJobReapable as i };