@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
@@ -6,12 +6,32 @@ import {
6
6
  import { randomUUID } from "crypto";
7
7
  var ENDPOINTS = {
8
8
  revenue: "/_internal/job-revenue",
9
+ cost: "/_internal/job-cost",
9
10
  deposit: "/_internal/credit-deposit",
10
11
  release: "/_internal/credit-draw-release",
11
12
  drain: "/_internal/credit-drain",
12
13
  payout: "/_internal/payout",
13
14
  expiry_release: "/_internal/credit-expiry-release"
14
15
  };
16
+ var PENDING_DVM_REPORTS_TABLE = "pending_dvm_reports";
17
+ var LEGACY_PENDING_REPORTS_TABLE = "pending_revenue_reports";
18
+ var SDK_REPORT_TABLE_BASE_COLUMNS = [
19
+ "id",
20
+ "payload",
21
+ "retry_count",
22
+ "next_retry_at",
23
+ "stale",
24
+ "created_at"
25
+ ];
26
+ var PLATFORM_REPORT_TABLE_COLUMNS = [
27
+ "id",
28
+ "payload",
29
+ "source",
30
+ "retry_count",
31
+ "next_retry_at",
32
+ "last_error",
33
+ "created_at"
34
+ ];
15
35
  var RevenueReporter = class _RevenueReporter {
16
36
  // 24h of failed retries
17
37
  constructor(db, platformUrl, platformToken) {
@@ -27,14 +47,14 @@ var RevenueReporter = class _RevenueReporter {
27
47
  static MAX_BACKOFF_MS = 3e5;
28
48
  // 5 min
29
49
  static ABANDON_AFTER_MS = 24 * 60 * 60 * 1e3;
30
- /** Create the pending-reports table (idempotent). */
50
+ /** Create the pending-reports table and migrate the legacy SDK outbox (idempotent). */
31
51
  async init() {
32
52
  await withSdkInitLock(this.db, () => this.createTables());
33
53
  }
34
54
  /** The boot DDL itself — always runs under {@link withSdkInitLock} (internal-review). */
35
55
  async createTables() {
36
56
  await this.db.query(`
37
- CREATE TABLE IF NOT EXISTS pending_revenue_reports (
57
+ CREATE TABLE IF NOT EXISTS ${PENDING_DVM_REPORTS_TABLE} (
38
58
  id TEXT PRIMARY KEY,
39
59
  payload JSONB NOT NULL,
40
60
  retry_count INTEGER NOT NULL DEFAULT 0,
@@ -45,17 +65,75 @@ var RevenueReporter = class _RevenueReporter {
45
65
  kind TEXT NOT NULL DEFAULT 'revenue'
46
66
  )
47
67
  `);
68
+ await this.assertSdkTableShape(PENDING_DVM_REPORTS_TABLE);
48
69
  await this.db.query(
49
- "ALTER TABLE pending_revenue_reports ADD COLUMN IF NOT EXISTS stale_reason TEXT"
70
+ `ALTER TABLE ${PENDING_DVM_REPORTS_TABLE} ADD COLUMN IF NOT EXISTS stale_reason TEXT`
50
71
  );
51
72
  await this.db.query(
52
- "ALTER TABLE pending_revenue_reports ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'revenue'"
73
+ `ALTER TABLE ${PENDING_DVM_REPORTS_TABLE} ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'revenue'`
53
74
  );
75
+ await this.migrateLegacySdkOutbox();
76
+ }
77
+ /** Copy rows from the old SDK-owned table without touching the platform's table of that name. */
78
+ async migrateLegacySdkOutbox() {
79
+ const columns = await this.tableColumns(LEGACY_PENDING_REPORTS_TABLE);
80
+ if (columns.size === 0) return;
81
+ if (PLATFORM_REPORT_TABLE_COLUMNS.every((column) => columns.has(column))) {
82
+ return;
83
+ }
84
+ this.assertSdkColumns(LEGACY_PENDING_REPORTS_TABLE, columns);
85
+ await this.db.query(
86
+ `ALTER TABLE ${LEGACY_PENDING_REPORTS_TABLE} ADD COLUMN IF NOT EXISTS stale_reason TEXT`
87
+ );
88
+ await this.db.query(
89
+ `ALTER TABLE ${LEGACY_PENDING_REPORTS_TABLE} ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'revenue'`
90
+ );
91
+ await this.db.query(`
92
+ WITH copied AS (
93
+ INSERT INTO ${PENDING_DVM_REPORTS_TABLE}
94
+ (id, payload, retry_count, next_retry_at, stale, stale_reason, created_at, kind)
95
+ SELECT id, payload, retry_count, next_retry_at, stale, stale_reason, created_at, kind
96
+ FROM ${LEGACY_PENDING_REPORTS_TABLE}
97
+ ON CONFLICT (id) DO NOTHING
98
+ RETURNING id
99
+ )
100
+
101
+ DELETE FROM ${LEGACY_PENDING_REPORTS_TABLE} AS legacy
102
+ WHERE EXISTS (SELECT 1 FROM copied WHERE copied.id = legacy.id)
103
+ OR EXISTS (
104
+ SELECT 1 FROM ${PENDING_DVM_REPORTS_TABLE} AS migrated
105
+ WHERE migrated.id = legacy.id
106
+ )
107
+ `);
108
+ }
109
+ /** Refuse a pre-existing relation whose columns cannot safely serve as the SDK outbox. */
110
+ async assertSdkTableShape(tableName) {
111
+ this.assertSdkColumns(tableName, await this.tableColumns(tableName));
112
+ }
113
+ assertSdkColumns(tableName, columns) {
114
+ const matchesSdk = SDK_REPORT_TABLE_BASE_COLUMNS.every((column) => columns.has(column)) && !columns.has("source");
115
+ if (matchesSdk) return;
116
+ throw new Error(
117
+ `RevenueReporter.init(): table "${tableName}" is not the SDK revenue outbox shape. The SDK owns "${PENDING_DVM_REPORTS_TABLE}"; the platform owns "${LEGACY_PENDING_REPORTS_TABLE}". Refusing to boot before credit funding can reach a mismatched outbox. Found columns: ${[...columns].sort().join(", ") || "none"}.`
118
+ );
119
+ }
120
+ async tableColumns(tableName) {
121
+ const { rows } = await this.db.query(
122
+ `SELECT column_name
123
+ FROM information_schema.columns
124
+ WHERE table_schema = current_schema() AND table_name = $1`,
125
+ [tableName]
126
+ );
127
+ return new Set(rows.map((row) => row.column_name));
54
128
  }
55
129
  /** Persist a revenue report and attempt immediate delivery. */
56
130
  async report(payload) {
57
131
  return this.enqueue("revenue", payload, payload.jobId);
58
132
  }
133
+ /** Persist a zero-revenue job cost and attempt immediate delivery. */
134
+ async reportCost(payload) {
135
+ return this.enqueue("cost", payload, payload.jobId);
136
+ }
59
137
  /**
60
138
  * Join a credit deposit to the caller-owned rail transaction.
61
139
  *
@@ -367,7 +445,7 @@ var RevenueReporter = class _RevenueReporter {
367
445
  async getQueueStats() {
368
446
  const { rows } = await this.db.query(
369
447
  `SELECT stale, stale_reason, COUNT(*)::int AS count
370
- FROM pending_revenue_reports
448
+ FROM ${PENDING_DVM_REPORTS_TABLE}
371
449
  GROUP BY stale, stale_reason`
372
450
  );
373
451
  let liveRetry = 0;
@@ -394,7 +472,7 @@ var RevenueReporter = class _RevenueReporter {
394
472
  async drainPending() {
395
473
  try {
396
474
  const { rows } = await this.db.query(
397
- `SELECT id, payload, retry_count, created_at, kind FROM pending_revenue_reports
475
+ `SELECT id, payload, retry_count, created_at, kind FROM ${PENDING_DVM_REPORTS_TABLE}
398
476
  WHERE stale = false AND next_retry_at <= $1
399
477
  ORDER BY created_at LIMIT 10`,
400
478
  [Date.now()]
@@ -428,7 +506,7 @@ var RevenueReporter = class _RevenueReporter {
428
506
  _RevenueReporter.MAX_BACKOFF_MS
429
507
  );
430
508
  await this.db.query(
431
- `UPDATE pending_revenue_reports
509
+ `UPDATE ${PENDING_DVM_REPORTS_TABLE}
432
510
  SET retry_count = $1, next_retry_at = $2
433
511
  WHERE id = $3`,
434
512
  [retryCount, Date.now() + backoff, row.id]
@@ -449,7 +527,7 @@ var RevenueReporter = class _RevenueReporter {
449
527
  async insertPending(q, kind, payload) {
450
528
  const id = randomUUID();
451
529
  await q.query(
452
- `INSERT INTO pending_revenue_reports (id, payload, created_at, kind)
530
+ `INSERT INTO ${PENDING_DVM_REPORTS_TABLE} (id, payload, created_at, kind)
453
531
  VALUES ($1, $2, $3, $4)`,
454
532
  [id, JSON.stringify(payload), Date.now(), kind]
455
533
  );
@@ -476,7 +554,7 @@ var RevenueReporter = class _RevenueReporter {
476
554
  throw err;
477
555
  }
478
556
  if (res.ok) {
479
- await this.db.query("DELETE FROM pending_revenue_reports WHERE id = $1", [id]);
557
+ await this.db.query(`DELETE FROM ${PENDING_DVM_REPORTS_TABLE} WHERE id = $1`, [id]);
480
558
  console.log(`[revenue-reporter] reported ${kind} ${label}`);
481
559
  return;
482
560
  }
@@ -500,7 +578,7 @@ var RevenueReporter = class _RevenueReporter {
500
578
  /** Mark a pending row terminal (stale) with a classification reason. */
501
579
  async markStale(id, reason) {
502
580
  await this.db.query(
503
- "UPDATE pending_revenue_reports SET stale = true, stale_reason = $1 WHERE id = $2",
581
+ `UPDATE ${PENDING_DVM_REPORTS_TABLE} SET stale = true, stale_reason = $1 WHERE id = $2`,
504
582
  [reason, id]
505
583
  );
506
584
  }
@@ -1,125 +1,10 @@
1
- // src/sdk/types.ts
2
- function isZodSchema(value) {
3
- return typeof value === "object" && value !== null && "_zod" in value && "parse" in value && "safeParse" in value;
4
- }
5
- var RAIL_REFUNDABLE = {
6
- cashu: false,
7
- x402: false,
8
- tempo: false
9
- };
10
- var DEFAULT_CREDIT_MIN = "$0.10";
11
- var DEFAULT_CREDIT_MAX = "$5.00";
12
- var DEFAULT_CREDIT_TTL_SECONDS = 30 * 24 * 60 * 60;
13
-
14
- // src/lib/cast-auth/canonical-json.ts
15
- function canonicalize(value) {
16
- return JSON.stringify(sortDeep(value));
17
- }
18
- function canonicaliseForSigning(value) {
19
- return new TextEncoder().encode(canonicalize(value));
20
- }
21
- function sortDeep(value) {
22
- if (value === null) return null;
23
- if (typeof value === "string" || typeof value === "boolean") return value;
24
- if (typeof value === "number") {
25
- if (!Number.isFinite(value)) {
26
- throw new TypeError(`canonicalize: non-finite number is not JSON-serialisable`);
27
- }
28
- return value;
29
- }
30
- if (Array.isArray(value)) return value.map(sortDeep);
31
- if (typeof value === "object") {
32
- const sorted = {};
33
- for (const key of Object.keys(value).sort()) {
34
- const v = value[key];
35
- if (v === void 0) continue;
36
- sorted[key] = sortDeep(v);
37
- }
38
- return sorted;
39
- }
40
- throw new TypeError(`canonicalize: unsupported value of type ${typeof value}`);
41
- }
42
-
43
- // src/lib/cast-auth/verify.ts
44
- import { schnorr } from "@noble/curves/secp256k1.js";
45
- function verifyCanonicalSignedRequest(payload, signatureHex, pubkeyHex) {
46
- if (!/^[0-9a-f]{64}$/i.test(pubkeyHex) || !/^[0-9a-f]{128}$/i.test(signatureHex)) {
47
- return false;
48
- }
49
- const data = canonicaliseForSigning(payload);
50
- const signature = hexToBytes(signatureHex);
51
- const pubkey = hexToBytes(pubkeyHex);
52
- try {
53
- return schnorr.verify(signature, data, pubkey);
54
- } catch {
55
- return false;
56
- }
57
- }
58
- function hexToBytes(hex) {
59
- const out = new Uint8Array(hex.length / 2);
60
- for (let i = 0; i < out.length; i++) {
61
- out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
62
- }
63
- return out;
64
- }
65
-
66
- // src/lib/signed-request-statement.ts
67
- var SIGNED_REQUEST_AUTH_ID = "secp256k1-schnorr-v2";
68
- var SIGNED_REQUEST_STATEMENT_VERSION = 2;
69
- var TEST_DOMAIN_SENTINEL = "__dvmkit_test_domain__";
70
- function normalizeSignedRequestDomain(domain) {
71
- return {
72
- audience: {
73
- dvm_id: domain.audience.dvm_id,
74
- builder_pubkey: domain.audience.builder_pubkey.trim().toLowerCase()
75
- },
76
- method: domain.method.trim().toUpperCase(),
77
- path: canonicalRequestPath(domain.path),
78
- capability: domain.capability
79
- };
80
- }
81
- function signedRequestStatementHeader(domain) {
82
- const normalized = normalizeSignedRequestDomain(domain);
83
- return {
84
- v: SIGNED_REQUEST_STATEMENT_VERSION,
85
- protocol: "dvmkit",
86
- protocol_version: 1,
87
- auth: SIGNED_REQUEST_AUTH_ID,
88
- audience: normalized.audience,
89
- method: normalized.method,
90
- path: normalized.path,
91
- capability: normalized.capability
92
- };
93
- }
94
- function canonicalSignedRequestStatement(header, input) {
95
- return canonicaliseForSigning({
96
- ...header,
97
- input
98
- });
99
- }
100
- function signedRequestHeaderMatchesDomain(header, domain) {
101
- if (!header || typeof header !== "object" || Array.isArray(header)) return false;
102
- const actual = header;
103
- const expected = signedRequestStatementHeader(domain);
104
- const audience = actual.audience;
105
- if (process.env.NODE_ENV === "test" && actual.method === TEST_DOMAIN_SENTINEL.toUpperCase() && actual.path === `/${TEST_DOMAIN_SENTINEL}` && actual.capability === TEST_DOMAIN_SENTINEL && audience?.dvm_id === TEST_DOMAIN_SENTINEL) {
106
- return true;
107
- }
108
- return actual.v === expected.v && actual.protocol === expected.protocol && actual.protocol_version === expected.protocol_version && actual.auth === expected.auth && actual.method === expected.method && actual.path === expected.path && actual.capability === expected.capability && !!audience && typeof audience === "object" && audience.dvm_id === expected.audience.dvm_id && audience.builder_pubkey === expected.audience.builder_pubkey;
109
- }
110
- function testOnlySignedRequestDomain() {
111
- if (process.env.NODE_ENV !== "test") return void 0;
112
- return {
113
- audience: { dvm_id: TEST_DOMAIN_SENTINEL, builder_pubkey: "00".repeat(32) },
114
- method: TEST_DOMAIN_SENTINEL,
115
- path: TEST_DOMAIN_SENTINEL,
116
- capability: TEST_DOMAIN_SENTINEL
117
- };
118
- }
119
- function canonicalRequestPath(pathOrUrl) {
120
- const parsed = new URL(pathOrUrl, "https://signed-request.invalid");
121
- return parsed.pathname.startsWith("/") ? parsed.pathname : `/${parsed.pathname}`;
122
- }
1
+ import {
2
+ canonicalSignedRequestStatement,
3
+ isZodSchema,
4
+ signedRequestHeaderMatchesDomain,
5
+ signedRequestStatementHeader,
6
+ testOnlySignedRequestDomain
7
+ } from "./chunk-KXZUCCEY.js";
123
8
 
124
9
  // src/sdk/server/zod-error.ts
125
10
  function humanizeZodError(err, opts) {
@@ -207,7 +92,7 @@ function pickHint(issues, schemaUrl) {
207
92
  }
208
93
 
209
94
  // src/sdk/server/signed-request.ts
210
- import { schnorr as schnorr2 } from "@noble/curves/secp256k1.js";
95
+ import { schnorr } from "@noble/curves/secp256k1.js";
211
96
  var SIGNED_ENVELOPE_TYPES = {
212
97
  pubkey: "string",
213
98
  signature: "string",
@@ -317,7 +202,7 @@ function createSignedRequestVerifier(schema, opts = {}) {
317
202
  }
318
203
  }
319
204
  function signRequest(privkey, body, signOpts, domain) {
320
- const pubkey = bytesToHex(schnorr2.getPublicKey(privkey));
205
+ const pubkey = bytesToHex(schnorr.getPublicKey(privkey));
321
206
  const timestamp = signOpts?.timestamp ?? now();
322
207
  const nonce = signOpts?.nonce ?? randomNonce();
323
208
  const unsigned = {
@@ -328,7 +213,7 @@ function createSignedRequestVerifier(schema, opts = {}) {
328
213
  };
329
214
  const authStatement = signedRequestStatementHeader(requiredDomain(domain));
330
215
  const signature = bytesToHex(
331
- schnorr2.sign(canonicalBytes({ ...unsigned, auth_statement: authStatement }, domain), privkey)
216
+ schnorr.sign(canonicalBytes({ ...unsigned, auth_statement: authStatement }, domain), privkey)
332
217
  );
333
218
  return { ...unsigned, auth_statement: authStatement, signature };
334
219
  }
@@ -370,12 +255,12 @@ function statementHeader(input) {
370
255
  }
371
256
  function verifyBytes(bytes, signatureHex, pubkeyHex) {
372
257
  try {
373
- return schnorr2.verify(hexToBytes2(signatureHex), bytes, hexToBytes2(pubkeyHex));
258
+ return schnorr.verify(hexToBytes(signatureHex), bytes, hexToBytes(pubkeyHex));
374
259
  } catch {
375
260
  return false;
376
261
  }
377
262
  }
378
- function hexToBytes2(hex) {
263
+ function hexToBytes(hex) {
379
264
  const out = new Uint8Array(hex.length / 2);
380
265
  for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
381
266
  return out;
@@ -427,20 +312,6 @@ function createDefaultReplayStore() {
427
312
  var SATURATION_WARN_INTERVAL_SECONDS = 60;
428
313
 
429
314
  export {
430
- isZodSchema,
431
- RAIL_REFUNDABLE,
432
- DEFAULT_CREDIT_MIN,
433
- DEFAULT_CREDIT_MAX,
434
- DEFAULT_CREDIT_TTL_SECONDS,
435
- canonicalize,
436
- canonicaliseForSigning,
437
- verifyCanonicalSignedRequest,
438
- SIGNED_REQUEST_AUTH_ID,
439
- SIGNED_REQUEST_STATEMENT_VERSION,
440
- signedRequestStatementHeader,
441
- canonicalSignedRequestStatement,
442
- testOnlySignedRequestDomain,
443
- canonicalRequestPath,
444
315
  humanizeZodError,
445
316
  humanizeZodIssues,
446
317
  zodIssuesOf,
@@ -0,0 +1,142 @@
1
+ // src/sdk/types.ts
2
+ function isZodSchema(value) {
3
+ return typeof value === "object" && value !== null && "_zod" in value && "parse" in value && "safeParse" in value;
4
+ }
5
+ var RAIL_REFUNDABLE = {
6
+ cashu: false,
7
+ x402: false,
8
+ tempo: false
9
+ };
10
+ var DEFAULT_CREDIT_MIN = "$0.10";
11
+ var DEFAULT_CREDIT_MAX = "$5.00";
12
+ var DEFAULT_CREDIT_TTL_SECONDS = 30 * 24 * 60 * 60;
13
+ var DEFAULT_JOB_RETENTION_DAYS = 90;
14
+
15
+ // src/lib/cast-auth/canonical-json.ts
16
+ function canonicalize(value) {
17
+ return JSON.stringify(sortDeep(value));
18
+ }
19
+ function canonicaliseForSigning(value) {
20
+ return new TextEncoder().encode(canonicalize(value));
21
+ }
22
+ function sortDeep(value) {
23
+ if (value === null) return null;
24
+ if (typeof value === "string" || typeof value === "boolean") return value;
25
+ if (typeof value === "number") {
26
+ if (!Number.isFinite(value)) {
27
+ throw new TypeError(`canonicalize: non-finite number is not JSON-serialisable`);
28
+ }
29
+ return value;
30
+ }
31
+ if (Array.isArray(value)) return value.map(sortDeep);
32
+ if (typeof value === "object") {
33
+ const sorted = {};
34
+ for (const key of Object.keys(value).sort()) {
35
+ const v = value[key];
36
+ if (v === void 0) continue;
37
+ sorted[key] = sortDeep(v);
38
+ }
39
+ return sorted;
40
+ }
41
+ throw new TypeError(`canonicalize: unsupported value of type ${typeof value}`);
42
+ }
43
+
44
+ // src/lib/cast-auth/verify.ts
45
+ import { schnorr } from "@noble/curves/secp256k1.js";
46
+ function verifyCanonicalSignedRequest(payload, signatureHex, pubkeyHex) {
47
+ if (!/^[0-9a-f]{64}$/i.test(pubkeyHex) || !/^[0-9a-f]{128}$/i.test(signatureHex)) {
48
+ return false;
49
+ }
50
+ const data = canonicaliseForSigning(payload);
51
+ const signature = hexToBytes(signatureHex);
52
+ const pubkey = hexToBytes(pubkeyHex);
53
+ try {
54
+ return schnorr.verify(signature, data, pubkey);
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+ function hexToBytes(hex) {
60
+ const out = new Uint8Array(hex.length / 2);
61
+ for (let i = 0; i < out.length; i++) {
62
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
63
+ }
64
+ return out;
65
+ }
66
+
67
+ // src/lib/signed-request-statement.ts
68
+ var SIGNED_REQUEST_AUTH_ID = "secp256k1-schnorr-v2";
69
+ var SIGNED_REQUEST_STATEMENT_VERSION = 2;
70
+ var TEST_DOMAIN_SENTINEL = "__dvmkit_test_domain__";
71
+ function normalizeSignedRequestDomain(domain) {
72
+ return {
73
+ audience: {
74
+ dvm_id: domain.audience.dvm_id,
75
+ builder_pubkey: domain.audience.builder_pubkey.trim().toLowerCase()
76
+ },
77
+ method: domain.method.trim().toUpperCase(),
78
+ path: canonicalRequestPath(domain.path),
79
+ capability: domain.capability
80
+ };
81
+ }
82
+ function signedRequestStatementHeader(domain) {
83
+ const normalized = normalizeSignedRequestDomain(domain);
84
+ return {
85
+ v: SIGNED_REQUEST_STATEMENT_VERSION,
86
+ protocol: "dvmkit",
87
+ protocol_version: 1,
88
+ auth: SIGNED_REQUEST_AUTH_ID,
89
+ audience: normalized.audience,
90
+ method: normalized.method,
91
+ path: normalized.path,
92
+ capability: normalized.capability
93
+ };
94
+ }
95
+ function canonicalSignedRequestStatement(header, input) {
96
+ return canonicaliseForSigning({
97
+ ...header,
98
+ input
99
+ });
100
+ }
101
+ function signedRequestHeaderMatchesDomain(header, domain) {
102
+ if (!header || typeof header !== "object" || Array.isArray(header)) return false;
103
+ const actual = header;
104
+ const expected = signedRequestStatementHeader(domain);
105
+ const audience = actual.audience;
106
+ if (process.env.NODE_ENV === "test" && actual.method === TEST_DOMAIN_SENTINEL.toUpperCase() && actual.path === `/${TEST_DOMAIN_SENTINEL}` && actual.capability === TEST_DOMAIN_SENTINEL && audience?.dvm_id === TEST_DOMAIN_SENTINEL) {
107
+ return true;
108
+ }
109
+ return actual.v === expected.v && actual.protocol === expected.protocol && actual.protocol_version === expected.protocol_version && actual.auth === expected.auth && actual.method === expected.method && actual.path === expected.path && actual.capability === expected.capability && !!audience && typeof audience === "object" && audience.dvm_id === expected.audience.dvm_id && audience.builder_pubkey === expected.audience.builder_pubkey;
110
+ }
111
+ function testOnlySignedRequestDomain() {
112
+ if (process.env.NODE_ENV !== "test") return void 0;
113
+ return {
114
+ audience: { dvm_id: TEST_DOMAIN_SENTINEL, builder_pubkey: "00".repeat(32) },
115
+ method: TEST_DOMAIN_SENTINEL,
116
+ path: TEST_DOMAIN_SENTINEL,
117
+ capability: TEST_DOMAIN_SENTINEL
118
+ };
119
+ }
120
+ function canonicalRequestPath(pathOrUrl) {
121
+ const parsed = new URL(pathOrUrl, "https://signed-request.invalid");
122
+ return parsed.pathname.startsWith("/") ? parsed.pathname : `/${parsed.pathname}`;
123
+ }
124
+
125
+ export {
126
+ isZodSchema,
127
+ RAIL_REFUNDABLE,
128
+ DEFAULT_CREDIT_MIN,
129
+ DEFAULT_CREDIT_MAX,
130
+ DEFAULT_CREDIT_TTL_SECONDS,
131
+ DEFAULT_JOB_RETENTION_DAYS,
132
+ canonicalize,
133
+ canonicaliseForSigning,
134
+ verifyCanonicalSignedRequest,
135
+ SIGNED_REQUEST_AUTH_ID,
136
+ SIGNED_REQUEST_STATEMENT_VERSION,
137
+ signedRequestStatementHeader,
138
+ canonicalSignedRequestStatement,
139
+ signedRequestHeaderMatchesDomain,
140
+ testOnlySignedRequestDomain,
141
+ canonicalRequestPath
142
+ };
@@ -1,9 +1,9 @@
1
- import {
2
- applyRetryToPool
3
- } from "./chunk-C3MTFLC6.js";
4
1
  import {
5
2
  withSdkInitLock
6
3
  } from "./chunk-S3XAHZQY.js";
4
+ import {
5
+ applyRetryToPool
6
+ } from "./chunk-C3MTFLC6.js";
7
7
 
8
8
  // src/sdk/server/credit-ledger.ts
9
9
  import { randomUUID } from "crypto";
@@ -2736,6 +2736,14 @@ var CreditLedger = class {
2736
2736
  );
2737
2737
  return rows.length === 1 ? drawRecordFromRow(rows[0], rows[0]) : void 0;
2738
2738
  }
2739
+ /** Read every draw linked to a job, including already-resolved rows. */
2740
+ async listDrawsByJobId(jobId) {
2741
+ const { rows } = await this.pool.query(
2742
+ `${DRAW_WITH_BASIS_SELECT} WHERE d.job_id = $1 ORDER BY d.created_at, d.credit_id, d.ledger_seq`,
2743
+ [jobId]
2744
+ );
2745
+ return rows.map((row) => drawRecordFromRow(row, row));
2746
+ }
2739
2747
  /**
2740
2748
  * All pending holds on a credit, in `ledger_seq` order (no lock). Read
2741
2749
  * surface for the internal-review sweeper wiring — a worker that dies mid-job