@dvmkit/sdk 0.7.0-rc.1 → 0.7.0-rc.10

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 (69) hide show
  1. package/README.md +39 -2
  2. package/dist/{chunk-S3OY533W.js → chunk-2NUFE2PG.js} +47 -5
  3. package/dist/{chunk-RA2WIX7G.js → chunk-3E4C6W7W.js} +17 -6
  4. package/dist/{chunk-GY66THYI.js → chunk-53SNRGQQ.js} +372 -153
  5. package/dist/{chunk-CXCQLOOK.js → chunk-62FI3NJF.js} +42 -107
  6. package/dist/chunk-6HJEVCVI.js +17 -0
  7. package/dist/chunk-A262WVYX.js +6 -0
  8. package/dist/{chunk-IV6Q7NRW.js → chunk-C5RIQWG4.js} +1 -1
  9. package/dist/{chunk-GAOAYHZA.js → chunk-FBQVOOYF.js} +37 -6
  10. package/dist/{chunk-Z4ZT5LNT.js → chunk-GEI4MAXE.js} +3 -3
  11. package/dist/{chunk-OML32F6I.js → chunk-GLIAR7RN.js} +1 -1
  12. package/dist/{chunk-X7EZ5CLI.js → chunk-HCATFJDD.js} +3 -3
  13. package/dist/{chunk-BYCEKO65.js → chunk-HPCXF3DV.js} +220 -32
  14. package/dist/{chunk-6XF3RVVX.js → chunk-IRXIH775.js} +5 -5
  15. package/dist/{chunk-QR2EF2NI.js → chunk-KCQDXIJU.js} +1 -42
  16. package/dist/chunk-MDW7ZR7V.js +47 -0
  17. package/dist/{chunk-KA6VN5FK.js → chunk-MQYM4T66.js} +3 -3
  18. package/dist/{chunk-P6QON7ER.js → chunk-NV7Z7377.js} +1 -1
  19. package/dist/{chunk-KMED3H3A.js → chunk-OBBVDKLE.js} +476 -230
  20. package/dist/{chunk-EQLA4FFY.js → chunk-OHUXCAOM.js} +17 -6
  21. package/dist/{chunk-FWGWLPXF.js → chunk-PZ5IAWFD.js} +1 -1
  22. package/dist/{chunk-IXR5RFK7.js → chunk-R3TQSXWT.js} +191 -5
  23. package/dist/{chunk-VHNPVGSA.js → chunk-T5DIXOLR.js} +3 -3
  24. package/dist/{chunk-RCM3P2HE.js → chunk-UKD3R2KU.js} +55 -4
  25. package/dist/chunk-WMGXJQI3.js +120 -0
  26. package/dist/{chunk-N5RZ5HVB.js → chunk-X6S2CHFM.js} +2 -2
  27. package/dist/{chunk-ETXDTUJO.js → chunk-ZCNPIILZ.js} +112 -1
  28. package/dist/{content-retention-DHyLdA6b.d.ts → content-retention-D2K4MKNO.d.ts} +1 -1
  29. package/dist/{credit-ledger-MCUXYKSJ.js → credit-ledger-MFIIOSZH.js} +4 -4
  30. package/dist/{credit-menu-DjnkeEUU.d.ts → credit-menu-D2t1-Tf6.d.ts} +13 -6
  31. package/dist/{fx-zMWFzfrU.d.ts → fx-Ql4nvdbh.d.ts} +10 -3
  32. package/dist/index.d.ts +5 -5
  33. package/dist/index.js +248 -16
  34. package/dist/internal/caller.d.ts +5032 -8272
  35. package/dist/internal/caller.js +4302 -8084
  36. package/dist/internal/index.js +1 -1
  37. package/dist/internal/server.d.ts +7 -7
  38. package/dist/internal/server.js +30 -31
  39. package/dist/{job-store-DdLXBpol.d.ts → job-store-DCvJQUX0.d.ts} +1 -1
  40. package/dist/{lightning-backend-Bw-qtAbt.d.ts → lightning-backend-CY5ML3km.d.ts} +2 -2
  41. package/dist/{memory-credit-ledger-SBSMGMDX.js → memory-credit-ledger-44NE4PZC.js} +5 -5
  42. package/dist/{mpp-setup-RF5FXGGV.js → mpp-setup-HRWUBULX.js} +4 -2
  43. package/dist/{payout-reporter-3Y2U55ZX.js → payout-reporter-7P66T77I.js} +6 -4
  44. package/dist/{postgres-consumed-credential-store-BUVCAPLS.js → postgres-consumed-credential-store-IDGPFKIL.js} +3 -3
  45. package/dist/{postgres-job-store-VHLQBJZC.js → postgres-job-store-UKNJ2F6Z.js} +2 -2
  46. package/dist/{postgres-replay-store-ARZSQ45X.js → postgres-replay-store-PMA4ADXN.js} +2 -2
  47. package/dist/{pricing-XT2IAPNE.js → pricing-52XJHJ6K.js} +12 -12
  48. package/dist/{revenue-reporter-MAERPYGK.js → revenue-reporter-HV7RMS54.js} +2 -2
  49. package/dist/server/index.d.ts +12 -11
  50. package/dist/server/index.js +47 -48
  51. package/dist/{step-cache-2MkvenJn.d.ts → step-cache-BOWAn_BH.d.ts} +84 -7
  52. package/dist/{tempo-charge-store-Q3D7KYMH.js → tempo-charge-store-P2Z4RMU7.js} +2 -2
  53. package/dist/{tempo-session-store-ZRNCOX7P.js → tempo-session-store-VSP7KLBS.js} +3 -3
  54. package/dist/testing/index.d.ts +3 -3
  55. package/dist/testing/index.js +7 -6
  56. package/dist/{usd-DI3d3gTK.d.ts → usd-MAtMp3DB.d.ts} +1 -1
  57. package/dist/{x402-FT4S4JS4.js → x402-3IQ3HXSG.js} +8 -26
  58. package/package.json +1 -1
  59. package/dist/chunk-AT6V3SY7.js +0 -102
  60. package/dist/chunk-EZXAJ6EI.js +0 -1082
  61. package/dist/chunk-HCLVYZUZ.js +0 -244
  62. package/dist/chunk-JEVLNAO3.js +0 -1668
  63. package/dist/chunk-NFMOPYBA.js +0 -357
  64. package/dist/chunk-O2OTZ5GP.js +0 -1869
  65. package/dist/chunk-OFVV37XC.js +0 -57
  66. package/dist/chunk-SZVXOQ75.js +0 -211
  67. package/dist/chunk-WBOKPC37.js +0 -795
  68. package/dist/tempo-lifecycle-GJ77YKVY.js +0 -15
  69. package/dist/tempo-wallet-X2GGAZ6X.js +0 -40
