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