@dvmkit/sdk 0.2.1-rc.9 → 0.3.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.
@@ -11,6 +11,9 @@ import {
11
11
  import {
12
12
  MemoryCreditLedger
13
13
  } from "./chunk-U5VNEALI.js";
14
+ import {
15
+ PostgresJobStore
16
+ } from "./chunk-HHTL3ZFT.js";
14
17
  import {
15
18
  SIGNED_ENVELOPE_FIELDS,
16
19
  SignedRequestError,
@@ -6756,6 +6759,18 @@ async function handleCreditRequest(c, deps) {
6756
6759
  await deps.settleLightning(callerPubkey, body.credit_id);
6757
6760
  return handleDrain(c, deps, { callerPubkey, body, envelope });
6758
6761
  case "fund":
6762
+ if (deps.withFundingAdmission && body.fund) {
6763
+ const creditId = body.credit_id ?? derivedFundCreditId(callerPubkey, body.fund.fund_id);
6764
+ const [prior, invoice] = await Promise.all([
6765
+ deps.ledger.getFunding({ creditId, fundId: body.fund.fund_id }),
6766
+ body.fund.method === "lightning" ? deps.ledger.getInvoice({ creditId, fundId: body.fund.fund_id }) : Promise.resolve(void 0)
6767
+ ]);
6768
+ const priorCredit = prior ? await deps.ledger.get(creditId) : void 0;
6769
+ if (priorCredit?.callerPubkey === callerPubkey || invoice?.callerPubkey === callerPubkey) {
6770
+ return handleFund(c, deps, { callerPubkey, body, envelope, menu });
6771
+ }
6772
+ return deps.withFundingAdmission(c, body, () => handleFund(c, deps, { callerPubkey, body, envelope, menu }));
6773
+ }
6759
6774
  return handleFund(c, deps, { callerPubkey, body, envelope, menu });
6760
6775
  }
6761
6776
  }
@@ -9071,6 +9086,371 @@ function safeError(err) {
9071
9086
  return "settlement_transport_error";
9072
9087
  }
9073
9088
 
9089
+ // src/sdk/server/hosting-control.ts
9090
+ import { createHmac, timingSafeEqual } from "crypto";
9091
+ function signHostingControl(secret, command) {
9092
+ const signed = command.version === 2 ? [
9093
+ "dvmkit-hosting-v2",
9094
+ command.action,
9095
+ command.scope,
9096
+ command.dvmId,
9097
+ command.generation ?? null,
9098
+ command.leaseExpiresAtMs ?? null,
9099
+ command.deadlineMs ?? null,
9100
+ command.timestampMs,
9101
+ command.nonce
9102
+ ] : [
9103
+ "dvmkit-hosting-v1",
9104
+ command.action,
9105
+ command.scope,
9106
+ command.dvmId,
9107
+ command.generation ?? null,
9108
+ command.deadlineMs ?? null,
9109
+ command.timestampMs,
9110
+ command.nonce
9111
+ ];
9112
+ return createHmac("sha256", secret).update(JSON.stringify(signed)).digest("hex");
9113
+ }
9114
+ var HostingControl = class {
9115
+ constructor(pool, dvmId, config) {
9116
+ this.pool = pool;
9117
+ this.dvmId = dvmId;
9118
+ this.config = config;
9119
+ if (Buffer.byteLength(config.secret) < 32 || !config.scope) {
9120
+ throw new Error("Hosting control requires a 32-byte secret and a non-empty scope");
9121
+ }
9122
+ }
9123
+ pool;
9124
+ dvmId;
9125
+ config;
9126
+ async init() {
9127
+ await withSdkInitLock(this.pool, async () => {
9128
+ await this.pool.query(`
9129
+ CREATE TABLE IF NOT EXISTS sdk_hosting_control (
9130
+ dvm_id TEXT NOT NULL, scope TEXT NOT NULL, state TEXT NOT NULL,
9131
+ generation BIGINT NOT NULL, deadline_ms BIGINT, lease_expires_at_ms BIGINT,
9132
+ PRIMARY KEY (dvm_id, scope)
9133
+ );
9134
+ CREATE TABLE IF NOT EXISTS sdk_hosting_nonces (
9135
+ dvm_id TEXT NOT NULL, scope TEXT NOT NULL, nonce TEXT NOT NULL,
9136
+ created_at_ms BIGINT NOT NULL,
9137
+ PRIMARY KEY (dvm_id, scope, nonce)
9138
+ );
9139
+ CREATE TABLE IF NOT EXISTS sdk_hosting_admissions (
9140
+ id TEXT PRIMARY KEY, dvm_id TEXT NOT NULL, scope TEXT NOT NULL,
9141
+ job_id TEXT NOT NULL, admitted_at_ms BIGINT NOT NULL,
9142
+ outcome TEXT NOT NULL DEFAULT 'pending', drain_deadline_ms BIGINT,
9143
+ credit_id TEXT, fund_id TEXT, payment_artifact_hash TEXT
9144
+ );
9145
+ ALTER TABLE sdk_hosting_admissions ADD COLUMN IF NOT EXISTS drain_deadline_ms BIGINT;
9146
+ ALTER TABLE sdk_hosting_admissions ADD COLUMN IF NOT EXISTS credit_id TEXT;
9147
+ ALTER TABLE sdk_hosting_admissions ADD COLUMN IF NOT EXISTS fund_id TEXT;
9148
+ ALTER TABLE sdk_hosting_admissions ADD COLUMN IF NOT EXISTS payment_artifact_hash TEXT;
9149
+ ALTER TABLE sdk_hosting_control ADD COLUMN IF NOT EXISTS lease_expires_at_ms BIGINT;
9150
+ CREATE INDEX IF NOT EXISTS sdk_hosting_admissions_pending
9151
+ ON sdk_hosting_admissions (dvm_id, scope) WHERE outcome = 'pending';
9152
+ CREATE INDEX IF NOT EXISTS sdk_hosting_admissions_unresolved
9153
+ ON sdk_hosting_admissions (dvm_id, scope) WHERE outcome = 'unresolved';
9154
+ CREATE INDEX IF NOT EXISTS sdk_hosting_admissions_artifact
9155
+ ON sdk_hosting_admissions (dvm_id, scope, payment_artifact_hash)
9156
+ WHERE payment_artifact_hash IS NOT NULL;
9157
+ CREATE INDEX IF NOT EXISTS sdk_hosting_admissions_job
9158
+ ON sdk_hosting_admissions (dvm_id, job_id, scope);
9159
+ CREATE INDEX IF NOT EXISTS sdk_hosting_nonces_age ON sdk_hosting_nonces (created_at_ms);
9160
+ `);
9161
+ await this.pool.query(
9162
+ `INSERT INTO sdk_hosting_control (dvm_id, scope, state, generation)
9163
+ VALUES ($1, $2, 'draining', 0) ON CONFLICT DO NOTHING`,
9164
+ [this.dvmId, this.config.scope]
9165
+ );
9166
+ const client = await this.pool.connect();
9167
+ try {
9168
+ await client.query("BEGIN");
9169
+ const migrated = await client.query(
9170
+ `UPDATE sdk_hosting_control SET state='draining',
9171
+ deadline_ms=FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint + 3600000
9172
+ WHERE dvm_id=$1 AND scope=$2 AND state='accepting' AND lease_expires_at_ms IS NULL
9173
+ RETURNING deadline_ms`,
9174
+ [this.dvmId, this.config.scope]
9175
+ );
9176
+ if (migrated.rows[0]) await this.pinAdmissionDeadlines(client, Number(migrated.rows[0].deadline_ms));
9177
+ await client.query("COMMIT");
9178
+ } catch (error) {
9179
+ await client.query("ROLLBACK").catch(() => void 0);
9180
+ throw error;
9181
+ } finally {
9182
+ client.release();
9183
+ }
9184
+ });
9185
+ }
9186
+ async authorize(command, signature) {
9187
+ if (command.version !== void 0 && command.version !== 2 || command.version === 2 && command.action !== "resume" || command.version === void 0 && command.leaseExpiresAtMs !== void 0 || command.scope !== this.config.scope || command.dvmId !== this.dvmId || !Number.isSafeInteger(command.timestampMs) || Math.abs(Date.now() - command.timestampMs) > 3e5 || typeof command.nonce !== "string" || !/^[A-Za-z0-9_-]{16,128}$/.test(command.nonce) || !signature || !/^[0-9a-f]{64}$/i.test(signature)) return false;
9188
+ const actual = Buffer.from(signature, "hex");
9189
+ const expected = Buffer.from(signHostingControl(this.config.secret, command), "hex");
9190
+ if (!timingSafeEqual(actual, expected)) return false;
9191
+ const result = await this.pool.query(
9192
+ `INSERT INTO sdk_hosting_nonces (dvm_id, scope, nonce, created_at_ms)
9193
+ VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING`,
9194
+ [this.dvmId, this.config.scope, command.nonce, Date.now()]
9195
+ );
9196
+ await this.pool.query("DELETE FROM sdk_hosting_nonces WHERE created_at_ms < $1", [Date.now() - 6e5]);
9197
+ return result.rowCount === 1;
9198
+ }
9199
+ async admit(id, jobId, details) {
9200
+ const client = await this.pool.connect();
9201
+ try {
9202
+ await client.query("BEGIN");
9203
+ const { rows } = await client.query(
9204
+ `SELECT state, lease_expires_at_ms, deadline_ms FROM sdk_hosting_control WHERE dvm_id=$1 AND scope=$2 FOR UPDATE`,
9205
+ [this.dvmId, this.config.scope]
9206
+ );
9207
+ const now = await this.databaseNow(client);
9208
+ const current = rows[0];
9209
+ if (current) await this.expireIfNeeded(client, current, now);
9210
+ if (current?.state !== "accepting" || current.lease_expires_at_ms === null || Number(current.lease_expires_at_ms) <= now) {
9211
+ await client.query("COMMIT");
9212
+ return false;
9213
+ }
9214
+ await client.query(
9215
+ `INSERT INTO sdk_hosting_admissions
9216
+ (id, dvm_id, scope, job_id, admitted_at_ms, credit_id, fund_id, payment_artifact_hash)
9217
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
9218
+ [
9219
+ id,
9220
+ this.dvmId,
9221
+ this.config.scope,
9222
+ jobId,
9223
+ now,
9224
+ details?.creditId ?? null,
9225
+ details?.fundId ?? null,
9226
+ details?.paymentArtifactHash ?? null
9227
+ ]
9228
+ );
9229
+ await client.query("COMMIT");
9230
+ return true;
9231
+ } catch (error) {
9232
+ await client.query("ROLLBACK").catch(() => void 0);
9233
+ throw error;
9234
+ } finally {
9235
+ client.release();
9236
+ }
9237
+ }
9238
+ async acceptedJobByPaymentArtifactHash(hash2) {
9239
+ const { rows } = await this.pool.query(
9240
+ `SELECT a.job_id FROM sdk_hosting_admissions a
9241
+ JOIN jobs j ON j.id=a.job_id
9242
+ WHERE a.dvm_id=$1 AND a.scope=$2 AND a.payment_artifact_hash=$3
9243
+ AND a.outcome IN ('accepted', 'unresolved', 'pending')
9244
+ ORDER BY a.admitted_at_ms DESC LIMIT 1`,
9245
+ [this.dvmId, this.config.scope, hash2]
9246
+ );
9247
+ return rows[0]?.job_id;
9248
+ }
9249
+ async rebindAdmissionJob(id, jobId) {
9250
+ await this.pool.query(
9251
+ `UPDATE sdk_hosting_admissions SET job_id=$2 WHERE id=$1 AND outcome='pending'`,
9252
+ [id, jobId]
9253
+ );
9254
+ }
9255
+ async finishAdmission(id, outcome, jobId) {
9256
+ await this.pool.query(
9257
+ `UPDATE sdk_hosting_admissions SET outcome=$2, job_id=COALESCE($3, job_id)
9258
+ WHERE id=$1 AND outcome='pending'`,
9259
+ [id, outcome, jobId ?? null]
9260
+ );
9261
+ }
9262
+ async jobDeadline(jobId) {
9263
+ const { rows } = await this.pool.query(
9264
+ `SELECT MIN(drain_deadline_ms)::text AS drain_deadline_ms
9265
+ FROM sdk_hosting_admissions WHERE dvm_id=$1 AND scope=$2 AND job_id=$3`,
9266
+ [this.dvmId, this.config.scope, jobId]
9267
+ );
9268
+ return rows[0]?.drain_deadline_ms === null || !rows[0] ? null : Number(rows[0].drain_deadline_ms);
9269
+ }
9270
+ async dispatchDeadlineReached(jobId) {
9271
+ const { rows } = await this.pool.query(
9272
+ `SELECT EXISTS (
9273
+ SELECT 1 FROM sdk_hosting_admissions
9274
+ WHERE dvm_id=$1 AND scope=$2 AND job_id=$3
9275
+ AND drain_deadline_ms <= FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint
9276
+ ) OR EXISTS (
9277
+ SELECT 1 FROM sdk_hosting_control
9278
+ WHERE dvm_id=$1 AND scope=$2 AND state='draining'
9279
+ AND deadline_ms <= FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint
9280
+ ) AS reached`,
9281
+ [this.dvmId, this.config.scope, jobId]
9282
+ );
9283
+ return rows[0]?.reached ?? false;
9284
+ }
9285
+ async databaseNow(client) {
9286
+ const { rows } = await client.query(
9287
+ `SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint AS now_ms`
9288
+ );
9289
+ return Number(rows[0].now_ms);
9290
+ }
9291
+ async pinAdmissionDeadlines(client, deadlineMs) {
9292
+ await client.query(
9293
+ `UPDATE sdk_hosting_admissions SET drain_deadline_ms=
9294
+ CASE WHEN drain_deadline_ms IS NULL THEN $3 ELSE LEAST(drain_deadline_ms, $3) END
9295
+ WHERE dvm_id=$1 AND scope=$2 AND outcome <> 'failed'`,
9296
+ [this.dvmId, this.config.scope, deadlineMs]
9297
+ );
9298
+ }
9299
+ async expireIfNeeded(client, row, now) {
9300
+ if (row.state !== "accepting" || row.lease_expires_at_ms !== null && Number(row.lease_expires_at_ms) > now) return;
9301
+ const deadline = row.deadline_ms === null ? now + 36e5 : Number(row.deadline_ms);
9302
+ await client.query(
9303
+ `UPDATE sdk_hosting_control SET state='draining', deadline_ms=$3
9304
+ WHERE dvm_id=$1 AND scope=$2`,
9305
+ [this.dvmId, this.config.scope, deadline]
9306
+ );
9307
+ row.state = "draining";
9308
+ row.deadline_ms = String(deadline);
9309
+ await this.pinAdmissionDeadlines(client, deadline);
9310
+ }
9311
+ async transition(command) {
9312
+ if (command.action !== "drain" && command.action !== "resume" || !Number.isSafeInteger(command.generation) || command.generation < 1 || command.action === "drain" && (command.version !== void 0 || !Number.isSafeInteger(command.deadlineMs) || command.leaseExpiresAtMs !== void 0) || command.action === "resume" && (command.version !== 2 || !Number.isSafeInteger(command.leaseExpiresAtMs) || !Number.isSafeInteger(command.deadlineMs))) return "conflict";
9313
+ const client = await this.pool.connect();
9314
+ try {
9315
+ await client.query("BEGIN");
9316
+ const { rows } = await client.query(
9317
+ `SELECT state, generation, deadline_ms, lease_expires_at_ms FROM sdk_hosting_control
9318
+ WHERE dvm_id=$1 AND scope=$2 FOR UPDATE`,
9319
+ [this.dvmId, this.config.scope]
9320
+ );
9321
+ const prior = rows[0];
9322
+ if (!prior) throw new Error("Hosting control was not initialized");
9323
+ const now = await this.databaseNow(client);
9324
+ await this.expireIfNeeded(client, prior, now);
9325
+ const generation = Number(prior.generation);
9326
+ const nextState = command.action === "drain" ? "draining" : "accepting";
9327
+ if (command.generation === generation && prior.state === nextState && Number(prior.deadline_ms ?? 0) === (command.deadlineMs ?? 0) && (command.action !== "resume" || Number(prior.lease_expires_at_ms ?? 0) === command.leaseExpiresAtMs)) {
9328
+ await client.query("COMMIT");
9329
+ return "ok";
9330
+ }
9331
+ if (command.generation <= generation) {
9332
+ await client.query("ROLLBACK");
9333
+ return "conflict";
9334
+ }
9335
+ if (command.action === "drain" && (command.deadlineMs <= now || command.deadlineMs > now + 864e5)) {
9336
+ await client.query("ROLLBACK");
9337
+ return "conflict";
9338
+ }
9339
+ if (command.action === "resume" && (command.leaseExpiresAtMs <= now || command.leaseExpiresAtMs > now + 36e5 || command.deadlineMs <= command.leaseExpiresAtMs || command.deadlineMs > command.leaseExpiresAtMs + 36e5)) {
9340
+ await client.query("ROLLBACK");
9341
+ return "conflict";
9342
+ }
9343
+ await client.query(
9344
+ `UPDATE sdk_hosting_control SET state=$3, generation=$4, deadline_ms=$5,
9345
+ lease_expires_at_ms=$6
9346
+ WHERE dvm_id=$1 AND scope=$2`,
9347
+ [
9348
+ this.dvmId,
9349
+ this.config.scope,
9350
+ nextState,
9351
+ command.generation,
9352
+ command.deadlineMs ?? null,
9353
+ command.action === "resume" ? command.leaseExpiresAtMs : null
9354
+ ]
9355
+ );
9356
+ if (command.action === "drain") {
9357
+ await this.pinAdmissionDeadlines(client, command.deadlineMs);
9358
+ }
9359
+ await client.query("COMMIT");
9360
+ return "ok";
9361
+ } catch (error) {
9362
+ await client.query("ROLLBACK").catch(() => void 0);
9363
+ throw error;
9364
+ } finally {
9365
+ client.release();
9366
+ }
9367
+ }
9368
+ async status() {
9369
+ const client = await this.pool.connect();
9370
+ try {
9371
+ await client.query("BEGIN");
9372
+ const locked = await client.query(
9373
+ `SELECT state, deadline_ms, lease_expires_at_ms FROM sdk_hosting_control
9374
+ WHERE dvm_id=$1 AND scope=$2 FOR UPDATE`,
9375
+ [this.dvmId, this.config.scope]
9376
+ );
9377
+ if (locked.rows[0]) await this.expireIfNeeded(client, locked.rows[0], await this.databaseNow(client));
9378
+ await client.query("COMMIT");
9379
+ } catch (error) {
9380
+ await client.query("ROLLBACK").catch(() => void 0);
9381
+ throw error;
9382
+ } finally {
9383
+ client.release();
9384
+ }
9385
+ await this.pool.query(
9386
+ `UPDATE sdk_hosting_admissions a SET outcome='accepted'
9387
+ WHERE a.dvm_id=$1 AND a.scope=$2 AND a.outcome='pending'
9388
+ AND EXISTS (SELECT 1 FROM jobs j WHERE j.id=a.job_id)`,
9389
+ [this.dvmId, this.config.scope]
9390
+ );
9391
+ const creditTable = await this.pool.query(
9392
+ "SELECT to_regclass('credit_fundings') IS NOT NULL AS present"
9393
+ );
9394
+ await this.pool.query(
9395
+ `UPDATE sdk_hosting_admissions a SET outcome='accepted'
9396
+ WHERE a.dvm_id=$1 AND a.scope=$2 AND a.outcome='unresolved'
9397
+ AND (EXISTS (SELECT 1 FROM jobs j WHERE j.id=a.job_id)
9398
+ ${creditTable.rows[0].present ? `OR (a.credit_id IS NOT NULL AND EXISTS (
9399
+ SELECT 1 FROM credit_fundings f WHERE f.credit_id=a.credit_id AND f.fund_id=a.fund_id
9400
+ ))` : ""})`,
9401
+ [this.dvmId, this.config.scope]
9402
+ );
9403
+ const { rows } = await this.pool.query(
9404
+ `SELECT state, generation, deadline_ms, lease_expires_at_ms FROM sdk_hosting_control WHERE dvm_id=$1 AND scope=$2`,
9405
+ [this.dvmId, this.config.scope]
9406
+ );
9407
+ const active = await this.pool.query(
9408
+ `SELECT j.id FROM jobs j WHERE j.dvm_id=$1
9409
+ AND j.status IN ('processing', 'working', 'awaiting-input')
9410
+ AND EXISTS (SELECT 1 FROM sdk_hosting_admissions a
9411
+ WHERE a.dvm_id=j.dvm_id AND a.job_id=j.id AND a.scope=$2)
9412
+ ORDER BY j.id`,
9413
+ [this.dvmId, this.config.scope]
9414
+ );
9415
+ const unowned = await this.pool.query(
9416
+ `SELECT j.id FROM jobs j WHERE j.dvm_id=$1
9417
+ AND j.status IN ('processing', 'working', 'awaiting-input')
9418
+ AND NOT EXISTS (SELECT 1 FROM sdk_hosting_admissions a
9419
+ WHERE a.dvm_id=j.dvm_id AND a.job_id=j.id)
9420
+ ORDER BY j.id`,
9421
+ [this.dvmId]
9422
+ );
9423
+ const pending = await this.pool.query(
9424
+ `SELECT COUNT(*) FILTER (WHERE outcome='pending')::text AS pending,
9425
+ COUNT(*) FILTER (WHERE outcome='unresolved')::text AS unresolved
9426
+ FROM sdk_hosting_admissions WHERE dvm_id=$1 AND scope=$2`,
9427
+ [this.dvmId, this.config.scope]
9428
+ );
9429
+ const unresolved = await this.pool.query(
9430
+ `SELECT id, job_id FROM sdk_hosting_admissions
9431
+ WHERE dvm_id=$1 AND scope=$2 AND outcome='unresolved' ORDER BY admitted_at_ms, id`,
9432
+ [this.dvmId, this.config.scope]
9433
+ );
9434
+ const inFlight = await this.pool.query(
9435
+ `SELECT id, job_id FROM sdk_hosting_admissions
9436
+ WHERE dvm_id=$1 AND scope=$2 AND outcome='pending' ORDER BY admitted_at_ms, id`,
9437
+ [this.dvmId, this.config.scope]
9438
+ );
9439
+ return {
9440
+ state: rows[0].state,
9441
+ generation: Number(rows[0].generation),
9442
+ deadlineMs: rows[0].deadline_ms === null ? null : Number(rows[0].deadline_ms),
9443
+ leaseExpiresAtMs: rows[0].lease_expires_at_ms === null ? null : Number(rows[0].lease_expires_at_ms),
9444
+ activeJobIds: active.rows.map((row) => row.id),
9445
+ unownedActiveJobIds: unowned.rows.map((row) => row.id),
9446
+ pendingAdmissionCount: Number(pending.rows[0].pending),
9447
+ pendingAdmissions: inFlight.rows.map((row) => ({ admissionId: row.id, workId: row.job_id })),
9448
+ unresolvedAdmissionCount: Number(pending.rows[0].unresolved),
9449
+ unresolvedAdmissions: unresolved.rows.map((row) => ({ admissionId: row.id, jobId: row.job_id }))
9450
+ };
9451
+ }
9452
+ };
9453
+
9074
9454
  // src/sdk/server/job-credential.ts
9075
9455
  function jobCredentialGate(opts) {
9076
9456
  const alternative = opts.acceptsBearer ? ", or the Authorization: Bearer token the job was submitted with" : "";
@@ -16730,6 +17110,7 @@ async function createDVMServer(descriptor, opts) {
16730
17110
  let x402ChannelStorage;
16731
17111
  let x402BatchSettlement;
16732
17112
  let server;
17113
+ let hostingControl;
16733
17114
  let shutdownPromise;
16734
17115
  const shutdown = () => shutdownPromise ??= (async () => {
16735
17116
  const errors = [];
@@ -16923,8 +17304,19 @@ async function createDVMServer(descriptor, opts) {
16923
17304
  });
16924
17305
  }
16925
17306
  }) : void 0;
17307
+ if (opts.hostingControl) {
17308
+ if (!opts.db || !opts.dvmId || !(opts.jobStore instanceof PostgresJobStore)) {
17309
+ throw new Error("Hosting control requires a Postgres pool, dvmId, and PostgresJobStore");
17310
+ }
17311
+ if (descriptor.credit && !opts.creditLedger?.durable) {
17312
+ throw new Error("Hosting control with credit requires a durable credit ledger");
17313
+ }
17314
+ hostingControl = new HostingControl(opts.db, opts.dvmId, opts.hostingControl);
17315
+ await hostingControl.init();
17316
+ }
16926
17317
  server = new DVMServer(descriptor, {
16927
17318
  ...opts,
17319
+ hostingControlInstance: hostingControl,
16928
17320
  creditLedger,
16929
17321
  processedPayments,
16930
17322
  onJobCompleted: effectiveOnJobCompleted,
@@ -17001,6 +17393,7 @@ var DVMServer = class {
17001
17393
  x402ExactSettlement;
17002
17394
  /** Withdraws this mount's Tempo session capability from the poll. */
17003
17395
  undeclareSessionCapability;
17396
+ hostingSweepTimer;
17004
17397
  constructor(descriptor, opts) {
17005
17398
  this.descriptor = descriptor;
17006
17399
  this.pricingCurrency = descriptor.currency ?? "usd";
@@ -17084,6 +17477,17 @@ var DVMServer = class {
17084
17477
  this.app = new Hono();
17085
17478
  try {
17086
17479
  this.registerRoutes();
17480
+ if (opts.hostingControlInstance) {
17481
+ this.hostingSweepTimer = setInterval(() => {
17482
+ void this.sweepHostingDeadline().catch((error) => {
17483
+ console.error("Hosting deadline sweep failed:", error);
17484
+ });
17485
+ }, 1e3);
17486
+ this.hostingSweepTimer.unref();
17487
+ void this.sweepHostingDeadline().catch((error) => {
17488
+ console.error("Hosting deadline sweep failed:", error);
17489
+ });
17490
+ }
17087
17491
  if (this.ownsMintHealthTracker) this.mintHealthTracker?.start();
17088
17492
  if (this.opts.tempoObserverHealth?.declareSessionCapability) {
17089
17493
  this.undeclareSessionCapability = this.opts.tempoObserverHealth.declareSessionCapability(
@@ -17117,6 +17521,7 @@ var DVMServer = class {
17117
17521
  * and host teardown layers can finish later cleanup before surfacing it.
17118
17522
  */
17119
17523
  shutdown() {
17524
+ if (this.hostingSweepTimer) clearInterval(this.hostingSweepTimer);
17120
17525
  this.jobManager.shutdown();
17121
17526
  const settlement = this.x402BatchSettlementInit ?? Promise.resolve(this.x402BatchSettlement);
17122
17527
  const stopped = settlement.then((s) => s?.stop());
@@ -17125,6 +17530,40 @@ var DVMServer = class {
17125
17530
  this.undeclareSessionCapability = void 0;
17126
17531
  return stopped;
17127
17532
  }
17533
+ async sweepHostingDeadline() {
17534
+ const control = this.opts.hostingControlInstance;
17535
+ if (!control) return;
17536
+ const status = await control.status();
17537
+ const store = this.jobManager.store;
17538
+ if (!(store instanceof PostgresJobStore)) return;
17539
+ for (const jobId of status.activeJobIds) {
17540
+ if (await control.dispatchDeadlineReached(jobId) && await store.cancelJobForHosting(jobId, "Hosting drain deadline reached")) {
17541
+ await this.jobManager.finalizeStoredTerminal(jobId);
17542
+ }
17543
+ }
17544
+ }
17545
+ async handleHostingCommand(c, statusOnly) {
17546
+ const control = this.opts.hostingControlInstance;
17547
+ if (!control) return c.json({ error: "not_found" }, 404);
17548
+ let command;
17549
+ try {
17550
+ command = await c.req.json();
17551
+ } catch {
17552
+ return c.json({ error: "invalid_hosting_control" }, 400);
17553
+ }
17554
+ if (!command || (statusOnly ? command.action !== "status" : command.action !== "drain" && command.action !== "resume")) {
17555
+ return c.json({ error: "invalid_hosting_control" }, 400);
17556
+ }
17557
+ if (!await control.authorize(command, c.req.header("X-DVM-Hosting-Signature"))) {
17558
+ return c.json({ error: "hosting_control_unauthorized" }, 401);
17559
+ }
17560
+ if (!statusOnly && command.action === "resume") await this.sweepHostingDeadline();
17561
+ if (!statusOnly && await control.transition(command) === "conflict") {
17562
+ return c.json({ error: "hosting_generation_conflict" }, 409);
17563
+ }
17564
+ if (!statusOnly) await this.sweepHostingDeadline();
17565
+ return c.json(await control.status());
17566
+ }
17128
17567
  registerRoutes() {
17129
17568
  const dvmSlug = this.descriptor.name;
17130
17569
  const builderPubkey = this.opts.builder?.pubkey;
@@ -17232,6 +17671,10 @@ var DVMServer = class {
17232
17671
  this.app.get("/v1/info", (c) => this.handleInfo(c));
17233
17672
  this.app.post("/v1/quote", (c) => this.handleQuote(c));
17234
17673
  this.app.post("/v1/job", (c) => this.handleCreateJob(c));
17674
+ if (this.opts.hostingControlInstance) {
17675
+ this.app.post("/_hosting/control", (c) => this.handleHostingCommand(c, false));
17676
+ this.app.post("/_hosting/status", (c) => this.handleHostingCommand(c, true));
17677
+ }
17235
17678
  this.app.post("/v1/request-resume/:requestId", (c) => this.handleCreateJob(c, true));
17236
17679
  this.app.post("/v1/request-result/:requestId", (c) => this.handleRequestResult(c));
17237
17680
  this.app.get("/v1/job/:id", (c) => this.handleGetJob(c));
@@ -17850,6 +18293,32 @@ var DVMServer = class {
17850
18293
  receiptIssuer: this.receiptIssuer,
17851
18294
  lightning: this.opts.lightningReceive ? { receive: this.opts.lightningReceive } : void 0,
17852
18295
  settleLightning: (callerPubkey, creditId) => this.settleLightningFunding(callerPubkey, creditId),
18296
+ withFundingAdmission: this.opts.hostingControlInstance ? async (ctx, body, run) => {
18297
+ const control = this.opts.hostingControlInstance;
18298
+ const admissionId = randomUUID4();
18299
+ const fundId = body.fund?.fund_id ?? admissionId;
18300
+ const creditId = body.credit_id ?? derivedFundCreditId(body.pubkey, fundId);
18301
+ const workId = `credit-fund:${creditId}:${fundId}`;
18302
+ if (!await control.admit(admissionId, workId, { creditId, fundId })) {
18303
+ return ctx.json({
18304
+ error: "hosting_draining",
18305
+ message: "This service is draining and is not accepting new credit funding.",
18306
+ hint: "Balance, reclaim, and already-issued funding recovery remain available."
18307
+ }, 503);
18308
+ }
18309
+ const authorization = ctx.req.header("Authorization");
18310
+ const paymentArtifact = Boolean(
18311
+ ctx.req.header("X-Cashu") || ctx.req.header("PAYMENT-SIGNATURE") || ctx.req.header("X-PAYMENT") || authorization && /^Payment\s/i.test(authorization)
18312
+ );
18313
+ let outcome = paymentArtifact ? "unresolved" : "failed";
18314
+ try {
18315
+ const response = await run();
18316
+ if (response.ok || body.fund?.method === "lightning" && await this.creditLedger.getInvoice({ creditId, fundId })) outcome = "accepted";
18317
+ return response;
18318
+ } finally {
18319
+ await control.finishAdmission(admissionId, outcome);
18320
+ }
18321
+ } : void 0,
17853
18322
  tempoSettlementReadiness: this.opts.tempoSettlementReadiness
17854
18323
  });
17855
18324
  }
@@ -18514,8 +18983,40 @@ var DVMServer = class {
18514
18983
  { capability: capabilityName, input: body.input, params: body.params, data: body.data },
18515
18984
  { stripAuthEnvelope: false }
18516
18985
  ) : void 0;
18986
+ const authorization = c.req.header("Authorization");
18987
+ const attachedPayment = c.req.header("X-Cashu") ?? c.req.header("PAYMENT-SIGNATURE") ?? c.req.header("X-PAYMENT") ?? (authorization && /^Payment\s/i.test(authorization) ? authorization : void 0);
18988
+ const hostingPaymentArtifactHash = attachedPayment ? createHash7("sha256").update(attachedPayment).digest("hex") : void 0;
18989
+ if (!resumeOnly && this.opts.hostingControlInstance && hostingPaymentArtifactHash) {
18990
+ const priorId = await this.opts.hostingControlInstance.acceptedJobByPaymentArtifactHash(
18991
+ hostingPaymentArtifactHash
18992
+ );
18993
+ const prior = priorId ? await this.jobManager.store.get(priorId) : void 0;
18994
+ if (prior && prior.requestFingerprint === fingerprint && prior.requesterId === c.get("requesterId") && (!this.descriptor.auth || prior.requesterPubkey === authPubkey)) {
18995
+ await this.jobManager.ensureReceipt(prior);
18996
+ return buildSubmitResponse(c, prior, prior.requesterToken);
18997
+ }
18998
+ }
18999
+ if (!resumeOnly && this.opts.hostingControlInstance && creditEnvelope && authPubkey) {
19000
+ const draw = await this.creditLedger.getDraw({
19001
+ creditId: creditEnvelope.creditId,
19002
+ drawId: creditEnvelope.drawId
19003
+ });
19004
+ if (draw?.jobId && await this.jobManager.store.get(draw.jobId)) {
19005
+ const recovered = await this.recoverDrawnJob(c, {
19006
+ creditId: creditEnvelope.creditId,
19007
+ drawId: creditEnvelope.drawId,
19008
+ jobId: draw.jobId,
19009
+ fingerprint,
19010
+ callerPubkey: authPubkey
19011
+ });
19012
+ if (recovered) return recovered;
19013
+ }
19014
+ }
18517
19015
  let publicRequestClaim;
18518
19016
  let publicRequestClaimResult;
19017
+ let hostingAdmissionId;
19018
+ let hostingAdmissionAccepted = false;
19019
+ let hostingPaymentMayHaveLanded = false;
18519
19020
  if (publicRequestId && authPubkey && recoveryStore) {
18520
19021
  publicRequestClaim = {
18521
19022
  requestId: publicRequestId,
@@ -18538,6 +19039,18 @@ var DVMServer = class {
18538
19039
  }
18539
19040
  }
18540
19041
  let jobId = publicRequestClaimResult?.jobId ?? this.jobManager.allocateJobId();
19042
+ if (!resumeOnly && this.opts.hostingControlInstance) {
19043
+ hostingAdmissionId = randomUUID4();
19044
+ if (!await this.opts.hostingControlInstance.admit(hostingAdmissionId, jobId, {
19045
+ paymentArtifactHash: hostingPaymentArtifactHash
19046
+ })) {
19047
+ return c.json({
19048
+ error: "hosting_draining",
19049
+ message: "This service is draining and is not accepting new jobs or payments.",
19050
+ hint: "Keep any existing job token to fetch its result or reclaim unused credit. Retry a new job after service resumes."
19051
+ }, 503);
19052
+ }
19053
+ }
18541
19054
  let payment = { paidMsats: 0, receivedProofs: [] };
18542
19055
  let resumedAcceptedPayment = false;
18543
19056
  if (resumeOnly) {
@@ -18594,6 +19107,9 @@ var DVMServer = class {
18594
19107
  const skipPayment = devModeSkipsPaymentVerification(this.opts);
18595
19108
  if (!skipPayment && !resumedAcceptedPayment) {
18596
19109
  const railOpts = await this.railVerifyOpts(c);
19110
+ hostingPaymentMayHaveLanded = Boolean(
19111
+ creditEnvelope || railOpts.cashuToken || railOpts.x402Payment || railOpts.mppCredential
19112
+ );
18597
19113
  try {
18598
19114
  payment = await loggers.isolate.span(
18599
19115
  "dvm.payment.validate",
@@ -18707,8 +19223,12 @@ var DVMServer = class {
18707
19223
  );
18708
19224
  }
18709
19225
  const exactAdmission = payment.x402ExactAdmission;
19226
+ hostingPaymentMayHaveLanded ||= resumedAcceptedPayment || payment.paidMsats > 0 || Boolean(payment.funding || exactAdmission);
18710
19227
  if (exactAdmission) {
18711
19228
  jobId = exactAdmission.jobId;
19229
+ if (hostingAdmissionId) {
19230
+ await this.opts.hostingControlInstance.rebindAdmissionJob(hostingAdmissionId, jobId);
19231
+ }
18712
19232
  const prior = await this.jobManager.store.get(jobId);
18713
19233
  if (prior) {
18714
19234
  if (prior.requestFingerprint !== fingerprint || prior.requesterPubkey !== authPubkey || prior.requesterId !== c.get("requesterId")) {
@@ -18769,6 +19289,19 @@ var DVMServer = class {
18769
19289
  }
18770
19290
  );
18771
19291
  await jm.persistJob(job);
19292
+ if (hostingAdmissionId) {
19293
+ await this.opts.hostingControlInstance.finishAdmission(hostingAdmissionId, "accepted", jobId);
19294
+ hostingAdmissionAccepted = true;
19295
+ }
19296
+ await this.opts.hostingControlInstance?.status();
19297
+ if (this.opts.hostingControlInstance && await this.opts.hostingControlInstance.dispatchDeadlineReached(jobId)) {
19298
+ const store = jm.store;
19299
+ if (store instanceof PostgresJobStore && await store.cancelJobForHosting(jobId, "Hosting drain deadline reached")) {
19300
+ await jm.finalizeStoredTerminal(jobId);
19301
+ }
19302
+ const terminal = await store.get(jobId);
19303
+ if (terminal) return buildSubmitResponse(c, terminal, jobToken);
19304
+ }
18772
19305
  if (exactAdmission) {
18773
19306
  await this.x402ExactSettlement?.completeAdmission(
18774
19307
  this.opts.dvmId ?? "",
@@ -18790,6 +19323,12 @@ var DVMServer = class {
18790
19323
  }
18791
19324
  return response;
18792
19325
  } finally {
19326
+ if (hostingAdmissionId && !hostingAdmissionAccepted) {
19327
+ await this.opts.hostingControlInstance.finishAdmission(
19328
+ hostingAdmissionId,
19329
+ hostingPaymentMayHaveLanded ? "unresolved" : "failed"
19330
+ );
19331
+ }
18793
19332
  if (publicRequestClaim && recoveryStore) {
18794
19333
  await recoveryStore.releaseRequestIdClaim(publicRequestClaim);
18795
19334
  }
@@ -19921,6 +20460,7 @@ export {
19921
20460
  createAdminCashuNonceStore,
19922
20461
  X402ExactSettlementServer,
19923
20462
  X402ExactSettlementEvidenceMissingError,
20463
+ signHostingControl,
19924
20464
  jobCredentialGate,
19925
20465
  REQUEST_ID_FIELD,
19926
20466
  protocolEnvelopeIgnoreFields,
@@ -1053,10 +1053,13 @@ var PostgresJobStore = class {
1053
1053
  );
1054
1054
  }
1055
1055
  cancelStaleJob(jobId, expectedActivityBefore, reason, terminalStatus) {
1056
- return this.terminateJob(jobId, reason, terminalStatus, expectedActivityBefore);
1056
+ return this.terminateJob(jobId, reason, terminalStatus, expectedActivityBefore, null);
1057
1057
  }
1058
1058
  cancelJob(jobId, reason) {
1059
- return this.terminateJob(jobId, reason, "cancelled", null);
1059
+ return this.terminateJob(jobId, reason, "cancelled", null, "caller");
1060
+ }
1061
+ cancelJobForHosting(jobId, reason) {
1062
+ return this.terminateJob(jobId, reason, "cancelled", null, "provider");
1060
1063
  }
1061
1064
  async getCounters(jobId) {
1062
1065
  const { rows } = await this.pool.query(
@@ -1209,7 +1212,7 @@ var PostgresJobStore = class {
1209
1212
  * machine running the handler wakes as soon as the terminal status is
1210
1213
  * visible.
1211
1214
  */
1212
- async terminateJob(jobId, reason, terminalStatus, expectedActivityBefore) {
1215
+ async terminateJob(jobId, reason, terminalStatus, expectedActivityBefore, endedBy) {
1213
1216
  const staleOnly = expectedActivityBefore !== null;
1214
1217
  const client = await this.pool.connect();
1215
1218
  try {
@@ -1241,7 +1244,7 @@ var PostgresJobStore = class {
1241
1244
  );
1242
1245
  await client.query(
1243
1246
  `UPDATE jobs SET
1244
- ended_by = CASE WHEN $7::boolean OR status = 'awaiting-input' THEN 'caller' ELSE 'provider' END,
1247
+ ended_by = COALESCE($7::text, CASE WHEN status = 'awaiting-input' THEN 'caller' ELSE 'provider' END),
1245
1248
  terminal_at_ms = $5,
1246
1249
  caller_wait_ms = caller_wait_ms + CASE WHEN status = 'awaiting-input'
1247
1250
  THEN GREATEST(0, $5 - COALESCE(awaiting_input_at_ms, last_activity_at)) ELSE 0 END,
@@ -1253,7 +1256,7 @@ var PostgresJobStore = class {
1253
1256
  next_seq = $4 + 1,
1254
1257
  last_activity_at = $5
1255
1258
  WHERE id = $1`,
1256
- [jobId, reason, JSON.stringify([cancelMsg]), nextSeq, now, terminalStatus, !staleOnly]
1259
+ [jobId, reason, JSON.stringify([cancelMsg]), nextSeq, now, terminalStatus, endedBy]
1257
1260
  );
1258
1261
  await client.query("SELECT pg_notify('job_messages', $1)", [jobId]);
1259
1262
  await client.query("COMMIT");
@@ -181,6 +181,45 @@ interface CreditExpirySweepOpts {
181
181
  limit?: number;
182
182
  }
183
183
 
184
+ interface HostingControlConfig {
185
+ /** Dedicated management key. Use at least 32 random bytes; never reuse a caller or builder key. */
186
+ secret: string;
187
+ /** Deployment scope shared by the platform and this DVM, independent of its caller origin. */
188
+ scope: string;
189
+ }
190
+ interface HostingControlCommand {
191
+ /** Version two is required for resume; version one remains valid for drain and status. */
192
+ version?: 2;
193
+ action: "drain" | "resume" | "status";
194
+ scope: string;
195
+ dvmId: string;
196
+ timestampMs: number;
197
+ nonce: string;
198
+ generation?: number;
199
+ deadlineMs?: number;
200
+ leaseExpiresAtMs?: number;
201
+ }
202
+ /** Ordered JSON arrays are the versioned wire signing contract. */
203
+ declare function signHostingControl(secret: string, command: HostingControlCommand): string;
204
+ interface HostingControlStatus {
205
+ state: "accepting" | "draining";
206
+ generation: number;
207
+ deadlineMs: number | null;
208
+ leaseExpiresAtMs: number | null;
209
+ activeJobIds: string[];
210
+ unownedActiveJobIds: string[];
211
+ pendingAdmissionCount: number;
212
+ pendingAdmissions: {
213
+ admissionId: string;
214
+ workId: string;
215
+ }[];
216
+ unresolvedAdmissionCount: number;
217
+ unresolvedAdmissions: {
218
+ admissionId: string;
219
+ jobId: string;
220
+ }[];
221
+ }
222
+
184
223
  interface PromptResolver {
185
224
  resolve: (value: ResponseContent) => void;
186
225
  reject: (reason: Error) => void;
@@ -3676,6 +3715,8 @@ interface DVMServeResult {
3676
3715
  paymentModes: PaymentMode[];
3677
3716
  }
3678
3717
  interface DVMHostOpts {
3718
+ /** Dedicated hosting-management authority for each mounted DVM. Requires durable Postgres. */
3719
+ hostingControl?: HostingControlConfig;
3679
3720
  /**
3680
3721
  * Postgres connection string. Defaults to `DATABASE_URL` env.
3681
3722
  * Required at runtime. Boot fails when missing unless `jobStore`
@@ -4054,6 +4095,8 @@ interface SDKServerOpts {
4054
4095
  canonicalDvmId?: string;
4055
4096
  /** Postgres pool for builder admin state and wallet accumulator persistence. */
4056
4097
  db?: Pool;
4098
+ /** Enables authenticated, durable hosting admission and bounded drain. Requires Postgres and dvmId. */
4099
+ hostingControl?: HostingControlConfig;
4057
4100
  /**
4058
4101
  * Host strict-mode flag. Reporter readiness follows hosted context regardless
4059
4102
  * of this value.
@@ -4370,4 +4413,4 @@ declare function attachCreditMenu(body: Record<string, unknown>, menu: CreditMen
4370
4413
  */
4371
4414
  declare function toCreditTerms(menu: CreditMenu): CreditTerms;
4372
4415
 
4373
- export { isTerminal as $, type AccumulatorPool as A, buildPaymentErrorResponse as B, type ConsumedCredentialStore as C, clearAccumulatorForDvm as D, createDVMServer as E, type FiatDenomination as F, creditDepositPayload as G, derivedFundCreditId as H, type IncomingPaymentOpts as I, JobCancelledError as J, devModeSkipsPaymentVerification as K, fromJobRecord as L, MintHealthTracker as M, fundedMicroFor as N, hasPaymentProof as O, PAYMENT_PROOF_KEYS as P, hashLockKey as Q, type ReporterBannerOpts as R, type SDKServerOpts as S, type TempoChannelReport as T, type UpfrontPaymentOpts as U, type VerifiedIncomingPayment as V, implicitCreditId as W, type X402BatchChannelObservation as X, initWalletAccumulatorTable as Y, insertAccumulatorRows as Z, isDerivedCreditId as _, type AccumulatorQuerier as a, type X402ExactSettlementAttempt as a$, isYieldMessage as a0, issueUpfrontChallenges as a1, msatsToFiatMicro as a2, paymentErrorBody as a3, pinAskFiat as a4, priceFiatMicro as a5, processIncomingPayment as a6, providerMessage as a7, repairX402ExactSettlementEffect as a8, resolveFxSnapshot as a9, JobManager as aA, type JobManagerOpts as aB, type LightningReceiveConfig as aC, MIN_INVOICE_TTL_SECONDS as aD, MemoryConsumedCredentialStore as aE, type MemoryConsumedCredentialStoreOpts as aF, MemoryProcessedPaymentStore as aG, MemoryX402ExactSettlementStore as aH, type MountOpts as aI, type OwnerDisplay as aJ, type PaidJobCompletion as aK, type PaymentMode as aL, type PlatformReporterOpts as aM, PostgresProcessedPaymentStore as aN, PostgresX402ExactSettlementStore as aO, type PriceFiat as aP, type ProcessedPaymentQuerier as aQ, type ProcessedPaymentRail as aR, type ProcessedPaymentRecord as aS, ProcessedPaymentReplayError as aT, type ProcessedPaymentStore as aU, type X402BatchAcceptance as aV, type X402BatchFunding as aW, type X402BatchRefusal as aX, type X402BatchSettlementServer as aY, type X402ExactAcceptance as aZ, X402ExactIntentConflictError as a_, resolvePriceFiat as aa, revenueReporterBannerState as ab, toCreditTerms as ac, toJobRecord as ad, unknownRouteNotFound as ae, verifyIncomingPayment as af, verifyTempoSessionManagementCredential as ag, verifyUpfrontPayment as ah, x402RequiredUsdcMicro as ai, x402SettledShare as aj, type CreditMenu as ak, ReceiptIssuer as al, LightningReceive as am, TempoSettlementReadiness as an, type DVMHostOpts as ao, type DVMServeResult as ap, type BuildCreditMenuArgs as aq, type BuilderIdentity as ar, CREDIT_ENVELOPE_KEYS as as, type CreateX402BatchSettlementServerOpts as at, type CreditEnvelope as au, CreditEnvelopeError as av, type CreditFundCommitment as aw, type CreditTerms as ax, DEFAULT_INVOICE_TTL_SECONDS as ay, type DVMHost as az, type AppEnv as b, type X402ExactSettlementChainEvidence as b0, type X402ExactSettlementEffect as b1, X402ExactSettlementEvidenceMissingError as b2, type X402ExactSettlementEvidenceReader as b3, type X402ExactSettlementIntent as b4, X402ExactSettlementNotReadyError as b5, X402ExactSettlementServer as b6, type X402ExactSettlementServerOpts as b7, type X402ExactSettlementStatus as b8, type X402ExactSettlementStore as b9, type X402SettlementChainEvidence as ba, type X402SettlementEvidenceReader as bb, X402SettlementSubmissionError as bc, X402_BATCH_AUTO_SETTLEMENT as bd, X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS as be, X402_BATCH_SETTLEMENT_NETWORK as bf, attachCreditMenu as bg, buildCreditMenu as bh, createDVMHost as bi, createX402BatchSettlementServer as bj, creditEnvelopeIgnoreFields as bk, drawSettlementRef as bl, extractCreditEnvelope as bm, fundingCommitment as bn, selectPrimaryCredit as bo, stripCreditEnvelope as bp, toCreditView as bq, type CreditFundingReport as c, type FiatDenominationFailure as d, type FundOnlyRequest as e, type PaymentErrorCode as f, type PaymentErrorDetail as g, type PaymentInfo as h, type ResolvedFx as i, type RevenueBootCheckOpts as j, type ServerJob as k, type ShortPayForfeit as l, type TempoObserverHealth as m, TempoSessionChannelMismatchError as n, type VerifyIncomingSnapshot as o, X402FacilitatorHealth as p, type X402SettlementEvidenceOutcome as q, type X402SettlementRepair as r, type X402SettlementRepairRefusal as s, type X402SettlementRepaired as t, type X402WedgedSettlement as u, type X402WedgedSettlementPage as v, X402_BATCH_SETTLEMENT_MAINNET_NETWORK as w, abortJob as x, applyPaymentInfoToJob as y, assertRevenueReporterReady as z };
4416
+ export { isTerminal as $, type AccumulatorPool as A, buildPaymentErrorResponse as B, type ConsumedCredentialStore as C, clearAccumulatorForDvm as D, createDVMServer as E, type FiatDenomination as F, creditDepositPayload as G, derivedFundCreditId as H, type IncomingPaymentOpts as I, JobCancelledError as J, devModeSkipsPaymentVerification as K, fromJobRecord as L, MintHealthTracker as M, fundedMicroFor as N, hasPaymentProof as O, PAYMENT_PROOF_KEYS as P, hashLockKey as Q, type ReporterBannerOpts as R, type SDKServerOpts as S, type TempoChannelReport as T, type UpfrontPaymentOpts as U, type VerifiedIncomingPayment as V, implicitCreditId as W, type X402BatchChannelObservation as X, initWalletAccumulatorTable as Y, insertAccumulatorRows as Z, isDerivedCreditId as _, type AccumulatorQuerier as a, type X402BatchSettlementServer as a$, isYieldMessage as a0, issueUpfrontChallenges as a1, msatsToFiatMicro as a2, paymentErrorBody as a3, pinAskFiat as a4, priceFiatMicro as a5, processIncomingPayment as a6, providerMessage as a7, repairX402ExactSettlementEffect as a8, resolveFxSnapshot as a9, type HostingControlCommand as aA, type HostingControlConfig as aB, type HostingControlStatus as aC, JobManager as aD, type JobManagerOpts as aE, type LightningReceiveConfig as aF, MIN_INVOICE_TTL_SECONDS as aG, MemoryConsumedCredentialStore as aH, type MemoryConsumedCredentialStoreOpts as aI, MemoryProcessedPaymentStore as aJ, MemoryX402ExactSettlementStore as aK, type MountOpts as aL, type OwnerDisplay as aM, type PaidJobCompletion as aN, type PaymentMode as aO, type PlatformReporterOpts as aP, PostgresProcessedPaymentStore as aQ, PostgresX402ExactSettlementStore as aR, type PriceFiat as aS, type ProcessedPaymentQuerier as aT, type ProcessedPaymentRail as aU, type ProcessedPaymentRecord as aV, ProcessedPaymentReplayError as aW, type ProcessedPaymentStore as aX, type X402BatchAcceptance as aY, type X402BatchFunding as aZ, type X402BatchRefusal as a_, resolvePriceFiat as aa, revenueReporterBannerState as ab, toCreditTerms as ac, toJobRecord as ad, unknownRouteNotFound as ae, verifyIncomingPayment as af, verifyTempoSessionManagementCredential as ag, verifyUpfrontPayment as ah, x402RequiredUsdcMicro as ai, x402SettledShare as aj, type CreditMenu as ak, ReceiptIssuer as al, LightningReceive as am, TempoSettlementReadiness as an, type DVMHostOpts as ao, type DVMServeResult as ap, type BuildCreditMenuArgs as aq, type BuilderIdentity as ar, CREDIT_ENVELOPE_KEYS as as, type CreateX402BatchSettlementServerOpts as at, type CreditEnvelope as au, CreditEnvelopeError as av, type CreditFundCommitment as aw, type CreditTerms as ax, DEFAULT_INVOICE_TTL_SECONDS as ay, type DVMHost as az, type AppEnv as b, type X402ExactAcceptance as b0, X402ExactIntentConflictError as b1, type X402ExactSettlementAttempt as b2, type X402ExactSettlementChainEvidence as b3, type X402ExactSettlementEffect as b4, X402ExactSettlementEvidenceMissingError as b5, type X402ExactSettlementEvidenceReader as b6, type X402ExactSettlementIntent as b7, X402ExactSettlementNotReadyError as b8, X402ExactSettlementServer as b9, type X402ExactSettlementServerOpts as ba, type X402ExactSettlementStatus as bb, type X402ExactSettlementStore as bc, type X402SettlementChainEvidence as bd, type X402SettlementEvidenceReader as be, X402SettlementSubmissionError as bf, X402_BATCH_AUTO_SETTLEMENT as bg, X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS as bh, X402_BATCH_SETTLEMENT_NETWORK as bi, attachCreditMenu as bj, buildCreditMenu as bk, createDVMHost as bl, createX402BatchSettlementServer as bm, creditEnvelopeIgnoreFields as bn, drawSettlementRef as bo, extractCreditEnvelope as bp, fundingCommitment as bq, selectPrimaryCredit as br, signHostingControl as bs, stripCreditEnvelope as bt, toCreditView as bu, type CreditFundingReport as c, type FiatDenominationFailure as d, type FundOnlyRequest as e, type PaymentErrorCode as f, type PaymentErrorDetail as g, type PaymentInfo as h, type ResolvedFx as i, type RevenueBootCheckOpts as j, type ServerJob as k, type ShortPayForfeit as l, type TempoObserverHealth as m, TempoSessionChannelMismatchError as n, type VerifyIncomingSnapshot as o, X402FacilitatorHealth as p, type X402SettlementEvidenceOutcome as q, type X402SettlementRepair as r, type X402SettlementRepairRefusal as s, type X402SettlementRepaired as t, type X402WedgedSettlement as u, type X402WedgedSettlementPage as v, X402_BATCH_SETTLEMENT_MAINNET_NETWORK as w, abortJob as x, applyPaymentInfoToJob as y, assertRevenueReporterReady as z };
@@ -6,8 +6,8 @@ import { Env, BlankSchema } from 'hono/types';
6
6
  import { Pool } from 'pg';
7
7
  export { Pool } from 'pg';
8
8
  import { g as getWallet } from '../wallet-Dwjs5n_M.js';
9
- import { A as AccumulatorPool, a as AccumulatorQuerier, C as ConsumedCredentialStore } from '../credit-menu-kDA9RVzD.js';
10
- export { b as AppEnv, c as CreditFundingReport, F as FiatDenomination, d as FiatDenominationFailure, e as FundOnlyRequest, I as IncomingPaymentOpts, J as JobCancelledError, M as MintHealthTracker, P as PAYMENT_PROOF_KEYS, f as PaymentErrorCode, g as PaymentErrorDetail, h as PaymentInfo, R as ReporterBannerOpts, i as ResolvedFx, j as RevenueBootCheckOpts, S as SDKServerOpts, k as ServerJob, l as ShortPayForfeit, T as TempoChannelReport, m as TempoObserverHealth, n as TempoSessionChannelMismatchError, U as UpfrontPaymentOpts, V as VerifiedIncomingPayment, o as VerifyIncomingSnapshot, X as X402BatchChannelObservation, p as X402FacilitatorHealth, q as X402SettlementEvidenceOutcome, r as X402SettlementRepair, s as X402SettlementRepairRefusal, t as X402SettlementRepaired, u as X402WedgedSettlement, v as X402WedgedSettlementPage, w as X402_BATCH_SETTLEMENT_MAINNET_NETWORK, x as abortJob, y as applyPaymentInfoToJob, z as assertRevenueReporterReady, B as buildPaymentErrorResponse, D as clearAccumulatorForDvm, E as createDVMServer, G as creditDepositPayload, H as derivedFundCreditId, K as devModeSkipsPaymentVerification, L as fromJobRecord, N as fundedMicroFor, O as hasPaymentProof, Q as hashLockKey, W as implicitCreditId, Y as initWalletAccumulatorTable, Z as insertAccumulatorRows, _ as isDerivedCreditId, $ as isTerminal, a0 as isYieldMessage, a1 as issueUpfrontChallenges, a2 as msatsToFiatMicro, a3 as paymentErrorBody, a4 as pinAskFiat, a5 as priceFiatMicro, a6 as processIncomingPayment, a7 as providerMessage, a8 as repairX402ExactSettlementEffect, a9 as resolveFxSnapshot, aa as resolvePriceFiat, ab as revenueReporterBannerState, ac as toCreditTerms, ad as toJobRecord, ae as unknownRouteNotFound, af as verifyIncomingPayment, ag as verifyTempoSessionManagementCredential, ah as verifyUpfrontPayment, ai as x402RequiredUsdcMicro, aj as x402SettledShare } from '../credit-menu-kDA9RVzD.js';
9
+ import { A as AccumulatorPool, a as AccumulatorQuerier, C as ConsumedCredentialStore } from '../credit-menu-0-UtJ3hh.js';
10
+ export { b as AppEnv, c as CreditFundingReport, F as FiatDenomination, d as FiatDenominationFailure, e as FundOnlyRequest, I as IncomingPaymentOpts, J as JobCancelledError, M as MintHealthTracker, P as PAYMENT_PROOF_KEYS, f as PaymentErrorCode, g as PaymentErrorDetail, h as PaymentInfo, R as ReporterBannerOpts, i as ResolvedFx, j as RevenueBootCheckOpts, S as SDKServerOpts, k as ServerJob, l as ShortPayForfeit, T as TempoChannelReport, m as TempoObserverHealth, n as TempoSessionChannelMismatchError, U as UpfrontPaymentOpts, V as VerifiedIncomingPayment, o as VerifyIncomingSnapshot, X as X402BatchChannelObservation, p as X402FacilitatorHealth, q as X402SettlementEvidenceOutcome, r as X402SettlementRepair, s as X402SettlementRepairRefusal, t as X402SettlementRepaired, u as X402WedgedSettlement, v as X402WedgedSettlementPage, w as X402_BATCH_SETTLEMENT_MAINNET_NETWORK, x as abortJob, y as applyPaymentInfoToJob, z as assertRevenueReporterReady, B as buildPaymentErrorResponse, D as clearAccumulatorForDvm, E as createDVMServer, G as creditDepositPayload, H as derivedFundCreditId, K as devModeSkipsPaymentVerification, L as fromJobRecord, N as fundedMicroFor, O as hasPaymentProof, Q as hashLockKey, W as implicitCreditId, Y as initWalletAccumulatorTable, Z as insertAccumulatorRows, _ as isDerivedCreditId, $ as isTerminal, a0 as isYieldMessage, a1 as issueUpfrontChallenges, a2 as msatsToFiatMicro, a3 as paymentErrorBody, a4 as pinAskFiat, a5 as priceFiatMicro, a6 as processIncomingPayment, a7 as providerMessage, a8 as repairX402ExactSettlementEffect, a9 as resolveFxSnapshot, aa as resolvePriceFiat, ab as revenueReporterBannerState, ac as toCreditTerms, ad as toJobRecord, ae as unknownRouteNotFound, af as verifyIncomingPayment, ag as verifyTempoSessionManagementCredential, ah as verifyUpfrontPayment, ai as x402RequiredUsdcMicro, aj as x402SettledShare } from '../credit-menu-0-UtJ3hh.js';
11
11
  import { g as LockPubkey } from '../lightning-backend-BhsyrBLa.js';
12
12
  export { t as lockPubkeyStoreToLockPubkey } from '../lightning-backend-BhsyrBLa.js';
13
13
  export { O as OutgoingMessage, S as StaleJobReapable, i as isStaleJobReapable } from '../job-store-D3CcZp_g.js';
@@ -65,13 +65,14 @@ import {
65
65
  verifyUpfrontPayment,
66
66
  x402RequiredUsdcMicro,
67
67
  x402SettledShare
68
- } from "../chunk-JNKQZVZX.js";
68
+ } from "../chunk-4VHTKNOR.js";
69
69
  import "../chunk-XRLKGCAW.js";
70
70
  import {
71
71
  settleWithFacilitator,
72
72
  verifyWithFacilitator
73
73
  } from "../chunk-BQ2NMWKE.js";
74
74
  import "../chunk-U5VNEALI.js";
75
+ import "../chunk-HHTL3ZFT.js";
75
76
  import {
76
77
  SIGNED_ENVELOPE_FIELDS,
77
78
  SIGNED_ENVELOPE_TYPES,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PostgresJobStore
3
- } from "./chunk-JAHRF5X3.js";
3
+ } from "./chunk-HHTL3ZFT.js";
4
4
  import "./chunk-COYKJRMM.js";
5
5
  export {
6
6
  PostgresJobStore
@@ -1,8 +1,8 @@
1
1
  import { ab as CashuMode, bK as CreditDrainEnqueue, R as ResolvedCreditConfig, bL as DVMAuthScheme, z as SignedRequestDomain, bM as CreditLedgerLike, bN as X402RefundSettlementGate, bO as CreditSnapshot, bP as DrawResult, bQ as X402SettlementStatus, bR as X402UnresolvedRefund, bS as GrownDrawResult, bT as DrawResolution, bU as FundingRecord, X as FundingReceipt, bV as CreditInvoiceRecord, bW as InvoiceSettlement, bX as BlockedInvoiceCursor, bY as InvoiceReconciliation, bZ as InvoiceWriteOff, b_ as DrawRecord, b$ as StalePendingDrawCursor, c0 as TempoCreditLossEvidence, c1 as CreditLedgerQuerier, c2 as TempoCreditLoss, c3 as X402CreditLossEvidence, c4 as X402CreditLoss, c5 as DrainMethod, c6 as DrainRequestResult, c7 as BitcoinDepositLiability, bo as FundingLot, c8 as CreditDrainRecord, c9 as ChannelDrainCursor, ca as DrainWriteOff, cb as DrainReleaseResult, cc as DrainFulfilment, cd as DrainTransitionResult, Y as JobReceipt, a1 as Message, K as KVStore, F as SignedRequestReplayStore, Z as ZodLike, a as DVMDescriptor } from '../step-cache-v2yhyhSZ.js';
2
2
  export { ce as CLIENT_COMPATIBILITY_HEADERS, d as CanonicalEnvelope, cf as ClientCompatibility, cg as ClientCompatibilityEnv, ch as ClientCompatibilityGate, ci as ClientCompatibilityRequirement, cj as ClientSemVer, e as CreateSignedRequestVerifierOpts, ck as CreditFundingBasis, cl as CreditInvoiceStatus, cm as CreditLedger, cn as CreditLedgerError, co as CreditLedgerErrorCode, cp as CreditLedgerErrorDetails, cq as CreditLedgerPool, cr as CreditStatus, cs as DRAIN_DELIVERY_RESERVE_SATS, ct as DVM_PROTOCOL_VERSION, cu as DrainConflictReason, cv as DrawRailValue, cw as DrawStatus, cx as JobCostReportPayload, cy as JobTerminalReportPayload, cz as PostgresX402ChannelStorage, cA as ReplayStoreBackend, cB as RevenueSkippedNoRailPayload, cC as RevenueSkippedNoRailReason, w as SIGNED_REQUEST_AUTH_ID, x as SIGNED_REQUEST_STATEMENT_VERSION, cD as Secp256k1AuthOpts, y as SignedRequestAudience, B as SignedRequestError, E as SignedRequestFailure, G as SignedRequestSignOpts, H as SignedRequestStatementHeader, M as SignedRequestVerifier, cE as X402ChannelStorageOpts, cF as X402RelayLockHolder, cG as X402RelaySubmissionLock, cH as X402RelaySubmissionLockError, cI as allocateDrawValue, cJ as clientCompatibilityAttributes, cK as clientCompatibilityMiddleware, cL as clientUpgradeRequired, N as createSignedRequestVerifier, cM as parseClientCapabilities, cN as parseClientCompatibility, cO as parseDvmClient, cP as parseProtocolVersion, cQ as requireClientCompatibility, cR as secp256k1Auth, cS as signedRequestInput, T as signedRequestStatementHeader } from '../step-cache-v2yhyhSZ.js';
3
3
  import { Pool } from 'pg';
4
- import { ak as CreditMenu, b as AppEnv, U as UpfrontPaymentOpts, h as PaymentInfo, al as ReceiptIssuer, am as LightningReceive, an as TempoSettlementReadiness, ao as DVMHostOpts, ap as DVMServeResult } from '../credit-menu-kDA9RVzD.js';
5
- export { aq as BuildCreditMenuArgs, ar as BuilderIdentity, as as CREDIT_ENVELOPE_KEYS, C as ConsumedCredentialStore, at as CreateX402BatchSettlementServerOpts, au as CreditEnvelope, av as CreditEnvelopeError, aw as CreditFundCommitment, ax as CreditTerms, ay as DEFAULT_INVOICE_TTL_SECONDS, az as DVMHost, aA as JobManager, aB as JobManagerOpts, aC as LightningReceiveConfig, aD as MIN_INVOICE_TTL_SECONDS, aE as MemoryConsumedCredentialStore, aF as MemoryConsumedCredentialStoreOpts, aG as MemoryProcessedPaymentStore, aH as MemoryX402ExactSettlementStore, aI as MountOpts, aJ as OwnerDisplay, aK as PaidJobCompletion, aL as PaymentMode, aM as PlatformReporterOpts, aN as PostgresProcessedPaymentStore, aO as PostgresX402ExactSettlementStore, aP as PriceFiat, aQ as ProcessedPaymentQuerier, aR as ProcessedPaymentRail, aS as ProcessedPaymentRecord, aT as ProcessedPaymentReplayError, aU as ProcessedPaymentStore, aV as X402BatchAcceptance, aW as X402BatchFunding, aX as X402BatchRefusal, aY as X402BatchSettlementServer, aZ as X402ExactAcceptance, a_ as X402ExactIntentConflictError, a$ as X402ExactSettlementAttempt, b0 as X402ExactSettlementChainEvidence, b1 as X402ExactSettlementEffect, b2 as X402ExactSettlementEvidenceMissingError, b3 as X402ExactSettlementEvidenceReader, b4 as X402ExactSettlementIntent, b5 as X402ExactSettlementNotReadyError, b6 as X402ExactSettlementServer, b7 as X402ExactSettlementServerOpts, b8 as X402ExactSettlementStatus, b9 as X402ExactSettlementStore, ba as X402SettlementChainEvidence, bb as X402SettlementEvidenceReader, bc as X402SettlementSubmissionError, bd as X402_BATCH_AUTO_SETTLEMENT, be as X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS, bf as X402_BATCH_SETTLEMENT_NETWORK, bg as attachCreditMenu, bh as buildCreditMenu, bi as createDVMHost, bj as createX402BatchSettlementServer, bk as creditEnvelopeIgnoreFields, bl as drawSettlementRef, bm as extractCreditEnvelope, bn as fundingCommitment, bo as selectPrimaryCredit, bp as stripCreditEnvelope, bq as toCreditView } from '../credit-menu-kDA9RVzD.js';
4
+ import { ak as CreditMenu, b as AppEnv, U as UpfrontPaymentOpts, h as PaymentInfo, al as ReceiptIssuer, am as LightningReceive, an as TempoSettlementReadiness, ao as DVMHostOpts, ap as DVMServeResult } from '../credit-menu-0-UtJ3hh.js';
5
+ export { aq as BuildCreditMenuArgs, ar as BuilderIdentity, as as CREDIT_ENVELOPE_KEYS, C as ConsumedCredentialStore, at as CreateX402BatchSettlementServerOpts, au as CreditEnvelope, av as CreditEnvelopeError, aw as CreditFundCommitment, ax as CreditTerms, ay as DEFAULT_INVOICE_TTL_SECONDS, az as DVMHost, aA as HostingControlCommand, aB as HostingControlConfig, aC as HostingControlStatus, aD as JobManager, aE as JobManagerOpts, aF as LightningReceiveConfig, aG as MIN_INVOICE_TTL_SECONDS, aH as MemoryConsumedCredentialStore, aI as MemoryConsumedCredentialStoreOpts, aJ as MemoryProcessedPaymentStore, aK as MemoryX402ExactSettlementStore, aL as MountOpts, aM as OwnerDisplay, aN as PaidJobCompletion, aO as PaymentMode, aP as PlatformReporterOpts, aQ as PostgresProcessedPaymentStore, aR as PostgresX402ExactSettlementStore, aS as PriceFiat, aT as ProcessedPaymentQuerier, aU as ProcessedPaymentRail, aV as ProcessedPaymentRecord, aW as ProcessedPaymentReplayError, aX as ProcessedPaymentStore, aY as X402BatchAcceptance, aZ as X402BatchFunding, a_ as X402BatchRefusal, a$ as X402BatchSettlementServer, b0 as X402ExactAcceptance, b1 as X402ExactIntentConflictError, b2 as X402ExactSettlementAttempt, b3 as X402ExactSettlementChainEvidence, b4 as X402ExactSettlementEffect, b5 as X402ExactSettlementEvidenceMissingError, b6 as X402ExactSettlementEvidenceReader, b7 as X402ExactSettlementIntent, b8 as X402ExactSettlementNotReadyError, b9 as X402ExactSettlementServer, ba as X402ExactSettlementServerOpts, bb as X402ExactSettlementStatus, bc as X402ExactSettlementStore, bd as X402SettlementChainEvidence, be as X402SettlementEvidenceReader, bf as X402SettlementSubmissionError, bg as X402_BATCH_AUTO_SETTLEMENT, bh as X402_BATCH_MIN_WITHDRAW_DELAY_SECONDS, bi as X402_BATCH_SETTLEMENT_NETWORK, bj as attachCreditMenu, bk as buildCreditMenu, bl as createDVMHost, bm as createX402BatchSettlementServer, bn as creditEnvelopeIgnoreFields, bo as drawSettlementRef, bp as extractCreditEnvelope, bq as fundingCommitment, br as selectPrimaryCredit, bs as signHostingControl, bt as stripCreditEnvelope, bu as toCreditView } from '../credit-menu-0-UtJ3hh.js';
6
6
  import { Context } from 'hono';
7
7
  import { z } from 'zod';
8
8
  import { F as FxFetcher } from '../fx-DhyT1Cjh.js';
@@ -129,6 +129,8 @@ interface CreditRouteDeps {
129
129
  * invoice on the same credit is swept with it.
130
130
  */
131
131
  settleLightning: (callerPubkey: string, creditId?: string) => Promise<void>;
132
+ /** Hosting gate around a fresh funding operation, after caller auth and before any rail effect. */
133
+ withFundingAdmission?: (c: Context<AppEnv>, body: CreditRequest, run: () => Promise<Response>) => Promise<Response>;
132
134
  tempoSettlementReadiness?: TempoSettlementReadiness;
133
135
  }
134
136
  declare function handleCreditRequest(c: Context<AppEnv>, deps: CreditRouteDeps): Promise<Response>;
@@ -401,6 +403,7 @@ declare class PostgresJobStore implements StreamableJobStore, ReceiptIssuingStor
401
403
  heartbeatActiveJobs(jobIds: string[], now: number): Promise<void>;
402
404
  cancelStaleJob(jobId: string, expectedActivityBefore: number, reason: string, terminalStatus: "failed" | "cancelled"): Promise<boolean>;
403
405
  cancelJob(jobId: string, reason: string): Promise<boolean>;
406
+ cancelJobForHosting(jobId: string, reason: string): Promise<boolean>;
404
407
  getCounters(jobId: string): Promise<JobCounters | undefined>;
405
408
  claimForProcessing(jobId: string): Promise<boolean>;
406
409
  tryReactivationLock<T>(jobId: string, fn: () => Promise<T>): Promise<{
@@ -1,9 +1,6 @@
1
1
  import {
2
2
  PostgresReplayStore
3
3
  } from "../chunk-AJNFXR7J.js";
4
- import {
5
- PostgresJobStore
6
- } from "../chunk-JAHRF5X3.js";
7
4
  import {
8
5
  PostgresKVStore
9
6
  } from "../chunk-V5ZRATJH.js";
@@ -74,11 +71,12 @@ import {
74
71
  revenueReporterBannerState,
75
72
  seedLockPubkeyState,
76
73
  selectPrimaryCredit,
74
+ signHostingControl,
77
75
  stripCreditEnvelope,
78
76
  stripProtocolEnvelope,
79
77
  toCreditView,
80
78
  unknownRouteNotFound
81
- } from "../chunk-JNKQZVZX.js";
79
+ } from "../chunk-4VHTKNOR.js";
82
80
  import {
83
81
  MemoryProcessedPaymentStore,
84
82
  PostgresProcessedPaymentStore,
@@ -88,6 +86,9 @@ import "../chunk-BQ2NMWKE.js";
88
86
  import {
89
87
  MemoryCreditLedger
90
88
  } from "../chunk-U5VNEALI.js";
89
+ import {
90
+ PostgresJobStore
91
+ } from "../chunk-HHTL3ZFT.js";
91
92
  import {
92
93
  SignedRequestError,
93
94
  createDefaultReplayStore,
@@ -1098,6 +1099,12 @@ function createDVMHost(opts = {}) {
1098
1099
  )
1099
1100
  );
1100
1101
  const envMaybe = env;
1102
+ const hostingSecret = envMaybe.DVMKIT_HOSTING_CONTROL_SECRET;
1103
+ const hostingScope = envMaybe.DVMKIT_HOSTING_CONTROL_SCOPE;
1104
+ if (!opts.hostingControl && Boolean(hostingSecret) !== Boolean(hostingScope)) {
1105
+ throw new Error("Hosting control requires both DVMKIT_HOSTING_CONTROL_SECRET and DVMKIT_HOSTING_CONTROL_SCOPE");
1106
+ }
1107
+ const hostingControl = opts.hostingControl ?? (hostingSecret && hostingScope ? { secret: hostingSecret, scope: hostingScope } : void 0);
1101
1108
  const port = serveOpts?.port ?? opts.port ?? Number(envMaybe.PORT ?? "8080");
1102
1109
  const bootStart = Date.now();
1103
1110
  const phaseMarks = [];
@@ -1127,7 +1134,7 @@ function createDVMHost(opts = {}) {
1127
1134
  });
1128
1135
  applyRetryToPool(pool, "dvm-host");
1129
1136
  pgPool = pool;
1130
- const { PostgresJobStore: PostgresJobStore2 } = await import("../postgres-job-store-MZZZMS2B.js");
1137
+ const { PostgresJobStore: PostgresJobStore2 } = await import("../postgres-job-store-VE2JRUEC.js");
1131
1138
  const pgJobStore = new PostgresJobStore2(pool);
1132
1139
  pendingInits.push(pgJobStore.init(), pgJobStore.initStreaming());
1133
1140
  jobStore = pgJobStore;
@@ -1547,7 +1554,8 @@ ${config.facilitatorAuth?.keyId ?? ""}`;
1547
1554
  lockPubkey: cashuOpts.lockPubkey,
1548
1555
  lockPubkeyGraceSeconds: cashuOpts.lockPubkeyGraceSeconds,
1549
1556
  canonicalDvmId: cashuOpts.canonicalDvmId,
1550
- db: cashuOpts.db,
1557
+ db: hostingControl ? pgPool : cashuOpts.db,
1558
+ hostingControl,
1551
1559
  dvmId: cashuOpts.dvmId,
1552
1560
  failFast,
1553
1561
  mintHealthTracker: opts.mintHealthTracker,
@@ -2116,6 +2124,7 @@ export {
2116
2124
  secp256k1Auth,
2117
2125
  selectPrimaryCredit,
2118
2126
  serve,
2127
+ signHostingControl,
2119
2128
  signedRequestInput,
2120
2129
  signedRequestStatementHeader,
2121
2130
  stripCreditEnvelope,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dvmkit/sdk",
3
- "version": "0.2.1-rc.9",
3
+ "version": "0.3.0-rc.10",
4
4
  "description": "SDK for building accountless, pay-per-use Digital Vending Machines",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {