@dvmkit/sdk 0.7.0-rc.9 → 0.8.1
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/NOTICE +1 -1
- package/README.md +57 -261
- package/dist/{chunk-5CSR33B5.js → chunk-2U6LAASN.js} +1 -1
- package/dist/chunk-66HGCPBU.js +25 -0
- package/dist/{chunk-QM2OS3NJ.js → chunk-7PM4R4GD.js} +1 -1
- package/dist/{chunk-YE53E4BT.js → chunk-AGE5XZKE.js} +1 -1
- package/dist/{chunk-RJFBBEWQ.js → chunk-ALWVJ2RB.js} +1 -1
- package/dist/{chunk-FYETJDBB.js → chunk-AVV5SYGO.js} +2 -2
- package/dist/{chunk-5PLQOC4B.js → chunk-BFGQG4WY.js} +9 -1
- package/dist/{chunk-IOKEKMIU.js → chunk-FM6QAGJC.js} +2 -2
- package/dist/{chunk-KXWROQGK.js → chunk-FUJ36YDV.js} +1 -24
- package/dist/{chunk-6JZDFYQL.js → chunk-G3MPHW5I.js} +1 -1
- package/dist/{chunk-2HAFZ5GC.js → chunk-IHQUBATH.js} +2 -2
- package/dist/{chunk-YS6PWVCZ.js → chunk-ILBSZMJL.js} +1 -1
- package/dist/{chunk-RGSWVNQG.js → chunk-IOQIXRGM.js} +2 -43
- package/dist/{chunk-YNHXOONR.js → chunk-J5J63WDC.js} +1 -1
- package/dist/{chunk-G7IXOVKV.js → chunk-JIZ4ZIXX.js} +331 -102
- package/dist/{chunk-ZLWPEIE2.js → chunk-NA2KOR6G.js} +1 -1
- package/dist/{chunk-2DE32RAW.js → chunk-NU5RWJ5B.js} +1 -1
- package/dist/chunk-OP7VCGQR.js +47 -0
- package/dist/{chunk-QN3CQOE2.js → chunk-QKE5NDPF.js} +368 -7
- package/dist/{chunk-7RZEJJTN.js → chunk-RSNC5T6C.js} +2 -2
- package/dist/{chunk-2GTLF74N.js → chunk-UDJNZ3UG.js} +1 -1
- package/dist/{chunk-WGK5KZ6S.js → chunk-UVS7MRJX.js} +3 -3
- package/dist/{chunk-HU6ZMQDN.js → chunk-UWGBI5WK.js} +58 -1
- package/dist/{chunk-OFSOLHRX.js → chunk-WMGXJQI3.js} +1 -1
- package/dist/{chunk-5TIFEMHJ.js → chunk-ZEPJF4JC.js} +2 -2
- package/dist/{content-retention-U7dJM7Ph.d.ts → content-retention-B7ddtJ2y.d.ts} +1 -1
- package/dist/{credit-ledger-F2LWQU6N.js → credit-ledger-KIQIE6I6.js} +3 -3
- package/dist/{credit-menu-CUJdCfpt.d.ts → credit-menu-BH3g9GQa.d.ts} +5 -5
- package/dist/{fx-BvG2Wnjk.d.ts → fx-DN26_J7o.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -4
- package/dist/internal/caller.d.ts +10 -8
- package/dist/internal/caller.js +12 -9
- package/dist/internal/server.d.ts +7 -7
- package/dist/internal/server.js +22 -20
- package/dist/{job-store-DjgN2AOk.d.ts → job-store-Cq21JT37.d.ts} +1 -1
- package/dist/{lightning-backend-DeasHNOP.d.ts → lightning-backend-S8F8nf8q.d.ts} +1 -1
- package/dist/{memory-credit-ledger-XOOWJTBZ.js → memory-credit-ledger-ATYFAYBG.js} +4 -4
- package/dist/{mpp-setup-KGX6PLAF.js → mpp-setup-ZTORW3VF.js} +4 -2
- package/dist/{payout-reporter-W3CCLYCN.js → payout-reporter-NN6K2IVQ.js} +2 -2
- package/dist/{postgres-consumed-credential-store-LUJ5CUJS.js → postgres-consumed-credential-store-XJTE5PCA.js} +1 -1
- package/dist/{postgres-job-store-IX7IUXF4.js → postgres-job-store-AR6ZJUQV.js} +2 -2
- package/dist/{postgres-replay-store-Q6MEQDWZ.js → postgres-replay-store-33K33QV7.js} +2 -2
- package/dist/{pricing-ZUEFO3CM.js → pricing-G5IN5YJE.js} +3 -3
- package/dist/{revenue-reporter-NRS2FANA.js → revenue-reporter-KDUZLD5F.js} +2 -2
- package/dist/server/index.d.ts +10 -10
- package/dist/server/index.js +30 -28
- package/dist/{step-cache-C9JRSFhM.d.ts → step-cache-CR12EZ1Q.d.ts} +136 -4
- package/dist/{tempo-charge-store-NZJTJCUO.js → tempo-charge-store-XEGJYM3G.js} +2 -2
- package/dist/{tempo-session-store-ENFDLDKC.js → tempo-session-store-3WTDOO7H.js} +3 -3
- package/dist/testing/index.d.ts +3 -3
- package/dist/testing/index.js +8 -6
- package/dist/{usd-DXDkHn_8.d.ts → usd-s_VZ_eS7.d.ts} +1 -1
- package/dist/{x402-T7WVKDD4.js → x402-HQ5GRQZU.js} +6 -5
- package/package.json +18 -3
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
installHostedTempoCloseGuard,
|
|
8
8
|
isTempoInsufficientFundsError,
|
|
9
9
|
resolveTempoNetwork
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-QKE5NDPF.js";
|
|
11
11
|
import {
|
|
12
12
|
x402EarnedCeiling
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-J5J63WDC.js";
|
|
14
14
|
import {
|
|
15
15
|
createX402FacilitatorAuthHeaders,
|
|
16
16
|
createX402FacilitatorClient,
|
|
@@ -19,13 +19,13 @@ import {
|
|
|
19
19
|
verifyWithFacilitator,
|
|
20
20
|
withPaymentDiagnostics,
|
|
21
21
|
withPaymentStage
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-AVV5SYGO.js";
|
|
23
23
|
import {
|
|
24
24
|
MemoryCreditLedger
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-AGE5XZKE.js";
|
|
26
26
|
import {
|
|
27
27
|
PostgresJobStore
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-G3MPHW5I.js";
|
|
29
29
|
import {
|
|
30
30
|
parseStoreValue
|
|
31
31
|
} from "./chunk-YG7G4DPZ.js";
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
humanizeZodIssues,
|
|
37
37
|
signedRequestInput,
|
|
38
38
|
zodIssuesOf
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-ILBSZMJL.js";
|
|
40
40
|
import {
|
|
41
41
|
MemoryJobStore,
|
|
42
42
|
UNCAPPABLE_ASK_TOTAL,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
isJobRetentionStore,
|
|
46
46
|
isReceiptIssuingStore,
|
|
47
47
|
isStreamableJobStore
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-IHQUBATH.js";
|
|
49
49
|
import {
|
|
50
50
|
ContentHoldError,
|
|
51
51
|
JobCancelledError,
|
|
@@ -85,7 +85,7 @@ import {
|
|
|
85
85
|
signReceipt,
|
|
86
86
|
toLockPubkey,
|
|
87
87
|
verifyChallenge
|
|
88
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-ZEPJF4JC.js";
|
|
89
89
|
import {
|
|
90
90
|
X402_BATCH_SETTLEMENT_SCHEME,
|
|
91
91
|
X402_DEFAULT_FACILITATOR,
|
|
@@ -112,12 +112,14 @@ import {
|
|
|
112
112
|
isZodSchema
|
|
113
113
|
} from "./chunk-UKD3R2KU.js";
|
|
114
114
|
import {
|
|
115
|
-
createConsoleLogger
|
|
115
|
+
createConsoleLogger
|
|
116
|
+
} from "./chunk-OP7VCGQR.js";
|
|
117
|
+
import {
|
|
116
118
|
loggers,
|
|
117
119
|
pinDimension,
|
|
118
120
|
withSpan,
|
|
119
121
|
withTraceContext
|
|
120
|
-
} from "./chunk-
|
|
122
|
+
} from "./chunk-IOQIXRGM.js";
|
|
121
123
|
import {
|
|
122
124
|
StepCache
|
|
123
125
|
} from "./chunk-BQJIFRAZ.js";
|
|
@@ -134,7 +136,7 @@ import {
|
|
|
134
136
|
roundUsd,
|
|
135
137
|
satsToFiat,
|
|
136
138
|
validateCurrency
|
|
137
|
-
} from "./chunk-
|
|
139
|
+
} from "./chunk-2U6LAASN.js";
|
|
138
140
|
import {
|
|
139
141
|
CreditLedger,
|
|
140
142
|
CreditLedgerError,
|
|
@@ -150,10 +152,10 @@ import {
|
|
|
150
152
|
isNonChannelBitcoinRail,
|
|
151
153
|
netOwedSats,
|
|
152
154
|
x402SettlementPending
|
|
153
|
-
} from "./chunk-
|
|
155
|
+
} from "./chunk-FM6QAGJC.js";
|
|
154
156
|
import {
|
|
155
157
|
withPostgresRecoveryContext
|
|
156
|
-
} from "./chunk-
|
|
158
|
+
} from "./chunk-7PM4R4GD.js";
|
|
157
159
|
import {
|
|
158
160
|
DvmError,
|
|
159
161
|
x402NetworkByCaip2
|
|
@@ -161,7 +163,7 @@ import {
|
|
|
161
163
|
import {
|
|
162
164
|
createDiagnosticCapabilityAuthority,
|
|
163
165
|
diagnosticConsole
|
|
164
|
-
} from "./chunk-
|
|
166
|
+
} from "./chunk-BFGQG4WY.js";
|
|
165
167
|
import {
|
|
166
168
|
MemoryProcessedPaymentStore,
|
|
167
169
|
PostgresProcessedPaymentStore,
|
|
@@ -3118,6 +3120,10 @@ function decodeBatchPayment(header) {
|
|
|
3118
3120
|
if (!isBatchSettlementDepositPayload2(parsed.payload) && !isBatchSettlementVoucherPayload(parsed.payload) && !isBatchSettlementRefundPayload2(parsed.payload)) {
|
|
3119
3121
|
throw new Error("invalid batch-settlement payload");
|
|
3120
3122
|
}
|
|
3123
|
+
const voucher = parsed.payload.voucher;
|
|
3124
|
+
if (typeof voucher.channelId !== "string" || typeof voucher.maxClaimableAmount !== "string" || typeof voucher.signature !== "string") {
|
|
3125
|
+
throw new Error("invalid batch-settlement voucher");
|
|
3126
|
+
}
|
|
3121
3127
|
return parsed;
|
|
3122
3128
|
}
|
|
3123
3129
|
function batchVoucher(payload) {
|
|
@@ -3877,7 +3883,7 @@ async function verifyUpfrontPaymentInScope(opts) {
|
|
|
3877
3883
|
x402Requirements
|
|
3878
3884
|
});
|
|
3879
3885
|
}
|
|
3880
|
-
const { verifyX402Payment } = await import("./x402-
|
|
3886
|
+
const { verifyX402Payment } = await import("./x402-HQ5GRQZU.js");
|
|
3881
3887
|
const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
|
|
3882
3888
|
x402Payment,
|
|
3883
3889
|
x402Config,
|
|
@@ -4040,6 +4046,30 @@ async function verifyUpfrontPaymentInScope(opts) {
|
|
|
4040
4046
|
}
|
|
4041
4047
|
const expectedAmount = opts.priceFiat?.currency === "usd" ? fiatMicroToMethodAmount(opts.priceFiat.amountMicro, cred.challenge.method) : msatsToMethodAmount(requiredMsats, cred.challenge.method, rate);
|
|
4042
4048
|
const sessionBasis = mppSessionBasis(cred);
|
|
4049
|
+
const replayRealm = mpp.realm ?? cred.challenge.realm;
|
|
4050
|
+
if (consumedCredentialStore && await withPaymentStage("replay_store", () => consumedCredentialStore.has(replayRealm, cred.challenge.id))) {
|
|
4051
|
+
const mppChallenges = await issueUpfrontChallenges(
|
|
4052
|
+
mpp,
|
|
4053
|
+
requiredMsats,
|
|
4054
|
+
resourcePath ?? "/v1/job",
|
|
4055
|
+
rate,
|
|
4056
|
+
sessionIssuable,
|
|
4057
|
+
opts.fundOnly?.allowOneShotStablecoin,
|
|
4058
|
+
opts.priceFiat
|
|
4059
|
+
);
|
|
4060
|
+
return {
|
|
4061
|
+
paidMsats: 0,
|
|
4062
|
+
receivedProofs: [],
|
|
4063
|
+
error: {
|
|
4064
|
+
body: paymentErrorBody("payment_invalid", {
|
|
4065
|
+
message: "MPP credential already used"
|
|
4066
|
+
}),
|
|
4067
|
+
status: 402,
|
|
4068
|
+
mppChallenges,
|
|
4069
|
+
...x402Requirements && { x402Requirements }
|
|
4070
|
+
}
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4043
4073
|
let tempoReceipt;
|
|
4044
4074
|
const broadcast = async () => {
|
|
4045
4075
|
const receipt = await loggers.tempo.span(
|
|
@@ -4157,35 +4187,12 @@ async function verifyUpfrontPaymentInScope(opts) {
|
|
|
4157
4187
|
}
|
|
4158
4188
|
};
|
|
4159
4189
|
}
|
|
4160
|
-
if (consumedCredentialStore) {
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
requiredMsats,
|
|
4167
|
-
resourcePath ?? "/v1/job",
|
|
4168
|
-
rate,
|
|
4169
|
-
sessionIssuable,
|
|
4170
|
-
opts.fundOnly?.allowOneShotStablecoin,
|
|
4171
|
-
opts.priceFiat
|
|
4172
|
-
);
|
|
4173
|
-
return {
|
|
4174
|
-
paidMsats: 0,
|
|
4175
|
-
receivedProofs: [],
|
|
4176
|
-
error: {
|
|
4177
|
-
body: paymentErrorBody("payment_invalid", {
|
|
4178
|
-
message: "MPP credential already used"
|
|
4179
|
-
}),
|
|
4180
|
-
status: 402,
|
|
4181
|
-
mppChallenges,
|
|
4182
|
-
...x402Requirements && { x402Requirements }
|
|
4183
|
-
}
|
|
4184
|
-
};
|
|
4185
|
-
}
|
|
4186
|
-
if (!sessionBasis) {
|
|
4187
|
-
await withPaymentStage("replay_store", () => consumedCredentialStore.mark(realm, id, CONSUMED_CREDENTIAL_TTL_MS / 1e3));
|
|
4188
|
-
}
|
|
4190
|
+
if (consumedCredentialStore && !sessionBasis) {
|
|
4191
|
+
await withPaymentStage("replay_store", () => consumedCredentialStore.mark(
|
|
4192
|
+
replayRealm,
|
|
4193
|
+
cred.challenge.id,
|
|
4194
|
+
CONSUMED_CREDENTIAL_TTL_MS / 1e3
|
|
4195
|
+
));
|
|
4189
4196
|
}
|
|
4190
4197
|
if (ledgerCtx) {
|
|
4191
4198
|
try {
|
|
@@ -4208,10 +4215,18 @@ async function verifyUpfrontPaymentInScope(opts) {
|
|
|
4208
4215
|
}
|
|
4209
4216
|
if (sessionBasis && consumedCredentialStore) {
|
|
4210
4217
|
await withPaymentStage("replay_store", () => consumedCredentialStore.mark(
|
|
4211
|
-
|
|
4218
|
+
replayRealm,
|
|
4212
4219
|
cred.challenge.id,
|
|
4213
4220
|
CONSUMED_CREDENTIAL_TTL_MS / 1e3
|
|
4214
|
-
))
|
|
4221
|
+
)).catch((error) => {
|
|
4222
|
+
diagnosticConsole.warn({
|
|
4223
|
+
level: "warn",
|
|
4224
|
+
event: "tempo_replay_mark_failed",
|
|
4225
|
+
phase: "upfront",
|
|
4226
|
+
rail: "tempo",
|
|
4227
|
+
...paymentFailureFields(error)
|
|
4228
|
+
});
|
|
4229
|
+
});
|
|
4215
4230
|
}
|
|
4216
4231
|
if (credited) {
|
|
4217
4232
|
await mpp.completeHostedTempoCharge?.(cred.challenge.id).catch((error) => {
|
|
@@ -4420,6 +4435,22 @@ async function verifyTempoSessionManagementCredential(args) {
|
|
|
4420
4435
|
throw new Error("Hosted Tempo close requires non-mutating credential validation");
|
|
4421
4436
|
}
|
|
4422
4437
|
await args.mpp.validateCredential(credential, verifyOptions);
|
|
4438
|
+
if (args.action === "close") {
|
|
4439
|
+
if (!args.mpp.assertTempoSessionCloseVoucher) {
|
|
4440
|
+
throw new Error("Hosted Tempo close requires stored-channel voucher validation");
|
|
4441
|
+
}
|
|
4442
|
+
const { cumulativeAmount, signature } = credential.payload;
|
|
4443
|
+
if (typeof cumulativeAmount !== "string" || typeof signature !== "string") {
|
|
4444
|
+
throw new Error("Expected a Tempo session close voucher");
|
|
4445
|
+
}
|
|
4446
|
+
await args.mpp.assertTempoSessionCloseVoucher({
|
|
4447
|
+
channelId: payload.channelId,
|
|
4448
|
+
cumulativeAmount: BigInt(cumulativeAmount),
|
|
4449
|
+
signature,
|
|
4450
|
+
descriptor: credential.payload.descriptor,
|
|
4451
|
+
request: credential.challenge.request
|
|
4452
|
+
});
|
|
4453
|
+
}
|
|
4423
4454
|
}
|
|
4424
4455
|
await args.onBound?.();
|
|
4425
4456
|
const receipt = await args.mpp.broadcastCredential(credential, verifyOptions);
|
|
@@ -4865,7 +4896,7 @@ async function verifyIncomingPaymentInScope(body, opts, snapshot) {
|
|
|
4865
4896
|
snapshot
|
|
4866
4897
|
});
|
|
4867
4898
|
}
|
|
4868
|
-
const { verifyX402Payment } = await import("./x402-
|
|
4899
|
+
const { verifyX402Payment } = await import("./x402-HQ5GRQZU.js");
|
|
4869
4900
|
const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
|
|
4870
4901
|
body.content.x402_payment,
|
|
4871
4902
|
opts.x402Config,
|
|
@@ -5296,7 +5327,7 @@ async function processIncomingPaymentInScope(job, body, opts) {
|
|
|
5296
5327
|
try {
|
|
5297
5328
|
const requiredMsats = job.pendingPaymentMsats ?? 0;
|
|
5298
5329
|
const requiredUsdcMicro = job.pendingX402AmountUsdcMicro !== void 0 ? BigInt(job.pendingX402AmountUsdcMicro) : BigInt(msatsToUsdc(requiredMsats, rate));
|
|
5299
|
-
const { verifyX402Payment } = await import("./x402-
|
|
5330
|
+
const { verifyX402Payment } = await import("./x402-HQ5GRQZU.js");
|
|
5300
5331
|
const receipt = await withPaymentStage("verifier_broadcast", () => verifyX402Payment(
|
|
5301
5332
|
body.content.x402_payment,
|
|
5302
5333
|
opts.x402Config,
|
|
@@ -7456,17 +7487,16 @@ async function completeTempoDrain(c, deps, record, expectedChannelId, credit) {
|
|
|
7456
7487
|
await beginClose(expectedChannelId, record.drainId);
|
|
7457
7488
|
recoveryVersion = await deps.tempoSettlementReadiness?.captureRecoveryVersion();
|
|
7458
7489
|
if (deps.hostedSettlementReports) {
|
|
7459
|
-
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
record
|
|
7490
|
+
hostedObligationId = await reserveHostedTempoClose(
|
|
7491
|
+
deps.hostedSettlementReports,
|
|
7492
|
+
rail.mpp,
|
|
7493
|
+
record,
|
|
7494
|
+
expectedChannelId
|
|
7463
7495
|
);
|
|
7464
|
-
if (reserved.replayed) throw new Error("Hosted Tempo close already has an original effect attempt");
|
|
7465
|
-
hostedObligationId = reserved.id;
|
|
7466
7496
|
if (!rail.mpp?.permitHostedTempoClose) {
|
|
7467
7497
|
throw new Error("Hosted Tempo close credential guard is unavailable");
|
|
7468
7498
|
}
|
|
7469
|
-
rail.mpp.permitHostedTempoClose(rail.mppCredential,
|
|
7499
|
+
rail.mpp.permitHostedTempoClose(rail.mppCredential, hostedObligationId);
|
|
7470
7500
|
}
|
|
7471
7501
|
}
|
|
7472
7502
|
});
|
|
@@ -7491,6 +7521,14 @@ async function completeTempoDrain(c, deps, record, expectedChannelId, credit) {
|
|
|
7491
7521
|
}
|
|
7492
7522
|
const wrongChannel = tempoWrongChannelRefusal(error, record);
|
|
7493
7523
|
if (wrongChannel) return wrongChannel;
|
|
7524
|
+
if (error instanceof HostedTempoCloseAttemptsExhaustedError) {
|
|
7525
|
+
return jsonPaymentError("tempo_channel_exit_required", 409, {
|
|
7526
|
+
message: `Tempo drain ${record.drainId} reached its limit of cooperative close attempts that never reached the chain.`,
|
|
7527
|
+
display: "The service could not complete a cooperative close of this stablecoin channel after several attempts. Your funds stay in the channel; use the timed on-chain exit to recover them.",
|
|
7528
|
+
hint: "Start the channel's timed on-chain exit. Retrying this drain with another close credential is refused.",
|
|
7529
|
+
extra: { drain_registered: true, drain_id: record.drainId, channel_id: expectedChannelId }
|
|
7530
|
+
});
|
|
7531
|
+
}
|
|
7494
7532
|
const refused = tempoFloorRefusal(error, record);
|
|
7495
7533
|
if (refused) return refused;
|
|
7496
7534
|
if (tempoCloseCredentialNeedsRefresh(error)) {
|
|
@@ -7537,6 +7575,49 @@ async function completeTempoDrain(c, deps, record, expectedChannelId, credit) {
|
|
|
7537
7575
|
});
|
|
7538
7576
|
}
|
|
7539
7577
|
}
|
|
7578
|
+
var HOSTED_TEMPO_CLOSE_STALE_MS = 3e5;
|
|
7579
|
+
var HOSTED_TEMPO_CLOSE_MAX_RELEASES = 3;
|
|
7580
|
+
var HOSTED_TEMPO_CLOSE_IN_FLIGHT = "Hosted Tempo close already has an original effect attempt";
|
|
7581
|
+
var HostedTempoCloseAttemptsExhaustedError = class extends Error {
|
|
7582
|
+
};
|
|
7583
|
+
async function reserveHostedTempoClose(reports, mpp, record, channelId) {
|
|
7584
|
+
const pending = await reports.pendingTempoClose(channelId, record.drainId);
|
|
7585
|
+
if (!pending) {
|
|
7586
|
+
const reserved = await reports.reserveTempo(channelId, "close", record.drainId);
|
|
7587
|
+
if (reserved.replayed) throw new Error(HOSTED_TEMPO_CLOSE_IN_FLIGHT);
|
|
7588
|
+
return reserved.id;
|
|
7589
|
+
}
|
|
7590
|
+
if (pending.txHash || !mpp.getTempoSessionDrainState) throw new Error(HOSTED_TEMPO_CLOSE_IN_FLIGHT);
|
|
7591
|
+
const state = await mpp.getTempoSessionDrainState(channelId);
|
|
7592
|
+
if (state.finalized || state.deposit === 0n || state.closeRequestedAt !== 0n) {
|
|
7593
|
+
throw new Error(HOSTED_TEMPO_CLOSE_IN_FLIGHT);
|
|
7594
|
+
}
|
|
7595
|
+
const result = await reports.releaseStaleTempoClose(pending.id, channelId, record.drainId, {
|
|
7596
|
+
staleAfterMs: HOSTED_TEMPO_CLOSE_STALE_MS,
|
|
7597
|
+
maxReleases: HOSTED_TEMPO_CLOSE_MAX_RELEASES
|
|
7598
|
+
});
|
|
7599
|
+
if (result.outcome === "in_flight") throw new Error(HOSTED_TEMPO_CLOSE_IN_FLIGHT);
|
|
7600
|
+
if (result.outcome === "exhausted") {
|
|
7601
|
+
loggers.tempo.warn("tempo_session_close_attempts_exhausted", {
|
|
7602
|
+
credit_id: record.creditId,
|
|
7603
|
+
drain_id: record.drainId,
|
|
7604
|
+
channel_id: channelId,
|
|
7605
|
+
obligation_id: pending.id,
|
|
7606
|
+
attempt: result.releases + 1
|
|
7607
|
+
});
|
|
7608
|
+
throw new HostedTempoCloseAttemptsExhaustedError(
|
|
7609
|
+
`Tempo drain ${record.drainId} exhausted its cooperative close attempts`
|
|
7610
|
+
);
|
|
7611
|
+
}
|
|
7612
|
+
loggers.tempo.warn("tempo_session_close_reservation_released", {
|
|
7613
|
+
credit_id: record.creditId,
|
|
7614
|
+
drain_id: record.drainId,
|
|
7615
|
+
channel_id: channelId,
|
|
7616
|
+
obligation_id: pending.id,
|
|
7617
|
+
attempt: result.releases + 1
|
|
7618
|
+
});
|
|
7619
|
+
return result.id;
|
|
7620
|
+
}
|
|
7540
7621
|
function tempoCloseCredentialNeedsRefresh(error) {
|
|
7541
7622
|
return error instanceof MppxErrors.InvalidChallengeError || error instanceof MppxErrors.PaymentExpiredError;
|
|
7542
7623
|
}
|
|
@@ -9900,6 +9981,8 @@ var HostedSettlementReportObligations = class {
|
|
|
9900
9981
|
created_at_ms BIGINT NOT NULL DEFAULT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint,
|
|
9901
9982
|
updated_at_ms BIGINT NOT NULL DEFAULT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint
|
|
9902
9983
|
);
|
|
9984
|
+
ALTER TABLE sdk_hosted_settlement_report_obligations
|
|
9985
|
+
ADD COLUMN IF NOT EXISTS close_search_before BIGINT;
|
|
9903
9986
|
CREATE INDEX IF NOT EXISTS sdk_hosted_settlement_report_pending
|
|
9904
9987
|
ON sdk_hosted_settlement_report_obligations (dvm_id,scope,state)
|
|
9905
9988
|
WHERE state NOT IN ('delivered','no_report');
|
|
@@ -10006,53 +10089,109 @@ var HostedSettlementReportObligations = class {
|
|
|
10006
10089
|
});
|
|
10007
10090
|
}
|
|
10008
10091
|
async reserveTempo(channelId, operation, drainId) {
|
|
10009
|
-
|
|
10010
|
-
|
|
10092
|
+
return transaction(this.pool, (tx) => this.reserveTempoIn(tx, channelId, operation, drainId));
|
|
10093
|
+
}
|
|
10094
|
+
/**
|
|
10095
|
+
* Retire a close reservation that never recorded a transaction and reserve
|
|
10096
|
+
* the drain's next attempt, in one transaction, so the drain always keeps one
|
|
10097
|
+
* outstanding close obligation. A close from the retired attempt that lands
|
|
10098
|
+
* late is a close of the same channel: `recordSubmitted` on the retired row
|
|
10099
|
+
* writes it onto the successor, and recovery attaches it there if that
|
|
10100
|
+
* attempt never got to write, so it is reported once.
|
|
10101
|
+
*
|
|
10102
|
+
* The caller has just read the channel open on chain. The age bound only
|
|
10103
|
+
* spares a reservation young enough to be inside its first receipt wait; an
|
|
10104
|
+
* attempt slowed past it by a degraded RPC can still be running.
|
|
10105
|
+
*/
|
|
10106
|
+
async releaseStaleTempoClose(id, channelId, drainId, limits) {
|
|
10011
10107
|
return transaction(this.pool, async (tx) => {
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10108
|
+
await this.lockIdentity(tx);
|
|
10109
|
+
const row = await this.lockRow(tx, id);
|
|
10110
|
+
if (row.rail !== "tempo" || row.operation !== "close" || row.channel_id !== channelId.toLowerCase() || row.original.drainId !== drainId || row.state !== "reserved" || row.tx_hash) {
|
|
10111
|
+
return { outcome: "in_flight" };
|
|
10112
|
+
}
|
|
10113
|
+
const age = await tx.query(
|
|
10114
|
+
`SELECT updated_at_ms <= FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint - $2 AS stale
|
|
10115
|
+
FROM sdk_hosted_settlement_report_obligations WHERE id=$1`,
|
|
10116
|
+
[id, limits.staleAfterMs]
|
|
10015
10117
|
);
|
|
10016
|
-
if (!
|
|
10017
|
-
const
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
}
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
[channelId.toLowerCase()]
|
|
10029
|
-
);
|
|
10030
|
-
if (close.rows[0]?.drain_id !== drainId) throw new Error("Hosted Tempo close has no matching drain");
|
|
10031
|
-
}
|
|
10032
|
-
const highestVoucherAmount = nativeAmount(row.highestVoucherAmount);
|
|
10033
|
-
const settledOnChain = nativeAmount(row.settledOnChain);
|
|
10034
|
-
const spent = nativeAmount(row.spent);
|
|
10035
|
-
if (BigInt(settledOnChain) > BigInt(highestVoucherAmount) || BigInt(spent) > BigInt(highestVoucherAmount)) {
|
|
10036
|
-
throw new Error("Hosted Tempo settlement source has inconsistent amounts");
|
|
10037
|
-
}
|
|
10038
|
-
const original = operation === "settle" ? {
|
|
10039
|
-
highestVoucherAmount,
|
|
10040
|
-
settledOnChain,
|
|
10041
|
-
spent
|
|
10042
|
-
} : { drainId, highestVoucherAmount, settledOnChain, spent };
|
|
10043
|
-
return this.insert(
|
|
10044
|
-
tx,
|
|
10045
|
-
identity,
|
|
10046
|
-
"tempo",
|
|
10047
|
-
operation,
|
|
10048
|
-
config.network,
|
|
10049
|
-
config.escrow,
|
|
10050
|
-
channelId.toLowerCase(),
|
|
10051
|
-
original,
|
|
10052
|
-
operation === "close" ? [drainId] : [highestVoucherAmount]
|
|
10118
|
+
if (!age.rows[0]?.stale) return { outcome: "in_flight" };
|
|
10119
|
+
const released = Number((await tx.query(
|
|
10120
|
+
`SELECT COUNT(*)::text AS n FROM sdk_hosted_settlement_report_obligations
|
|
10121
|
+
WHERE dvm_id=$1 AND scope=$2 AND rail='tempo' AND operation='close' AND channel_id=$3
|
|
10122
|
+
AND original->>'drainId'=$4 AND state='no_report' AND tx_hash IS NULL`,
|
|
10123
|
+
[this.dvmId, this.scope, channelId.toLowerCase(), drainId]
|
|
10124
|
+
)).rows[0].n);
|
|
10125
|
+
if (released >= limits.maxReleases) return { outcome: "exhausted", releases: released };
|
|
10126
|
+
await tx.query(
|
|
10127
|
+
`UPDATE sdk_hosted_settlement_report_obligations SET state='no_report',
|
|
10128
|
+
updated_at_ms=FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint WHERE id=$1`,
|
|
10129
|
+
[id]
|
|
10053
10130
|
);
|
|
10131
|
+
const next = await this.reserveTempoIn(tx, channelId, "close", drainId);
|
|
10132
|
+
return { outcome: "reserved", id: next.id, releases: released + 1 };
|
|
10054
10133
|
});
|
|
10055
10134
|
}
|
|
10135
|
+
/**
|
|
10136
|
+
* Resolve an unsubmitted close reservation whose channel the payer exited
|
|
10137
|
+
* on its own: no cooperative close of ours can land on a finalized channel.
|
|
10138
|
+
*/
|
|
10139
|
+
async resolveTempoCloseExit(id) {
|
|
10140
|
+
const result = await this.pool.query(
|
|
10141
|
+
`UPDATE sdk_hosted_settlement_report_obligations SET state='no_report',
|
|
10142
|
+
updated_at_ms=FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint
|
|
10143
|
+
WHERE id=$1 AND dvm_id=$2 AND scope=$3 AND rail='tempo' AND operation='close'
|
|
10144
|
+
AND state='reserved' AND tx_hash IS NULL`,
|
|
10145
|
+
[id, this.dvmId, this.scope]
|
|
10146
|
+
);
|
|
10147
|
+
return result.rowCount === 1;
|
|
10148
|
+
}
|
|
10149
|
+
async reserveTempoIn(tx, channelId, operation, drainId) {
|
|
10150
|
+
const config = this.tempo;
|
|
10151
|
+
if (!config || !hex(channelId, 32)) throw new Error("Hosted Tempo settlement configuration is unavailable");
|
|
10152
|
+
const state = await tx.query(
|
|
10153
|
+
"SELECT state_json FROM mpp_tempo_channels WHERE channel_key=$1",
|
|
10154
|
+
[channelId.toLowerCase()]
|
|
10155
|
+
);
|
|
10156
|
+
if (!state.rows[0]) throw new Error("Hosted Tempo channel is not durable");
|
|
10157
|
+
const identity = await this.lockIdentity(tx);
|
|
10158
|
+
const parsed = parseStoreValue(state.rows[0].state_json);
|
|
10159
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Hosted Tempo state is invalid");
|
|
10160
|
+
const row = parsed;
|
|
10161
|
+
if (row.chainId !== Number(config.network.split(":")[1]) || String(row.escrowContract).toLowerCase() !== config.escrow.toLowerCase() || String(row.payee).toLowerCase() !== config.payee.toLowerCase() || String(row.token).toLowerCase() !== config.token.toLowerCase()) {
|
|
10162
|
+
throw new Error("Hosted Tempo channel has another chain or recipient");
|
|
10163
|
+
}
|
|
10164
|
+
if (operation === "close") {
|
|
10165
|
+
if (!drainId) throw new Error("Hosted Tempo close requires the original drain");
|
|
10166
|
+
const close = await tx.query(
|
|
10167
|
+
"SELECT drain_id FROM mpp_tempo_close_intents WHERE channel_id=$1",
|
|
10168
|
+
[channelId.toLowerCase()]
|
|
10169
|
+
);
|
|
10170
|
+
if (close.rows[0]?.drain_id !== drainId) throw new Error("Hosted Tempo close has no matching drain");
|
|
10171
|
+
}
|
|
10172
|
+
const highestVoucherAmount = nativeAmount(row.highestVoucherAmount);
|
|
10173
|
+
const settledOnChain = nativeAmount(row.settledOnChain);
|
|
10174
|
+
const spent = nativeAmount(row.spent);
|
|
10175
|
+
if (BigInt(settledOnChain) > BigInt(highestVoucherAmount) || BigInt(spent) > BigInt(highestVoucherAmount)) {
|
|
10176
|
+
throw new Error("Hosted Tempo settlement source has inconsistent amounts");
|
|
10177
|
+
}
|
|
10178
|
+
const original = operation === "settle" ? {
|
|
10179
|
+
highestVoucherAmount,
|
|
10180
|
+
settledOnChain,
|
|
10181
|
+
spent
|
|
10182
|
+
} : { drainId, highestVoucherAmount, settledOnChain, spent };
|
|
10183
|
+
return this.insert(
|
|
10184
|
+
tx,
|
|
10185
|
+
identity,
|
|
10186
|
+
"tempo",
|
|
10187
|
+
operation,
|
|
10188
|
+
config.network,
|
|
10189
|
+
config.escrow,
|
|
10190
|
+
channelId.toLowerCase(),
|
|
10191
|
+
original,
|
|
10192
|
+
operation === "close" ? [drainId] : [highestVoucherAmount]
|
|
10193
|
+
);
|
|
10194
|
+
}
|
|
10056
10195
|
async findPendingTempo(channelId, operation) {
|
|
10057
10196
|
const result = await this.pool.query(
|
|
10058
10197
|
`SELECT * FROM sdk_hosted_settlement_report_obligations
|
|
@@ -10062,6 +10201,36 @@ var HostedSettlementReportObligations = class {
|
|
|
10062
10201
|
);
|
|
10063
10202
|
return result.rows[0];
|
|
10064
10203
|
}
|
|
10204
|
+
/** The original close obligation reserved for this drain, while its report is outstanding. */
|
|
10205
|
+
async pendingTempoClose(channelId, drainId) {
|
|
10206
|
+
const result = await this.pool.query(
|
|
10207
|
+
`SELECT id,tx_hash,created_at_ms,close_search_before FROM sdk_hosted_settlement_report_obligations
|
|
10208
|
+
WHERE dvm_id=$1 AND scope=$2 AND rail='tempo' AND operation='close' AND channel_id=$3
|
|
10209
|
+
AND original->>'drainId'=$4 AND state NOT IN ('delivered','no_report')
|
|
10210
|
+
ORDER BY created_at_ms DESC LIMIT 1`,
|
|
10211
|
+
[this.dvmId, this.scope, channelId.toLowerCase(), drainId]
|
|
10212
|
+
);
|
|
10213
|
+
const row = result.rows[0];
|
|
10214
|
+
return row && {
|
|
10215
|
+
id: row.id,
|
|
10216
|
+
txHash: row.tx_hash,
|
|
10217
|
+
reservedAtMs: Number(row.created_at_ms),
|
|
10218
|
+
searchBefore: searchCursor(row.close_search_before)
|
|
10219
|
+
};
|
|
10220
|
+
}
|
|
10221
|
+
/**
|
|
10222
|
+
* Where the next search for this close obligation's transaction resumes. It
|
|
10223
|
+
* lives on the obligation, not the drain's close intent, because releasing
|
|
10224
|
+
* the drain deletes the intent while the obligation still needs the search.
|
|
10225
|
+
*/
|
|
10226
|
+
async recordTempoCloseSearch(id, resumeAt) {
|
|
10227
|
+
await this.pool.query(
|
|
10228
|
+
`UPDATE sdk_hosted_settlement_report_obligations
|
|
10229
|
+
SET close_search_before = LEAST(COALESCE(close_search_before, $4), $4)
|
|
10230
|
+
WHERE id=$1 AND dvm_id=$2 AND scope=$3 AND rail='tempo' AND operation='close'`,
|
|
10231
|
+
[id, this.dvmId, this.scope, resumeAt.toString()]
|
|
10232
|
+
);
|
|
10233
|
+
}
|
|
10065
10234
|
async submittedIds(rail, operation, txHash, batchId) {
|
|
10066
10235
|
if (!hex(txHash, 32)) throw new Error("Hosted effect has no exact transaction hash");
|
|
10067
10236
|
const result = await this.pool.query(
|
|
@@ -10168,17 +10337,67 @@ var HostedSettlementReportObligations = class {
|
|
|
10168
10337
|
if (!hex(txHash, 32)) throw new Error("Hosted settlement has no transaction hash");
|
|
10169
10338
|
const write = this.x402SettlementStore ? this.x402Transaction.bind(this) : (operation) => transaction(this.pool, operation);
|
|
10170
10339
|
await write(async (tx) => {
|
|
10171
|
-
|
|
10340
|
+
let row = await this.lockRow(tx, id);
|
|
10341
|
+
if (row.state === "no_report" && !row.tx_hash) {
|
|
10342
|
+
row = await this.successorOfRetiredClose(tx, row);
|
|
10343
|
+
}
|
|
10172
10344
|
if (row.tx_hash && row.tx_hash.toLowerCase() !== txHash.toLowerCase()) {
|
|
10173
10345
|
throw new Error("Hosted settlement transaction replay conflicts");
|
|
10174
10346
|
}
|
|
10175
10347
|
await tx.query(
|
|
10176
10348
|
`UPDATE sdk_hosted_settlement_report_obligations SET tx_hash=COALESCE(tx_hash,$2),
|
|
10177
10349
|
updated_at_ms=FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint WHERE id=$1`,
|
|
10178
|
-
[id, txHash.toLowerCase()]
|
|
10350
|
+
[row.id, txHash.toLowerCase()]
|
|
10179
10351
|
);
|
|
10180
10352
|
});
|
|
10181
10353
|
}
|
|
10354
|
+
/**
|
|
10355
|
+
* Close reservations that never recorded a transaction, oldest first. Each
|
|
10356
|
+
* one owes a payout report only if its close lands, so it stays unresolved
|
|
10357
|
+
* until a reconcile reads how its channel finalized.
|
|
10358
|
+
*/
|
|
10359
|
+
async unsubmittedTempoCloses(limit = 100) {
|
|
10360
|
+
const result = await this.pool.query(
|
|
10361
|
+
`SELECT id,channel_id,original->>'drainId' AS drain_id,created_at_ms,close_search_before
|
|
10362
|
+
FROM sdk_hosted_settlement_report_obligations
|
|
10363
|
+
WHERE dvm_id=$1 AND scope=$2 AND rail='tempo' AND operation='close'
|
|
10364
|
+
AND state='reserved' AND tx_hash IS NULL
|
|
10365
|
+
ORDER BY created_at_ms,id LIMIT $3`,
|
|
10366
|
+
[this.dvmId, this.scope, limit]
|
|
10367
|
+
);
|
|
10368
|
+
return result.rows.flatMap((row) => row.drain_id === null ? [] : [{
|
|
10369
|
+
id: row.id,
|
|
10370
|
+
channelId: row.channel_id,
|
|
10371
|
+
drainId: row.drain_id,
|
|
10372
|
+
reservedAtMs: Number(row.created_at_ms),
|
|
10373
|
+
searchBefore: searchCursor(row.close_search_before)
|
|
10374
|
+
}]);
|
|
10375
|
+
}
|
|
10376
|
+
/**
|
|
10377
|
+
* A retired close reservation's attempt can still be running when its
|
|
10378
|
+
* successor is reserved, and its close can land. The close is the same
|
|
10379
|
+
* channel's, so it belongs to the drain's outstanding obligation; writing it
|
|
10380
|
+
* onto the retired row would leave that obligation without a transaction and
|
|
10381
|
+
* the payout unreported.
|
|
10382
|
+
*/
|
|
10383
|
+
async successorOfRetiredClose(tx, retired) {
|
|
10384
|
+
const drainId = retired.original.drainId;
|
|
10385
|
+
if (retired.rail !== "tempo" || retired.operation !== "close" || typeof drainId !== "string") {
|
|
10386
|
+
throw new Error("Hosted settlement obligation was resolved without a transaction");
|
|
10387
|
+
}
|
|
10388
|
+
const successor = await tx.query(
|
|
10389
|
+
`SELECT id FROM sdk_hosted_settlement_report_obligations
|
|
10390
|
+
WHERE dvm_id=$1 AND scope=$2 AND rail='tempo' AND operation='close' AND channel_id=$3
|
|
10391
|
+
AND original->>'drainId'=$4 AND state NOT IN ('delivered','no_report')
|
|
10392
|
+
ORDER BY created_at_ms DESC LIMIT 1`,
|
|
10393
|
+
[this.dvmId, this.scope, retired.channel_id, drainId]
|
|
10394
|
+
);
|
|
10395
|
+
if (!successor.rows[0]) {
|
|
10396
|
+
throw new Error("Hosted Tempo close was retired and the drain has no outstanding obligation");
|
|
10397
|
+
}
|
|
10398
|
+
const locked = await this.lockRow(tx, successor.rows[0].id);
|
|
10399
|
+
return locked.state === "no_report" && !locked.tx_hash ? this.successorOfRetiredClose(tx, locked) : locked;
|
|
10400
|
+
}
|
|
10182
10401
|
async recoveryCandidates() {
|
|
10183
10402
|
const result = await this.pool.query(
|
|
10184
10403
|
`SELECT * FROM sdk_hosted_settlement_report_obligations
|
|
@@ -10201,6 +10420,10 @@ var HostedSettlementReportObligations = class {
|
|
|
10201
10420
|
}));
|
|
10202
10421
|
}
|
|
10203
10422
|
async recoverOriginalX402Refunds() {
|
|
10423
|
+
const table = await this.pool.query(
|
|
10424
|
+
"SELECT to_regclass('x402_batch_settlements') IS NOT NULL AS present"
|
|
10425
|
+
);
|
|
10426
|
+
if (!table.rows[0]?.present) return [];
|
|
10204
10427
|
const rows2 = await this.pool.query(
|
|
10205
10428
|
`SELECT obligation.id,settlement.settle_response AS response
|
|
10206
10429
|
FROM sdk_hosted_settlement_report_obligations obligation
|
|
@@ -10636,6 +10859,9 @@ var HostedSettlementReportObligations = class {
|
|
|
10636
10859
|
return { id: row.id, replayed: true };
|
|
10637
10860
|
}
|
|
10638
10861
|
};
|
|
10862
|
+
function searchCursor(value) {
|
|
10863
|
+
return value === null ? null : BigInt(value);
|
|
10864
|
+
}
|
|
10639
10865
|
|
|
10640
10866
|
// src/sdk/server/hosting-channel-evidence.ts
|
|
10641
10867
|
import { createHash as createHash7 } from "crypto";
|
|
@@ -19537,7 +19763,7 @@ async function createDVMServer(descriptor, opts) {
|
|
|
19537
19763
|
}
|
|
19538
19764
|
}
|
|
19539
19765
|
if (!opts.onJobCompleted && platformToken && platformUrl && dvmId && opts.db) {
|
|
19540
|
-
const { RevenueReporter } = await import("./revenue-reporter-
|
|
19766
|
+
const { RevenueReporter } = await import("./revenue-reporter-KDUZLD5F.js");
|
|
19541
19767
|
const reporter = new RevenueReporter(
|
|
19542
19768
|
opts.db,
|
|
19543
19769
|
platformUrl,
|
|
@@ -19547,7 +19773,7 @@ async function createDVMServer(descriptor, opts) {
|
|
|
19547
19773
|
revenueReporter = reporter;
|
|
19548
19774
|
await reporter.init();
|
|
19549
19775
|
reporter.startRetryLoop();
|
|
19550
|
-
const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-
|
|
19776
|
+
const { MemoryX402BatchStore, PayoutReporter } = await import("./payout-reporter-NN6K2IVQ.js");
|
|
19551
19777
|
payoutReporter = new PayoutReporter({
|
|
19552
19778
|
dvmId,
|
|
19553
19779
|
db: opts.db,
|
|
@@ -20004,6 +20230,9 @@ var DVMServer = class {
|
|
|
20004
20230
|
if (!certificateOnly && await control.close(command, support) === "conflict") {
|
|
20005
20231
|
return c.json({ error: "hosting_closure_conflict" }, 409);
|
|
20006
20232
|
}
|
|
20233
|
+
await this.opts.mpp?.reconcileTempoSessionCloses?.().catch(() => {
|
|
20234
|
+
diagnosticConsole.warn({ level: "tempo_routine_sweep_failed", dvm_id: this.opts.dvmId, error: void 0 });
|
|
20235
|
+
});
|
|
20007
20236
|
const evidence = await control.certificate({ ...command, action: "certificate" }, support);
|
|
20008
20237
|
if (!evidence) return c.json({ error: "hosting_closure_stale" }, 409);
|
|
20009
20238
|
return c.json(evidence);
|
|
@@ -20905,7 +21134,7 @@ var DVMServer = class {
|
|
|
20905
21134
|
// sees only the shortened list cannot tell a DVM that never did
|
|
20906
21135
|
// sessions from one whose observer is down for a minute.
|
|
20907
21136
|
...withheld.length && { withheld },
|
|
20908
|
-
realm: this.opts.mpp.realm,
|
|
21137
|
+
...this.opts.mpp.realm && { realm: this.opts.mpp.realm },
|
|
20909
21138
|
...this.opts.mpp.tempoConfiguration && {
|
|
20910
21139
|
recipient: this.opts.mpp.tempoConfiguration.recipient,
|
|
20911
21140
|
payment_token: this.opts.mpp.tempoConfiguration.paymentToken,
|