@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.
- package/dist/{chunk-JNKQZVZX.js → chunk-4VHTKNOR.js} +540 -0
- package/dist/{chunk-JAHRF5X3.js → chunk-HHTL3ZFT.js} +8 -5
- package/dist/{credit-menu-kDA9RVzD.d.ts → credit-menu-0-UtJ3hh.d.ts} +44 -1
- package/dist/internal/server.d.ts +2 -2
- package/dist/internal/server.js +2 -1
- package/dist/{postgres-job-store-MZZZMS2B.js → postgres-job-store-VE2JRUEC.js} +1 -1
- package/dist/server/index.d.ts +5 -2
- package/dist/server/index.js +15 -6
- package/package.json +1 -1
|
@@ -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 =
|
|
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,
|
|
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
|
|
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-
|
|
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-
|
|
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';
|
package/dist/internal/server.js
CHANGED
|
@@ -65,13 +65,14 @@ import {
|
|
|
65
65
|
verifyUpfrontPayment,
|
|
66
66
|
x402RequiredUsdcMicro,
|
|
67
67
|
x402SettledShare
|
|
68
|
-
} from "../chunk-
|
|
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,
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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-
|
|
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
|
|
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<{
|
package/dist/server/index.js
CHANGED
|
@@ -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-
|
|
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-
|
|
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,
|