package/README.md CHANGED
@@ -92,12 +92,16 @@ Builders can pass `onPaidJobCompleted` to observe settled customer charges after
92
92
  - A `credit` block opts into reusable prepaid balances and their reclaim obligations. Reusable channels return undrawn collateral through their channel lifecycle; one-payment stablecoin funding is disabled unless `allowOneShotStablecoin: true` because it creates manual refunds. Run and monitor the documented reclaim/payout worker.
93
93
  - Honour `ctx.signal`, use `ctx.fetch` for abortable provider calls, and declare provider cost with `ctx.cost` only after the durable step that incurred it.
94
94
 
95
+ Reusable x402 channels default to a 24-hour payer withdrawal grace (86400 seconds) on every network. For Base Sepolia (`eip155:84532`) acceptance tests, set `x402.batchSettlement.withdrawDelay: 900` or `DVMKIT_X402_WITHDRAW_DELAY_SECONDS=900` with `DVMKIT_X402_BATCH_SETTLEMENT=true`. Explicit delays must be integer seconds and at least 900 on Base Sepolia; Base mainnet retains the 86400 minimum even in development mode and still requires self-relay. The grace is bound into each channel's immutable configuration: changing the server setting affects new channels, not existing deposits. A shorter testnet run exercises early-withdrawal refusal and post-deadline recovery; retain a separate 24-hour run to cover next-day recovery.
96
+
95
97
  For Bitcoin credit monitoring, `GET /admin/credit/bitcoin-liability` reports exact in-kind obligations in satoshis. Uncovered and basisless credits need live-rate pricing on reclaim, so their whole balances appear in `uncovered_by_currency` and `unbacked_by_currency` as `{ currency, micro, credits }` entries. Sum the credit counts if useful, but display or price each currency separately. Older consumers must replace the former `uncovered_micro` and `unbacked_micro` scalar reads; the [portable response fixture](./protocol-fixtures/bitcoin-liability.json) shows the full shape.
96
98
 
97
99
  The public [SDK guide](https://dvmkit.com/docs/sdk) covers signed requests, custom routes, rail configuration, credit reclaim, persistence, and deployment in depth. The [protocol guide](https://dvmkit.com/docs/protocol) specifies the HTTP contract.
98
100
 
99
101
  ## Diagnostic retention
100
102
 
103
+ [Payment diagnostics](./docs/DIAGNOSTICS.md) documents the closed failure categories, stages, and top-up causes retained by safe logging sinks.
104
+
101
105
  The SDK advertises 730 days of retention for sanitized diagnostics in every existing span dataset: `platform`, `cashu`, `lightning`, `stripe`, `x402`, `tempo`, `isolate`, `audit`, and `cron`. Dataset names, producer permissions, and query semantics remain unchanged; only operator services may write to `audit`.
102
106
 
103
107
  This metadata describes the retention schedule. The SDK does not automatically delete diagnostics or override platform destruction or case controls. The platform must enforce the schedule and those controls.
@@ -106,7 +110,7 @@ This metadata describes the retention schedule. The SDK does not automatically d
106
110
 
107
111
  Use the Node.js release in `.nvmrc` (Node 22). Install with `npm ci`, then run the same gate CI runs with `npm run verify`. The gate checks repository metadata in the checkout, copies only tracked and unignored source into an isolated directory, installs its locked dependencies, and runs lint, build, typecheck (including the release scripts under `checkJs`), unit and database tests, API, package, consumer, packed-artifact safety, and release checks there. The clean source tree owns the complete build and test pass, so verification never depends on the checkout's `node_modules` or pre-existing `dist` and does not repeat the full suite; after every check passes, its verified `dist` replaces the checkout's build output for release packaging.
108
112
 
109
- `DATABASE_URL` is optional locally. Without it the database suite is skipped rather than passed, and `verify` says so in its summary, because a skipped suite verified nothing and must not be reported as green. Point `DATABASE_URL` at a scratch Postgres to run it, and pass `npm run verify -- --require-database` to make a missing one a failure, which is what CI does.
113
+ `DATABASE_URL` is optional locally. Without it the database suite is skipped rather than passed, and `verify` says so in its summary, because a skipped suite verified nothing and must not be reported as green. Point `DATABASE_URL` at a scratch Postgres to run it, and pass `npm run verify -- --require-database` to make a missing one a failure, which is what CI does. That mode also requires the local test mint started by `node scripts/start-cashu-mint.mjs`.
110
114
 
111
115
  The public surface is exactly `@dvmkit/sdk`, `@dvmkit/sdk/server`, and `@dvmkit/sdk/testing`.
112
116
 
@@ -146,6 +150,10 @@ The same values are accepted as `--endpoint <url>` and `--job-token <hex>`. Thes
146
150
 
147
151
  If an upgrade broke something for you, an [API compatibility report](https://github.com/dvmkit/sdk/issues/new/choose) is the most useful thing you can send us. It tells us which parts of the published surface people actually build on, which no test of ours can.
148
152
 
153
+ ## Job identity field
154
+
155
+ Every 200 or 202 response to `POST /v1/job`, `POST /v1/request-resume/:id` and `POST /v1/request-result/:id` carries a `DVM-Job-Identity-V1` response-head field whose value is the body's `job_id`, byte for byte, so a router or caller can learn which job a response names without reading a body of any size. No other status carries it. The value grammar is `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`; a server whose job ID falls outside it sends no field. A reader takes the value from `Headers.get`, trims nothing and never folds case: a repeated field (joined with `", "`), an empty value or any other character is invalid, and a trailer of the same name is never identity. The portable contract and its classification vectors are in `protocol-fixtures/job-identity-v1.json`. Hosting control's status reports `jobIdentityHeader: "v1"` when the deployment emits the field.
156
+
149
157
  ## Per-job costs
150
158
 
151
159
  A handler can declare what a job cost **you**, the builder, to serve — a vendor API call, a GPU minute, a per-request licence fee — and the SDK reports it to the platform alongside what the caller paid, so your dashboard can state a margin instead of guessing at one.
@@ -167,7 +175,36 @@ Three things worth knowing:
167
175
  - **Declare the cost where you incur it, including inside `ctx.step`.** The step cache carries those declarations with the result and applies them once when a replay skips the body.
168
176
  - **Every terminal job reports its declared cost.** A paid completion carries the cost on its revenue report. A free completion, failure, or cancellation sends a separate cost-only report and does not create revenue.
169
177
 
170
- The cost is yours, never the caller's: it is recorded separately from what the caller paid and is never netted against it. Both report shapes use the same durable outbox. Cost-only reports are keyed on the job id and revisioned, so retries and cross-machine updates converge on the latest cumulative value. With the Postgres job store, boot-time and periodic reconciliation enqueue any terminal cost revision that a process exited before reporting.
178
+ The cost is yours, never the caller's: it is recorded separately from what the caller paid and is never netted against it. Both report shapes use the same durable outbox. Cost-only reports are keyed on the job id and revisioned, so retries and cross-machine updates converge on the latest cumulative value. With the Postgres job store, boot-time and periodic reconciliation enqueue any terminal cost revision that a process exited before reporting. Other job stores get no reconciliation: their cost-only report is durable only once the outbox insert commits, so an exit between the terminal save and that insert loses it.
179
+
180
+ ## Rate cards
181
+
182
+ A priced capability can tell callers what it charges per unit, so an agent can explain the price before it asks for a quote. Declare `rates` next to `price` or `onQuote`, in the flat shape or on an entry in `capabilities`:
183
+
184
+ ```ts
185
+ capabilities: {
186
+ synthesise: {
187
+ onQuote: { schema, handler: quoteBySize },
188
+ rates: [
189
+ { label: "balanced", amount: "0.00015", currency: "USD", per: "character" },
190
+ { label: "expressive", amount: "0.00030", currency: "USD", per: "character" },
191
+ ],
192
+ onJob,
193
+ },
194
+ },
195
+ ```
196
+
197
+ `/v1/info` publishes the list as `capabilities.<name>.pricing.rates`, exactly as you wrote it: same order, same strings. A capability without `rates` publishes nothing extra. For a quoted capability the rates describe how your quote is computed; the SDK does not compute or check quotes against them, so keep the two in step yourself.
198
+
199
+ The server refuses to start, naming the capability and the entry, when a rate card breaks one of these limits:
200
+
201
+ - 1 to 5 entries, on a capability with `price` or `onQuote`. A free capability cannot declare rates.
202
+ - `amount` is decimal dollars as a string, above zero and at most 12 characters: `"0.00015"`, not `0.00015` or `"$0.00015"`.
203
+ - `currency` is `"USD"`.
204
+ - `per` names the unit (`"character"`, `"episode"`, `"GB per day stored"`) and `label`, which is optional, names a tier. Each is 1 to 24 characters with no leading or trailing spaces.
205
+ - No other keys.
206
+
207
+ The [portable fixture](./protocol-fixtures/capability-rates.json) carries the same limits and the accepted and refused examples, for clients that parse the field.
171
208
 
172
209
  ## Contributing
173
210
 
@@ -1,9 +1,34 @@
1
1
  import {
2
2
  X402_DEFAULT_FACILITATOR
3
- } from "./chunk-CXCQLOOK.js";
3
+ } from "./chunk-62FI3NJF.js";
4
4
  import {
5
5
  callerLoggers
6
6
  } from "./chunk-66HGCPBU.js";
7
+ import {
8
+ diagnosticConsole
9
+ } from "./chunk-ZCNPIILZ.js";
10
+
11
+ // src/observability/payment-diagnostics.ts
12
+ import { AsyncLocalStorage } from "async_hooks";
13
+ var scopes = new AsyncLocalStorage();
14
+ function withPaymentDiagnostics(fn) {
15
+ return scopes.run({}, fn);
16
+ }
17
+ async function withPaymentStage(stage, fn) {
18
+ try {
19
+ return await fn();
20
+ } catch (error) {
21
+ const scope = scopes.getStore();
22
+ if (scope && (!scope.failure || !Object.is(scope.failure.error, error))) {
23
+ scope.failure = { error, stage };
24
+ }
25
+ throw error;
26
+ }
27
+ }
28
+ function paymentFailureStage(error) {
29
+ const failure = scopes.getStore()?.failure;
30
+ return failure && Object.is(failure.error, error) ? failure.stage : "unknown";
31
+ }
7
32
 
8
33
  // src/lib/x402/facilitator.ts
9
34
  import { createPrivateKey, randomBytes } from "crypto";
@@ -82,7 +107,7 @@ async function verifyWithFacilitator(payload, requirements, config = {}, createA
82
107
  return callerLoggers.x402.span(
83
108
  "x402.verify_with_facilitator",
84
109
  { facilitator_url: url },
85
- async () => {
110
+ () => withPaymentStage("verifier", async () => {
86
111
  const authHeaders = (await createAuthHeaders?.())?.verify ?? {};
87
112
  const res = await fetch(url, {
88
113
  method: "POST",
@@ -96,14 +121,17 @@ async function verifyWithFacilitator(payload, requirements, config = {}, createA
96
121
  if (!res.ok) {
97
122
  const rejection = await readFacilitatorRejection(res);
98
123
  if (rejection?.isValid === false && typeof rejection.invalidReason === "string" && rejection.invalidReason.length > 0) {
124
+ logRejected("verifier");
99
125
  return rejection;
100
126
  }
127
+ logRejected("verifier");
101
128
  return { isValid: false, invalidReason: `facilitator_status_${res.status}` };
102
129
  }
103
130
  const data = await res.json();
131
+ if (!data.isValid) logRejected("verifier");
104
132
  callerLoggers.x402.info("x402.verify_with_facilitator.result", { is_valid: data.isValid });
105
133
  return data;
106
- }
134
+ })
107
135
  );
108
136
  }
109
137
  async function settleWithFacilitator(payload, requirements, config = {}, createAuthHeaders = createX402FacilitatorAuthHeaders(config)) {
@@ -112,7 +140,7 @@ async function settleWithFacilitator(payload, requirements, config = {}, createA
112
140
  return callerLoggers.x402.span(
113
141
  "x402.settle_with_facilitator",
114
142
  { facilitator_url: url },
115
- async () => {
143
+ () => withPaymentStage("broadcast", async () => {
116
144
  const authHeaders = (await createAuthHeaders?.())?.settle ?? {};
117
145
  const res = await fetch(url, {
118
146
  method: "POST",
@@ -126,17 +154,20 @@ async function settleWithFacilitator(payload, requirements, config = {}, createA
126
154
  if (!res.ok) {
127
155
  const rejection = await readFacilitatorRejection(res);
128
156
  if (rejection?.success === false && typeof rejection.errorReason === "string" && rejection.errorReason.length > 0) {
157
+ logRejected("broadcast");
129
158
  return rejection;
130
159
  }
160
+ logRejected("broadcast");
131
161
  return { success: false, errorReason: `facilitator_status_${res.status}` };
132
162
  }
133
163
  const data = await res.json();
164
+ if (!data.success) logRejected("broadcast");
134
165
  callerLoggers.x402.info("x402.settle_with_facilitator.result", {
135
166
  success: data.success,
136
167
  tx_hash: data.transaction
137
168
  });
138
169
  return data;
139
- }
170
+ })
140
171
  );
141
172
  }
142
173
  async function readFacilitatorRejection(res) {
@@ -151,8 +182,19 @@ async function readFacilitatorRejection(res) {
151
182
  function isRecord(value) {
152
183
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
153
184
  }
185
+ function logRejected(stage) {
186
+ diagnosticConsole.warn({
187
+ event: "x402_credential_rejected",
188
+ rail: "x402",
189
+ failure_category: stage === "verifier" ? "verification_failed" : "settlement_failed",
190
+ failure_stage: stage
191
+ });
192
+ }
154
193
 
155
194
  export {
195
+ withPaymentDiagnostics,
196
+ withPaymentStage,
197
+ paymentFailureStage,
156
198
  createX402FacilitatorAuthHeaders,
157
199
  createX402FacilitatorClient,
158
200
  verifyWithFacilitator,
@@ -2,19 +2,20 @@ import {
2
2
  createX402FacilitatorAuthHeaders,
3
3
  settleWithFacilitator,
4
4
  verifyWithFacilitator
5
- } from "./chunk-S3OY533W.js";
6
- import {
7
- usdcToMsats
8
- } from "./chunk-NFMOPYBA.js";
5
+ } from "./chunk-2NUFE2PG.js";
9
6
  import {
10
7
  buildPaymentRequirements,
11
8
  decodePayment,
12
9
  encodeSettleResponseHeader,
13
- exactEvmAuthorization
14
- } from "./chunk-CXCQLOOK.js";
10
+ exactEvmAuthorization,
11
+ usdcToMsats
12
+ } from "./chunk-62FI3NJF.js";
15
13
  import {
16
14
  callerLoggers
17
15
  } from "./chunk-66HGCPBU.js";
16
+ import {
17
+ diagnosticConsole
18
+ } from "./chunk-ZCNPIILZ.js";
18
19
 
19
20
  // src/lib/x402/verify.ts
20
21
  async function verifyX402Payment(paymentHeader, config, requiredUsdcMicro, resource, btcUsdRate, exactVersions = { v1: true, v2: true }) {
@@ -30,9 +31,11 @@ async function verifyX402Payment(paymentHeader, config, requiredUsdcMicro, resou
30
31
  try {
31
32
  payload = decodePayment(paymentHeader);
32
33
  } catch {
34
+ logRejected("malformed_credential", "verifier");
33
35
  return { verified: false, amountMsats: 0, invalidReason: "decode_failed" };
34
36
  }
35
37
  if (payload.x402Version === 1 && !exactVersions.v1 || payload.x402Version === 2 && !exactVersions.v2) {
38
+ logRejected("unsupported_version", "verifier");
36
39
  return { verified: false, amountMsats: 0, invalidReason: "unsupported_version" };
37
40
  }
38
41
  const requirementOpts = { x402Config: config, requiredUsdcMicro, resource };
@@ -81,6 +84,14 @@ async function verifyX402Payment(paymentHeader, config, requiredUsdcMicro, resou
81
84
  }
82
85
  );
83
86
  }
87
+ function logRejected(category, stage) {
88
+ diagnosticConsole.warn({
89
+ event: "x402_credential_rejected",
90
+ rail: "x402",
91
+ failure_category: category,
92
+ failure_stage: stage
93
+ });
94
+ }
84
95
 
85
96
  export {
86
97
  verifyX402Payment