@dvmkit/sdk 0.7.0-rc.8 → 0.8.0
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-HG2CZPRJ.js → chunk-2I3OHMTY.js} +1 -1
- package/dist/{chunk-BDHJVTWA.js → chunk-55NKZQOC.js} +2 -2
- package/dist/{chunk-LQQ6H4UB.js → chunk-5CQKSLAU.js} +1 -1
- package/dist/{chunk-X5RY2G3C.js → chunk-5K5E6ME6.js} +1 -1
- package/dist/chunk-66HGCPBU.js +25 -0
- package/dist/{chunk-6RYCZLCK.js → chunk-6FEHRZO7.js} +1 -1
- package/dist/{chunk-XEOTTFLH.js → chunk-ATXDKX6F.js} +2 -2
- package/dist/{chunk-SX4WCPW7.js → chunk-BL6LRWXD.js} +2 -43
- package/dist/{chunk-FXDQMJQ2.js → chunk-DZQYQN2X.js} +31 -4
- package/dist/{chunk-FOJKZ4E7.js → chunk-EBCLWOKF.js} +1 -1
- package/dist/{chunk-KXWROQGK.js → chunk-FUJ36YDV.js} +1 -24
- package/dist/{chunk-KYCLHWKO.js → chunk-GB5XPAA5.js} +191 -5
- package/dist/{chunk-Z6EJOVIU.js → chunk-GSPV3UIM.js} +98 -51
- package/dist/{chunk-MYJOV4R4.js → chunk-I6PHA54S.js} +2 -2
- package/dist/chunk-IFWUWHTW.js +47 -0
- package/dist/{chunk-GDCIHJAR.js → chunk-P5WEZXJ2.js} +1 -1
- package/dist/{chunk-YK54BJ2L.js → chunk-PRSKC7AD.js} +1 -1
- package/dist/{chunk-KB7MEWBE.js → chunk-QPXI7YPH.js} +1 -1
- package/dist/{chunk-XO5EUHCW.js → chunk-RTFBVXCN.js} +2 -2
- package/dist/{chunk-BQP3LU2O.js → chunk-TFYY5APM.js} +1 -1
- package/dist/{chunk-WP7UX6RF.js → chunk-VYP6ADCS.js} +2 -2
- package/dist/{chunk-OFSOLHRX.js → chunk-WMGXJQI3.js} +1 -1
- package/dist/{chunk-3VFEX2Y3.js → chunk-X3JEME6P.js} +1 -1
- package/dist/{chunk-NUPEAWRM.js → chunk-XXLUIXPO.js} +1 -1
- package/dist/{chunk-BIZR5ORK.js → chunk-YPSJU3OH.js} +3 -3
- package/dist/{content-retention-U7dJM7Ph.d.ts → content-retention-D2K4MKNO.d.ts} +1 -1
- package/dist/{credit-ledger-SW4BGMNH.js → credit-ledger-AOU6S34N.js} +3 -3
- package/dist/{credit-menu-CUJdCfpt.d.ts → credit-menu-C-8g2cHT.d.ts} +5 -5
- package/dist/{fx-BvG2Wnjk.d.ts → fx-Ql4nvdbh.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-DCvJQUX0.d.ts} +1 -1
- package/dist/{lightning-backend-DeasHNOP.d.ts → lightning-backend-CY5ML3km.d.ts} +1 -1
- package/dist/{memory-credit-ledger-E4C3VQE4.js → memory-credit-ledger-4HWYINKJ.js} +4 -4
- package/dist/{mpp-setup-BYKGEYFA.js → mpp-setup-2BKTPEK6.js} +4 -2
- package/dist/{payout-reporter-3UVWPGMO.js → payout-reporter-HQSIIBFL.js} +2 -2
- package/dist/{postgres-consumed-credential-store-EZ4KETWH.js → postgres-consumed-credential-store-R7DNU6CM.js} +1 -1
- package/dist/{postgres-job-store-3YBSGLYU.js → postgres-job-store-6VGFL6XE.js} +2 -2
- package/dist/{postgres-replay-store-GIYZS4LJ.js → postgres-replay-store-SY26NUCI.js} +2 -2
- package/dist/{pricing-DG5IJ5QF.js → pricing-H2RA4X2R.js} +3 -3
- package/dist/{revenue-reporter-E56J3NZK.js → revenue-reporter-X52GPZ5U.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-BOWAn_BH.d.ts} +32 -4
- package/dist/{tempo-charge-store-WTBIX2JX.js → tempo-charge-store-NKXJ2AXE.js} +2 -2
- package/dist/{tempo-session-store-AWIFUAA6.js → tempo-session-store-ZSDQIWSU.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-MAtMp3DB.d.ts} +1 -1
- package/dist/{x402-BN26TQZ6.js → x402-Q4S36NHQ.js} +6 -5
- package/package.json +18 -3
package/NOTICE
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
@dvmkit/sdk
|
|
2
|
-
Copyright 2026
|
|
2
|
+
Copyright 2026 DVM Technologies Ltd
|
package/README.md
CHANGED
|
@@ -1,321 +1,117 @@
|
|
|
1
1
|
# @dvmkit/sdk
|
|
2
2
|
|
|
3
|
-
Build Digital Vending Machines:
|
|
3
|
+
Build Digital Vending Machines: pay-per-use HTTPS services that AI agents can find, pay, and call with no account or API key.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Pre-1.0 alpha: breaking changes are announced in the [release notes](./CHANGELOG.md); pin the version you install.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
Node.js 22.18 or later for the TypeScript quickstart.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
8
12
|
|
|
9
|
-
Create a project and install the
|
|
13
|
+
Create a project and install the current release, recording its exact version:
|
|
10
14
|
|
|
11
15
|
```sh
|
|
12
16
|
mkdir hello-dvm && cd hello-dvm
|
|
13
17
|
npm init -y
|
|
18
|
+
npm pkg set type=module
|
|
14
19
|
npm install --save-exact @dvmkit/sdk
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
## Quickstart: a free DVM
|
|
23
|
+
|
|
24
|
+
Save this as `handler.ts`:
|
|
18
25
|
|
|
19
|
-
```
|
|
26
|
+
```ts
|
|
20
27
|
import { configureDVM, z } from "@dvmkit/sdk";
|
|
21
28
|
|
|
29
|
+
export const inputSchema = z.object({ text: z.string().min(1) });
|
|
30
|
+
|
|
22
31
|
export default configureDVM({
|
|
23
32
|
name: "hello",
|
|
33
|
+
description: "Return the supplied text",
|
|
24
34
|
capability: "echo",
|
|
25
|
-
input:
|
|
35
|
+
input: inputSchema,
|
|
26
36
|
example: { text: "hello" },
|
|
27
|
-
onJob(ctx) {
|
|
37
|
+
async onJob(ctx) {
|
|
28
38
|
ctx.artifact({ mime_type: "text/plain", data: ctx.input.text });
|
|
29
39
|
ctx.complete("Echoed the input");
|
|
30
40
|
},
|
|
31
41
|
});
|
|
32
42
|
```
|
|
33
43
|
|
|
34
|
-
Save this as `server.
|
|
44
|
+
Save this as `server.ts`:
|
|
35
45
|
|
|
36
|
-
```
|
|
46
|
+
```ts
|
|
37
47
|
import { serve } from "@dvmkit/sdk/server";
|
|
38
|
-
import dvm from "./handler.mjs";
|
|
39
|
-
|
|
40
|
-
await serve(dvm, { devMode: true });
|
|
41
|
-
```
|
|
42
48
|
|
|
43
|
-
|
|
49
|
+
import dvm from "./handler.ts";
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
curl -sS http://localhost:8080/v1/job \
|
|
47
|
-
-H 'content-type: application/json' \
|
|
48
|
-
--data '{"capability":"echo","data":{"text":"hello"}}'
|
|
49
|
-
```
|
|
51
|
+
const server = await serve(dvm, { devMode: true });
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
import { createTestContext } from "@dvmkit/sdk/testing";
|
|
57
|
-
import dvm from "./handler.mjs";
|
|
58
|
-
|
|
59
|
-
test("echoes its input", async () => {
|
|
60
|
-
const ctx = createTestContext({ input: { text: "hello" } });
|
|
61
|
-
await dvm.capabilities.echo.onJob(ctx);
|
|
62
|
-
assert.equal(ctx.completed, true);
|
|
63
|
-
assert.equal(ctx.messages[0].content.data, "hello");
|
|
64
|
-
});
|
|
53
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
54
|
+
process.once(signal, () => {
|
|
55
|
+
void server.close().then(() => process.exit(0));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
65
58
|
```
|
|
66
59
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
## Public entry points
|
|
70
|
-
|
|
71
|
-
| Import | Use it for |
|
|
72
|
-
| --- | --- |
|
|
73
|
-
| `@dvmkit/sdk` | DVM descriptors, handler context, bundled Zod, pricing, signing and safe-fetch helpers |
|
|
74
|
-
| `@dvmkit/sdk/server` | Node hosting, Postgres stores, signed-request auth, compatibility gates and payment infrastructure |
|
|
75
|
-
| `@dvmkit/sdk/testing` | Deterministic handler contexts and in-memory stores |
|
|
76
|
-
|
|
77
|
-
The generated [API reference](./docs/API.md) lists every declaration under these three entries. Deep imports from `dist/` are unsupported.
|
|
78
|
-
|
|
79
|
-
## Verified payments in development
|
|
80
|
-
|
|
81
|
-
`serve()` returns a `paymentModes` snapshot describing whether each configured rail is verifying, unavailable, or absent. A priced development server skips verification only when it has no configured payment rail. Configuring an unusable rail fails closed instead of turning the request into a synthetic development payment.
|
|
82
|
-
|
|
83
|
-
Cashu development without Postgres is limited to known test or loopback mints. It uses a fresh lock identity and process-local accounting on every boot, so proofs, balances, and jobs do not survive restart. Production Cashu remains PostgreSQL-backed. Both paths require valid mint-provided NUT-12 DLEQ evidence on every proof before accepting its value; older tokens without that evidence must be replaced with fresh tokens from a compatible mint.
|
|
84
|
-
|
|
85
|
-
Builders can pass `onPaidJobCompleted` to observe settled customer charges after successful completion. The event contains `jobId`, verified rail, checked integer `amountMicro`, the credit currency, and whether configuration identifies test funds. Reconciliation can repeat an event, so consumers deduplicate by `jobId`. Free jobs, synthetic development payments, failed jobs, and released draws do not emit.
|
|
86
|
-
|
|
87
|
-
## Production next steps
|
|
88
|
-
|
|
89
|
-
- Use Postgres through `DATABASE_URL`; production boot refuses an in-memory job store unless you deliberately supply another durable implementation.
|
|
90
|
-
- Add `auth: secp256k1Auth()` for caller-owned state. The host automatically uses a Postgres replay store when one is available; a per-process replay window is unsafe across replicas.
|
|
91
|
-
- Configure only the rails you operate: Cashu, x402, and Tempo are job payment methods. Lightning is a receive-only prepaid-credit funding leg. Keep every private key, wallet connection, and platform token in your secret store rather than source.
|
|
92
|
-
- A `credit` block opts into reusable prepaid balances and their reclaim obligations. Reusable channels return undrawn collateral through their channel lifecycle; one-payment stablecoin funding is disabled unless `allowOneShotStablecoin: true` because it creates manual refunds. Run and monitor the documented reclaim/payout worker.
|
|
93
|
-
- Honour `ctx.signal`, use `ctx.fetch` for abortable provider calls, and declare provider cost with `ctx.cost` only after the durable step that incurred it.
|
|
94
|
-
|
|
95
|
-
Reusable x402 channels default to a 24-hour payer withdrawal grace (86400 seconds) on every network. For Base Sepolia (`eip155:84532`) acceptance tests, set `x402.batchSettlement.withdrawDelay: 900` or `DVMKIT_X402_WITHDRAW_DELAY_SECONDS=900` with `DVMKIT_X402_BATCH_SETTLEMENT=true`. Explicit delays must be integer seconds and at least 900 on Base Sepolia; Base mainnet retains the 86400 minimum even in development mode and still requires self-relay. The grace is bound into each channel's immutable configuration: changing the server setting affects new channels, not existing deposits. A shorter testnet run exercises early-withdrawal refusal and post-deadline recovery; retain a separate 24-hour run to cover next-day recovery.
|
|
96
|
-
|
|
97
|
-
For Bitcoin credit monitoring, `GET /admin/credit/bitcoin-liability` reports exact in-kind obligations in satoshis. Uncovered and basisless credits need live-rate pricing on reclaim, so their whole balances appear in `uncovered_by_currency` and `unbacked_by_currency` as `{ currency, micro, credits }` entries. Sum the credit counts if useful, but display or price each currency separately. Older consumers must replace the former `uncovered_micro` and `unbacked_micro` scalar reads; the [portable response fixture](./protocol-fixtures/bitcoin-liability.json) shows the full shape.
|
|
98
|
-
|
|
99
|
-
The public [SDK guide](https://dvmkit.com/docs/sdk) covers signed requests, custom routes, rail configuration, credit reclaim, persistence, and deployment in depth. The [protocol guide](https://dvmkit.com/docs/protocol) specifies the HTTP contract.
|
|
100
|
-
|
|
101
|
-
## Diagnostic retention
|
|
102
|
-
|
|
103
|
-
[Payment diagnostics](./docs/DIAGNOSTICS.md) documents the closed failure categories, stages, and top-up causes retained by safe logging sinks.
|
|
104
|
-
|
|
105
|
-
The SDK advertises 730 days of retention for sanitized diagnostics in every existing span dataset: `platform`, `cashu`, `lightning`, `stripe`, `x402`, `tempo`, `isolate`, `audit`, and `cron`. Dataset names, producer permissions, and query semantics remain unchanged; only operator services may write to `audit`.
|
|
106
|
-
|
|
107
|
-
This metadata describes the retention schedule. The SDK does not automatically delete diagnostics or override platform destruction or case controls. The platform must enforce the schedule and those controls.
|
|
108
|
-
|
|
109
|
-
## Repository development
|
|
110
|
-
|
|
111
|
-
Use the Node.js release in `.nvmrc` (Node 22). Install with `npm ci`, then run the same gate CI runs with `npm run verify`. The gate checks repository metadata in the checkout, copies only tracked and unignored source into an isolated directory, installs its locked dependencies, and runs lint, build, typecheck (including the release scripts under `checkJs`), unit and database tests, API, package, consumer, packed-artifact safety, and release checks there. The clean source tree owns the complete build and test pass, so verification never depends on the checkout's `node_modules` or pre-existing `dist` and does not repeat the full suite; after every check passes, its verified `dist` replaces the checkout's build output for release packaging.
|
|
112
|
-
|
|
113
|
-
`DATABASE_URL` is optional locally. Without it the database suite is skipped rather than passed, and `verify` says so in its summary, because a skipped suite verified nothing and must not be reported as green. Point `DATABASE_URL` at a scratch Postgres to run it, and pass `npm run verify -- --require-database` to make a missing one a failure, which is what CI does. That mode also requires the local test mint started by `node scripts/start-cashu-mint.mjs`.
|
|
114
|
-
|
|
115
|
-
The public surface is exactly `@dvmkit/sdk`, `@dvmkit/sdk/server`, and `@dvmkit/sdk/testing`.
|
|
116
|
-
|
|
117
|
-
When you pass `env` to `serve()` or `createDVMHost()`, that object is the complete host
|
|
118
|
-
environment. SDK configuration, including provenance, builder and owner identity, and
|
|
119
|
-
mint-health defaults, reads only from it. Omit `env` to use `process.env`.
|
|
120
|
-
|
|
121
|
-
## Terminal evidence
|
|
122
|
-
|
|
123
|
-
New job receipts keep schema version `1` and add optional signed `timing` with `accepted_at_ms`, `terminal_at_ms`, and cumulative `caller_wait_ms`. Acceptance and termination use Unix milliseconds; `issued_at` remains signing time in Unix seconds. Non-completed receipts also include SDK-derived `ended_by`: explicit caller cancellation and unanswered prompt/payment expiry are `caller`; handler failures, worker death, and idle expiry during processing or working are `provider`. Handlers cannot choose this attribution. Legacy receipts omit these fields and still verify. Jobs accepted before this change, including jobs still active during an upgrade, omit the additions and do not produce terminal reports because earlier caller waits are unknown; the SDK does not invent historical fault evidence.
|
|
124
|
-
|
|
125
|
-
Hosted servers with platform URL, token, DVM identity and durable database configured enqueue every terminal job, including free jobs, through the durable report outbox to `/_internal/job-terminal`. The payload contains the DVM/job identity, capability, outcome, attribution, payment-presence flag, timing and optional receipt sequence. It contains no caller identity, payment references or amounts, input, or output. The permanent deduplication claim and outbox insert commit together; startup and periodic recovery cover a crash after terminal persistence. Deliveries can repeat, so the platform deduplicates by DVM/job identity.
|
|
126
|
-
|
|
127
|
-
Deploy the platform endpoint before deploying report-emitting servers. These reports use the existing revenue transport policy: transport/5xx failures retry and abandon after 24 hours; 4xx responses, including 404, become permanently stale. The portable receipt and report contract is in `protocol-fixtures/job-terminal-evidence.json`; compatibility is tested with the pinned published `@dvmkit/sdk@0.1.5-rc.9` verifier.
|
|
128
|
-
|
|
129
|
-
## Compatibility and versioning
|
|
130
|
-
|
|
131
|
-
This is pre-1.0 alpha software and the promises match that. Breaking changes can land in any release, they are announced in the release notes for the release that carries them, and there is no long-term support release. Security fixes are made on the latest published release rather than backported.
|
|
132
|
-
|
|
133
|
-
Nothing reachable only through a deep path into `dist/` is public. `npm run check:api` pins every exported declaration name against `api-surface.json` and the generated API reference, so a change to the supported surface is a deliberate, reviewed act rather than a side effect.
|
|
134
|
-
|
|
135
|
-
Wire compatibility with callers is separate from the package version and is declared per endpoint. A server built on this SDK answers `DVM-Protocol-Version: 1`, and an individual endpoint may require a set of named capability tokens the caller advertises, a minimum caller version, or both. Capability tokens are additive, so an endpoint that declares no requirement stays reachable by a caller that sends no compatibility headers at all. A caller that cannot meet what an endpoint declares gets HTTP 426 naming the required capabilities and, where one is declared, the minimum version. The protocol is specified at [dvmkit.com/docs/protocol](https://dvmkit.com/docs/protocol).
|
|
136
|
-
|
|
137
|
-
## Job credentials
|
|
138
|
-
|
|
139
|
-
An unauthenticated `POST /v1/job` returns a one-time `job_token` alongside the `job_id`. Callers must send it as the `X-Job-Token` header on subsequent status reads, message reads (JSON or SSE), message writes, and cancellation. A request with no credential gets a 401 `job_credential_required`; a wrong credential remains the opaque 404 `Job not found` response.
|
|
140
|
-
|
|
141
|
-
When a caller has lost its local job record, the standalone `@dvmkit/dvm-cli` can use the provider endpoint and token explicitly. Prefer environment variables so the bearer token does not enter shell history or the process list:
|
|
60
|
+
Start the server:
|
|
142
61
|
|
|
143
62
|
```sh
|
|
144
|
-
|
|
145
|
-
DVM_JOB_TOKEN=<64-character-hex-token> \
|
|
146
|
-
dvm status <job-id>
|
|
63
|
+
node server.ts
|
|
147
64
|
```
|
|
148
65
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
If an upgrade broke something for you, an [API compatibility report](https://github.com/dvmkit/sdk/issues/new/choose) is the most useful thing you can send us. It tells us which parts of the published surface people actually build on, which no test of ours can.
|
|
152
|
-
|
|
153
|
-
## Job identity field
|
|
66
|
+
Leave it running. In another terminal, submit a job:
|
|
154
67
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
A handler can declare what a job cost **you**, the builder, to serve — a vendor API call, a GPU minute, a per-request licence fee — and the SDK reports it to the platform alongside what the caller paid, so your dashboard can state a margin instead of guessing at one.
|
|
160
|
-
|
|
161
|
-
```ts
|
|
162
|
-
const transcript = await ctx.step("transcribe", async () => {
|
|
163
|
-
const result = await whisper(audio);
|
|
164
|
-
ctx.cost({ amount: result.seconds * 0.0001, currency: "usd" });
|
|
165
|
-
return result;
|
|
166
|
-
});
|
|
167
|
-
ctx.artifact({ mime_type: "text/plain", data: transcript.text });
|
|
68
|
+
```sh
|
|
69
|
+
curl -sS http://localhost:8080/v1/job \
|
|
70
|
+
-H 'content-type: application/json' \
|
|
71
|
+
--data '{"capability":"echo","data":{"text":"hello"}}'
|
|
168
72
|
```
|
|
169
73
|
|
|
170
|
-
|
|
74
|
+
The response includes a generated `job_id` and `job_token`, `"status":"completed"`, and `"summary":"Echoed the input"`. The server listens at `http://localhost:8080`; stop it with Ctrl-C. `devMode` lets this free example run without Postgres.
|
|
171
75
|
|
|
172
|
-
|
|
76
|
+
The files above are copied verbatim from [`examples/quickstart`](./examples/quickstart). See the runnable [authenticated custom route](./examples/authenticated-route) and [payments and lifecycle](./examples/payments-and-lifecycle) examples for the next step.
|
|
173
77
|
|
|
174
|
-
|
|
175
|
-
- **Declare the cost where you incur it, including inside `ctx.step`.** The step cache carries those declarations with the result and applies them once when a replay skips the body.
|
|
176
|
-
- **Every terminal job reports its declared cost.** A paid completion carries the cost on its revenue report. A free completion, failure, or cancellation sends a separate cost-only report and does not create revenue.
|
|
78
|
+
## What's in the package
|
|
177
79
|
|
|
178
|
-
|
|
80
|
+
| Import | Purpose |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| `@dvmkit/sdk` | DVM descriptors, handler context, bundled Zod, pricing, signing, and safe-fetch helpers |
|
|
83
|
+
| `@dvmkit/sdk/server` | Node hosting, Postgres stores, signed-request auth, and payment infrastructure |
|
|
84
|
+
| `@dvmkit/sdk/testing` | Deterministic handler contexts and in-memory stores |
|
|
179
85
|
|
|
180
|
-
|
|
86
|
+
`@dvmkit/sdk/internal` also exists for the dvmkit platform; it has no stability promise and can change in any release.
|
|
181
87
|
|
|
182
|
-
|
|
88
|
+
The generated [API reference](./docs/API.md) lists the supported declarations. Deep imports from `dist/` are unsupported.
|
|
183
89
|
|
|
184
|
-
|
|
185
|
-
capabilities: {
|
|
186
|
-
synthesise: {
|
|
187
|
-
onQuote: { schema, handler: quoteBySize },
|
|
188
|
-
rates: [
|
|
189
|
-
{ label: "balanced", amount: "0.00015", currency: "USD", per: "character" },
|
|
190
|
-
{ label: "expressive", amount: "0.00030", currency: "USD", per: "character" },
|
|
191
|
-
],
|
|
192
|
-
onJob,
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
```
|
|
90
|
+
## Next steps
|
|
196
91
|
|
|
197
|
-
|
|
92
|
+
- [SDK guide](https://dvmkit.com/docs/sdk): add authentication, custom routes, payments, and persistence.
|
|
93
|
+
- [Protocol guide](https://dvmkit.com/docs/protocol): understand the HTTP contract callers use.
|
|
94
|
+
- [Builder guide](https://dvmkit.com/docs/build): scaffold and deploy a service; install the builder CLI with `npm install --global @dvmkit/dvmctl`.
|
|
198
95
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
- 1 to 5 entries, on a capability with `price` or `onQuote`. A free capability cannot declare rates.
|
|
202
|
-
- `amount` is decimal dollars as a string, above zero and at most 12 characters: `"0.00015"`, not `0.00015` or `"$0.00015"`.
|
|
203
|
-
- `currency` is `"USD"`.
|
|
204
|
-
- `per` names the unit (`"character"`, `"episode"`, `"GB per day stored"`) and `label`, which is optional, names a tier. Each is 1 to 24 characters with no leading or trailing spaces.
|
|
205
|
-
- No other keys.
|
|
206
|
-
|
|
207
|
-
The [portable fixture](./protocol-fixtures/capability-rates.json) carries the same limits and the accepted and refused examples, for clients that parse the field.
|
|
96
|
+
[Operating a DVM](./docs/OPERATING.md) covers production setup, verified development payments, credit liabilities, diagnostics, retention, content holds, and financial exports. [Protocol notes](./docs/PROTOCOL-NOTES.md) cover compatibility, job credentials, identity, terminal evidence, costs, and rate cards.
|
|
208
97
|
|
|
209
98
|
## Contributing
|
|
210
99
|
|
|
211
|
-
|
|
100
|
+
Issues and discussions are welcome. Unsolicited pull requests are closed; read [CONTRIBUTING.md](./CONTRIBUTING.md) and the [Code of Conduct](./CODE_OF_CONDUCT.md) before participating.
|
|
212
101
|
|
|
213
|
-
|
|
102
|
+
## Security
|
|
214
103
|
|
|
215
|
-
|
|
104
|
+
Report vulnerabilities privately as described in [SECURITY.md](./SECURITY.md).
|
|
216
105
|
|
|
217
|
-
##
|
|
106
|
+
## Support
|
|
218
107
|
|
|
219
|
-
|
|
108
|
+
Use [SUPPORT.md](./SUPPORT.md) to find the right place for questions and bug reports.
|
|
220
109
|
|
|
221
|
-
|
|
110
|
+
## Trademarks
|
|
222
111
|
|
|
223
|
-
|
|
112
|
+
The Apache License grants no rights in the dvmkit name or branding; identify your own products and forks without implying endorsement.
|
|
113
|
+
Permission requests go through [support](./SUPPORT.md#email); descriptive use of “digital vending machine” or “DVM” is not restricted.
|
|
224
114
|
|
|
225
115
|
## License
|
|
226
116
|
|
|
227
117
|
Apache-2.0. See [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
|
|
228
|
-
|
|
229
|
-
## Job content retention and case holds
|
|
230
|
-
|
|
231
|
-
`configureDVM` defaults `jobRetentionDays` to 730. Set a shorter or longer whole-day
|
|
232
|
-
window explicitly, or `0` to disable automatic age expiry. Only completed, failed,
|
|
233
|
-
and cancelled jobs are eligible. The terminal `lastActivityAt` is frozen: reads,
|
|
234
|
-
receipt delivery, late messages, and re-saving an outcome do not renew it. The
|
|
235
|
-
sweep removes input, params, summary, messages, state, caches, and bearer proofs;
|
|
236
|
-
it preserves durable status, signed receipts, payment amounts, and recovery linkage.
|
|
237
|
-
|
|
238
|
-
Authenticated job submit, request-result, status and JSON message responses expose
|
|
239
|
-
`content_expires_at` (epoch milliseconds or `null`) and `content_available` (boolean).
|
|
240
|
-
A null deadline means the job is active, expiry is disabled, or an active hold
|
|
241
|
-
suppresses it. A past deadline can still have available content until the sweep
|
|
242
|
-
runs. Missing fields on an older service or unsupported custom store mean unknown.
|
|
243
|
-
SSE messages retain their existing event format; read job status for expiry.
|
|
244
|
-
|
|
245
|
-
With durable service-admin configuration, use signed `Authorization: P2PK`
|
|
246
|
-
requests on the service-local routes:
|
|
247
|
-
|
|
248
|
-
| Method | Path | JSON body |
|
|
249
|
-
| --- | --- | --- |
|
|
250
|
-
| GET | `/admin/jobs/:jobId/content-holds` | none |
|
|
251
|
-
| POST | `/admin/jobs/:jobId/content-holds` | `purpose`, `reason`, `review_due_at` |
|
|
252
|
-
| POST | `/admin/jobs/:jobId/content-holds/:holdId/renew` | `reason`, `review_due_at` |
|
|
253
|
-
| POST | `/admin/jobs/:jobId/content-holds/:holdId/clear` | `reason` |
|
|
254
|
-
|
|
255
|
-
Purpose is `support` or `dispute`; reason is 1–1000 characters and review dates
|
|
256
|
-
are future epoch millisecond integers. Unknown body fields are rejected. Hold IDs,
|
|
257
|
-
actor public keys, and creation/clear times are server-derived. Authentication uses
|
|
258
|
-
the existing method/path/service-bound admin challenge, current and grace-period
|
|
259
|
-
keys, and atomic durable nonce consumption. A hold request does not grant access
|
|
260
|
-
to job content. Authentication failure returns 401 with a reason; an unsupported
|
|
261
|
-
store returns `content_holds_unavailable` (503), missing jobs return 404, and a
|
|
262
|
-
redacted job or inactive hold returns 409. Holds cannot restore redacted content.
|
|
263
|
-
|
|
264
|
-
Responses include `job_id`, caller content metadata, `baseline_content_expires_at`,
|
|
265
|
-
`holds`, and append-only `events`. Each hold contains `id`, `job_id`, `purpose`,
|
|
266
|
-
`reason`, original `actor`, `created_at`, `review_due_at`, `cleared_at`, and computed
|
|
267
|
-
`review_overdue`. Each event contains `id`, `hold_id`, `job_id`, `action`, `actor`,
|
|
268
|
-
`reason`, `at`, `review_due_at`. Renew and clear preserve the original actor and
|
|
269
|
-
creation date; their authenticated actors are in history. Any active hold suppresses
|
|
270
|
-
the effective deadline. Overdue review flags work; it does not release the hold.
|
|
271
|
-
The baseline remains visible for review. Clearing the last hold restores the
|
|
272
|
-
baseline and can make content immediately eligible for the next sweep.
|
|
273
|
-
|
|
274
|
-
`PostgresJobStore.init()` adds `job_content_holds` and `job_content_hold_events`
|
|
275
|
-
with job-scoped indexes; existing jobs need no expiry column or backfill. Placement
|
|
276
|
-
and sweep lock the same job row and check redaction/terminal activity/active holds
|
|
277
|
-
inside the transaction. Admin operations refuse jobs bound to another service;
|
|
278
|
-
legacy unbound rows belong to the local service database. A committed hold wins any later competing sweep; if
|
|
279
|
-
redaction wins first, placement fails. `MemoryJobStore` supplies the same contract
|
|
280
|
-
for development. Custom stores must implement `ContentHoldStore` and atomic
|
|
281
|
-
redaction fencing to support holds and truthful metadata.
|
|
282
|
-
|
|
283
|
-
Before explicitly destroying a service database, use your existing database
|
|
284
|
-
connection/proxy and the packaged `recipes/postgres/financial-export-v1.sql`:
|
|
285
|
-
|
|
286
|
-
```sh
|
|
287
|
-
psql "$DATABASE_URL" -X -q -t -A -v ON_ERROR_STOP=1 \
|
|
288
|
-
-f node_modules/@dvmkit/sdk/recipes/postgres/financial-export-v1.sql \
|
|
289
|
-
> financial-evidence.json
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
The versioned JSON export takes a consistent read-only snapshot and includes signed
|
|
293
|
-
receipt tuples, ledger balances/amounts/currencies, job/payment links, and recovery
|
|
294
|
-
outcomes. Funding lots preserve every deposit's original sats and credit basis,
|
|
295
|
-
including depleted lots. Draws retain both the original allocation and settled
|
|
296
|
-
amount. Refunds retain fulfilment costs and funding-lot linkage. Optional channel
|
|
297
|
-
projections preserve amounts and settlement links while excluding authorization
|
|
298
|
-
signatures and vouchers; Tempo bigint amounts are decimal strings.
|
|
299
|
-
Unsigned JSON blocks use typed financial projections: unsupported fields and
|
|
300
|
-
objects or arrays in scalar positions are excluded. Optional ledger tables absent from a service become empty arrays. It
|
|
301
|
-
excludes raw input/output, bearer tokens/proofs, payout descriptors, and operational
|
|
302
|
-
credentials. Signed receipts may retain their signed terminal reason and result
|
|
303
|
-
hash; supported version-1 job, funding, and drain receipts are returned unchanged.
|
|
304
|
-
Unsupported receipt versions, kinds, fields, or nested extensions fail the entire
|
|
305
|
-
export before any JSON is emitted. Update the recipe for that schema before
|
|
306
|
-
destruction; a failed export is not retained financial evidence. Verify receipt
|
|
307
|
-
signatures and check the file before destruction, then keep it securely with this recipe
|
|
308
|
-
and the service's public receipt-key attestation. The builder owns this export;
|
|
309
|
-
the platform retains its separate financial history. An export does not settle
|
|
310
|
-
caller balances or refunds: resolve liabilities through their existing lifecycle.
|
|
311
|
-
Case holds affect age expiry, not explicit erasure or contract/lifecycle destruction.
|
|
312
|
-
The active-service window does not extend any contractual maximum for copies after
|
|
313
|
-
explicit destruction, including a 90-day post-destruction limit where applicable.
|
|
314
|
-
|
|
315
|
-
The wire fields and routes are additive within protocol version 1. Existing explicit
|
|
316
|
-
retention settings retain their value. The new default also applies to existing
|
|
317
|
-
unredacted terminal rows; previously removed content stays unavailable. Upgrade
|
|
318
|
-
all replicas before relying on holds: older SDKs do not recognize the hold fence.
|
|
319
|
-
Do not downgrade or mix old replicas with active holds unless automatic sweeping
|
|
320
|
-
is disabled on those replicas. The SQL export format is versioned independently
|
|
321
|
-
of the package and is not a raw database backup or a content archive.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
callerLoggers
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-66HGCPBU.js";
|
|
4
4
|
import {
|
|
5
5
|
diagnosticConsole,
|
|
6
6
|
projectSpanRow
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DZQYQN2X.js";
|
|
8
8
|
|
|
9
9
|
// src/lib/x402/env.ts
|
|
10
10
|
import { isAddress } from "viem";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/lib/caller-loggers.ts
|
|
2
|
+
var callerLoggers = buildNoopLoggers();
|
|
3
|
+
function initCallerLoggers(nextLoggers) {
|
|
4
|
+
callerLoggers = nextLoggers;
|
|
5
|
+
}
|
|
6
|
+
function buildNoopLoggers() {
|
|
7
|
+
const noopLogger = {
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function -- standalone caller has no exporter
|
|
9
|
+
info() {
|
|
10
|
+
},
|
|
11
|
+
async span(_name, _attrs, fn) {
|
|
12
|
+
return fn();
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
return {
|
|
16
|
+
cashu: noopLogger,
|
|
17
|
+
lightning: noopLogger,
|
|
18
|
+
x402: noopLogger
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
callerLoggers,
|
|
24
|
+
initCallerLoggers
|
|
25
|
+
};
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
} from "./chunk-62FI3NJF.js";
|
|
4
4
|
import {
|
|
5
5
|
callerLoggers
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-66HGCPBU.js";
|
|
7
7
|
import {
|
|
8
8
|
diagnosticConsole
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-DZQYQN2X.js";
|
|
10
10
|
|
|
11
11
|
// src/observability/payment-diagnostics.ts
|
|
12
12
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -1,51 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
initCallerLoggers
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-66HGCPBU.js";
|
|
4
4
|
import {
|
|
5
5
|
bindDiagnosticCapabilityAuthority,
|
|
6
6
|
bindDiagnosticIdentityAuthority,
|
|
7
|
-
emitDiagnostic,
|
|
8
7
|
extendDiagnosticIdentityAuthority,
|
|
9
8
|
projectDiagnosticFields,
|
|
10
9
|
projectSpanRow
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
|
|
13
|
-
// src/sdk/logger.ts
|
|
14
|
-
function createConsoleLogger(_jobId) {
|
|
15
|
-
return {
|
|
16
|
-
debug(_message, data) {
|
|
17
|
-
emitDiagnostic("debug", "diagnostic.message", data);
|
|
18
|
-
},
|
|
19
|
-
info(_message, data) {
|
|
20
|
-
emitDiagnostic("info", "diagnostic.message", data);
|
|
21
|
-
},
|
|
22
|
-
warn(_message, data) {
|
|
23
|
-
emitDiagnostic("warn", "diagnostic.message", data);
|
|
24
|
-
},
|
|
25
|
-
error(_message, data) {
|
|
26
|
-
emitDiagnostic("error", "diagnostic.message", data);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function createStdoutLogger(context) {
|
|
31
|
-
const emit = (level) => (_message, data) => {
|
|
32
|
-
emitDiagnostic(level, "diagnostic.message", {
|
|
33
|
-
...projectDiagnosticFields(context),
|
|
34
|
-
...projectDiagnosticFields(data)
|
|
35
|
-
});
|
|
36
|
-
};
|
|
37
|
-
return {
|
|
38
|
-
debug: emit("debug"),
|
|
39
|
-
info: emit("info"),
|
|
40
|
-
warn: emit("warn"),
|
|
41
|
-
error: emit("error")
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function createNoopLogger() {
|
|
45
|
-
const noop2 = () => {
|
|
46
|
-
};
|
|
47
|
-
return { debug: noop2, info: noop2, warn: noop2, error: noop2 };
|
|
48
|
-
}
|
|
10
|
+
} from "./chunk-DZQYQN2X.js";
|
|
49
11
|
|
|
50
12
|
// src/observability/context.ts
|
|
51
13
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -452,9 +414,6 @@ function buildNoopLoggers() {
|
|
|
452
414
|
}
|
|
453
415
|
|
|
454
416
|
export {
|
|
455
|
-
createConsoleLogger,
|
|
456
|
-
createStdoutLogger,
|
|
457
|
-
createNoopLogger,
|
|
458
417
|
randomTraceId,
|
|
459
418
|
withTraceContext,
|
|
460
419
|
currentContext,
|
|
@@ -412,6 +412,7 @@ var diagnostic_catalogue_v1_default = {
|
|
|
412
412
|
"tempo_observer_health_unavailable",
|
|
413
413
|
"tempo_observer_scan_failed",
|
|
414
414
|
"tempo_refund_amount_unreported",
|
|
415
|
+
"tempo_replay_mark_failed",
|
|
415
416
|
"tempo_routine_sweep",
|
|
416
417
|
"tempo_routine_sweep_failed",
|
|
417
418
|
"tempo_session_advertised_refresh_failed",
|
|
@@ -419,6 +420,7 @@ var diagnostic_catalogue_v1_default = {
|
|
|
419
420
|
"tempo_session_close_failed",
|
|
420
421
|
"tempo_session_close_failure_reconcile_failed",
|
|
421
422
|
"tempo_session_close_recovery_failed",
|
|
423
|
+
"tempo_session_close_recovery_unmatched",
|
|
422
424
|
"tempo_session_open_refused",
|
|
423
425
|
"tempo_session_settled",
|
|
424
426
|
"tempo_session_withheld",
|
|
@@ -779,6 +781,7 @@ var diagnostic_catalogue_v1_default = {
|
|
|
779
781
|
"hardened",
|
|
780
782
|
"head_status",
|
|
781
783
|
"hint",
|
|
784
|
+
"hosting_identity_revoke_failed",
|
|
782
785
|
"ingest_concurrency",
|
|
783
786
|
"ingest_global_concurrency",
|
|
784
787
|
"ingest_ip_concurrency",
|
|
@@ -1067,6 +1070,25 @@ var diagnostic_catalogue_v1_default = {
|
|
|
1067
1070
|
"UA-14",
|
|
1068
1071
|
"UA-40",
|
|
1069
1072
|
"UA-43"
|
|
1073
|
+
],
|
|
1074
|
+
submitIdentitySources: [
|
|
1075
|
+
"header",
|
|
1076
|
+
"header_absent",
|
|
1077
|
+
"header_invalid",
|
|
1078
|
+
"not_extracted"
|
|
1079
|
+
],
|
|
1080
|
+
fundedRuntimeReclaims: [
|
|
1081
|
+
"closed"
|
|
1082
|
+
],
|
|
1083
|
+
suspensionReasons: [
|
|
1084
|
+
"request_limit",
|
|
1085
|
+
"dvm_limit",
|
|
1086
|
+
"behaviour",
|
|
1087
|
+
"free_tier_idle",
|
|
1088
|
+
"admin_org_suspend",
|
|
1089
|
+
"builder_pause",
|
|
1090
|
+
"withdrawn",
|
|
1091
|
+
"operator"
|
|
1070
1092
|
]
|
|
1071
1093
|
};
|
|
1072
1094
|
|
|
@@ -1080,6 +1102,12 @@ var PAYMENT_ENUMS = {
|
|
|
1080
1102
|
failure_stage: new Set(diagnostic_catalogue_v1_default.paymentFailureStages),
|
|
1081
1103
|
phase: new Set(diagnostic_catalogue_v1_default.paymentPhases)
|
|
1082
1104
|
};
|
|
1105
|
+
var ROUTER_ENUMS = {
|
|
1106
|
+
submit_identity_source: new Set(diagnostic_catalogue_v1_default.submitIdentitySources),
|
|
1107
|
+
funded_runtime_reclaim: new Set(diagnostic_catalogue_v1_default.fundedRuntimeReclaims),
|
|
1108
|
+
suspended_reclaim: new Set(diagnostic_catalogue_v1_default.suspensionReasons),
|
|
1109
|
+
suspended_admin: new Set(diagnostic_catalogue_v1_default.suspensionReasons)
|
|
1110
|
+
};
|
|
1083
1111
|
var EVENTS = new Set(diagnostic_catalogue_v1_default.events);
|
|
1084
1112
|
var ROUTE_TEMPLATES = [...diagnostic_catalogue_v1_default.routeTemplates].sort(
|
|
1085
1113
|
compareRouteTemplates
|
|
@@ -1120,7 +1148,6 @@ var REDACTED_SEGMENT_RE = /^:(?:uuid|hex|n|b58|token)$/;
|
|
|
1120
1148
|
var SERVICE_NAMES = /* @__PURE__ */ new Set([
|
|
1121
1149
|
"platform-api",
|
|
1122
1150
|
"router",
|
|
1123
|
-
"dvmadmin",
|
|
1124
1151
|
"admin",
|
|
1125
1152
|
"unknown"
|
|
1126
1153
|
]);
|
|
@@ -1280,9 +1307,9 @@ function projectFields(input, dataset, authority, identityAuthority) {
|
|
|
1280
1307
|
return output;
|
|
1281
1308
|
}
|
|
1282
1309
|
function projectField(key, value, dataset, authority, identityAuthority) {
|
|
1283
|
-
const
|
|
1284
|
-
if (
|
|
1285
|
-
return typeof value === "string" &&
|
|
1310
|
+
const closedEnum = Object.hasOwn(PAYMENT_ENUMS, key) ? PAYMENT_ENUMS[key] : Object.hasOwn(ROUTER_ENUMS, key) ? ROUTER_ENUMS[key] : void 0;
|
|
1311
|
+
if (closedEnum)
|
|
1312
|
+
return typeof value === "string" && closedEnum.has(value) ? value : void 0;
|
|
1286
1313
|
if (BOOLEAN_FIELDS.has(key))
|
|
1287
1314
|
return typeof value === "boolean" ? value : void 0;
|
|
1288
1315
|
if (INTEGER_FIELDS.has(key))
|