@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.
Files changed (57) hide show
  1. package/NOTICE +1 -1
  2. package/README.md +57 -261
  3. package/dist/{chunk-5CSR33B5.js → chunk-2U6LAASN.js} +1 -1
  4. package/dist/chunk-66HGCPBU.js +25 -0
  5. package/dist/{chunk-QM2OS3NJ.js → chunk-7PM4R4GD.js} +1 -1
  6. package/dist/{chunk-YE53E4BT.js → chunk-AGE5XZKE.js} +1 -1
  7. package/dist/{chunk-RJFBBEWQ.js → chunk-ALWVJ2RB.js} +1 -1
  8. package/dist/{chunk-FYETJDBB.js → chunk-AVV5SYGO.js} +2 -2
  9. package/dist/{chunk-5PLQOC4B.js → chunk-BFGQG4WY.js} +9 -1
  10. package/dist/{chunk-IOKEKMIU.js → chunk-FM6QAGJC.js} +2 -2
  11. package/dist/{chunk-KXWROQGK.js → chunk-FUJ36YDV.js} +1 -24
  12. package/dist/{chunk-6JZDFYQL.js → chunk-G3MPHW5I.js} +1 -1
  13. package/dist/{chunk-2HAFZ5GC.js → chunk-IHQUBATH.js} +2 -2
  14. package/dist/{chunk-YS6PWVCZ.js → chunk-ILBSZMJL.js} +1 -1
  15. package/dist/{chunk-RGSWVNQG.js → chunk-IOQIXRGM.js} +2 -43
  16. package/dist/{chunk-YNHXOONR.js → chunk-J5J63WDC.js} +1 -1
  17. package/dist/{chunk-G7IXOVKV.js → chunk-JIZ4ZIXX.js} +331 -102
  18. package/dist/{chunk-ZLWPEIE2.js → chunk-NA2KOR6G.js} +1 -1
  19. package/dist/{chunk-2DE32RAW.js → chunk-NU5RWJ5B.js} +1 -1
  20. package/dist/chunk-OP7VCGQR.js +47 -0
  21. package/dist/{chunk-QN3CQOE2.js → chunk-QKE5NDPF.js} +368 -7
  22. package/dist/{chunk-7RZEJJTN.js → chunk-RSNC5T6C.js} +2 -2
  23. package/dist/{chunk-2GTLF74N.js → chunk-UDJNZ3UG.js} +1 -1
  24. package/dist/{chunk-WGK5KZ6S.js → chunk-UVS7MRJX.js} +3 -3
  25. package/dist/{chunk-HU6ZMQDN.js → chunk-UWGBI5WK.js} +58 -1
  26. package/dist/{chunk-OFSOLHRX.js → chunk-WMGXJQI3.js} +1 -1
  27. package/dist/{chunk-5TIFEMHJ.js → chunk-ZEPJF4JC.js} +2 -2
  28. package/dist/{content-retention-U7dJM7Ph.d.ts → content-retention-B7ddtJ2y.d.ts} +1 -1
  29. package/dist/{credit-ledger-F2LWQU6N.js → credit-ledger-KIQIE6I6.js} +3 -3
  30. package/dist/{credit-menu-CUJdCfpt.d.ts → credit-menu-BH3g9GQa.d.ts} +5 -5
  31. package/dist/{fx-BvG2Wnjk.d.ts → fx-DN26_J7o.d.ts} +1 -1
  32. package/dist/index.d.ts +5 -5
  33. package/dist/index.js +4 -4
  34. package/dist/internal/caller.d.ts +10 -8
  35. package/dist/internal/caller.js +12 -9
  36. package/dist/internal/server.d.ts +7 -7
  37. package/dist/internal/server.js +22 -20
  38. package/dist/{job-store-DjgN2AOk.d.ts → job-store-Cq21JT37.d.ts} +1 -1
  39. package/dist/{lightning-backend-DeasHNOP.d.ts → lightning-backend-S8F8nf8q.d.ts} +1 -1
  40. package/dist/{memory-credit-ledger-XOOWJTBZ.js → memory-credit-ledger-ATYFAYBG.js} +4 -4
  41. package/dist/{mpp-setup-KGX6PLAF.js → mpp-setup-ZTORW3VF.js} +4 -2
  42. package/dist/{payout-reporter-W3CCLYCN.js → payout-reporter-NN6K2IVQ.js} +2 -2
  43. package/dist/{postgres-consumed-credential-store-LUJ5CUJS.js → postgres-consumed-credential-store-XJTE5PCA.js} +1 -1
  44. package/dist/{postgres-job-store-IX7IUXF4.js → postgres-job-store-AR6ZJUQV.js} +2 -2
  45. package/dist/{postgres-replay-store-Q6MEQDWZ.js → postgres-replay-store-33K33QV7.js} +2 -2
  46. package/dist/{pricing-ZUEFO3CM.js → pricing-G5IN5YJE.js} +3 -3
  47. package/dist/{revenue-reporter-NRS2FANA.js → revenue-reporter-KDUZLD5F.js} +2 -2
  48. package/dist/server/index.d.ts +10 -10
  49. package/dist/server/index.js +30 -28
  50. package/dist/{step-cache-C9JRSFhM.d.ts → step-cache-CR12EZ1Q.d.ts} +136 -4
  51. package/dist/{tempo-charge-store-NZJTJCUO.js → tempo-charge-store-XEGJYM3G.js} +2 -2
  52. package/dist/{tempo-session-store-ENFDLDKC.js → tempo-session-store-3WTDOO7H.js} +3 -3
  53. package/dist/testing/index.d.ts +3 -3
  54. package/dist/testing/index.js +8 -6
  55. package/dist/{usd-DXDkHn_8.d.ts → usd-s_VZ_eS7.d.ts} +1 -1
  56. package/dist/{x402-T7WVKDD4.js → x402-HQ5GRQZU.js} +6 -5
  57. package/package.json +18 -3
package/NOTICE CHANGED
@@ -1,2 +1,2 @@
1
1
  @dvmkit/sdk
2
- Copyright 2026 dvmkit contributors
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: accountless HTTPS services with typed inputs, jobs, and payment rails.
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
- Requires Node.js 22 or later. This is pre-1.0 alpha software: pin the exact package version and read the [release notes](./CHANGELOG.md) before upgrading.
5
+ Pre-1.0 alpha: breaking changes are announced in the [release notes](./CHANGELOG.md); pin the version you install.
6
6
 
7
- ## Quickstart: a free DVM
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 SDK:
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
- Save this as `handler.mjs`:
22
+ ## Quickstart: a free DVM
23
+
24
+ Save this as `handler.ts`:
18
25
 
19
- ```js
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: z.object({ text: z.string().min(1) }),
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.mjs`, then run it with `node server.mjs`:
44
+ Save this as `server.ts`:
35
45
 
36
- ```js
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
- Submit a local request. `devMode` makes Postgres optional and skips payment verification only when no Cashu, x402, or Tempo rail is configured:
49
+ import dvm from "./handler.ts";
44
50
 
45
- ```sh
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
- Test the handler without opening a port or configuring a payment rail:
52
-
53
- ```js
54
- import assert from "node:assert/strict";
55
- import test from "node:test";
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
- Run that file with `node --test handler.test.mjs`. The repository carries the complete version under [`examples/quickstart`](./examples/quickstart), plus runnable [signed-auth/custom-route](./examples/authenticated-route) and [payment/lifecycle](./examples/payments-and-lifecycle) examples.
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
- DVM_JOB_ENDPOINT=https://provider.example \
145
- DVM_JOB_TOKEN=<64-character-hex-token> \
146
- dvm status <job-id>
63
+ node server.ts
147
64
  ```
148
65
 
149
- The same values are accepted as `--endpoint <url>` and `--job-token <hex>`. These overrides are request-scoped and are not saved; `dvm pay` still requires the local pending request and spending policy, so an endpoint and token alone cannot authorize a payment.
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
- Every 200 or 202 response to `POST /v1/job`, `POST /v1/request-resume/:id` and `POST /v1/request-result/:id` carries a `DVM-Job-Identity-V1` response-head field whose value is the body's `job_id`, byte for byte, so a router or caller can learn which job a response names without reading a body of any size. No other status carries it. The value grammar is `^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$`; a server whose job ID falls outside it sends no field. A reader takes the value from `Headers.get`, trims nothing and never folds case: a repeated field (joined with `", "`), an empty value or any other character is invalid, and a trailer of the same name is never identity. The portable contract and its classification vectors are in `protocol-fixtures/job-identity-v1.json`. Hosting control's status reports `jobIdentityHeader: "v1"` when the deployment emits the field.
156
-
157
- ## Per-job costs
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
- `amount` is in whole currency units, like every other money figure you hand the SDK. Call it once per cost you incur; the declarations for one job are summed, and only the total is rounded, to a millionth of a unit. That ordering is what makes fine-grained declarations safe: a per-token charge of $0.0000003 is below the reported resolution on its own, but a thousand of them still add up to $0.0003 rather than to nothing. Every call in a job must name the same currency.
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
- Three things worth knowing:
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
- - **Declaring nothing is not declaring zero.** A job whose handler never calls `ctx.cost` reports no cost at all, and the dashboard says the margin is unknown rather than showing it as 100%. `ctx.cost({ amount: 0, currency: "usd" })` is how you say a job genuinely cost you nothing.
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
- The cost is yours, never the caller's: it is recorded separately from what the caller paid and is never netted against it. Both report shapes use the same durable outbox. Cost-only reports are keyed on the job id and revisioned, so retries and cross-machine updates converge on the latest cumulative value. With the Postgres job store, boot-time and periodic reconciliation enqueue any terminal cost revision that a process exited before reporting. Other job stores get no reconciliation: their cost-only report is durable only once the outbox insert commits, so an exit between the terminal save and that insert loses it.
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
- ## Rate cards
86
+ `@dvmkit/sdk/internal` also exists for the dvmkit platform; it has no stability promise and can change in any release.
181
87
 
182
- A priced capability can tell callers what it charges per unit, so an agent can explain the price before it asks for a quote. Declare `rates` next to `price` or `onQuote`, in the flat shape or on an entry in `capabilities`:
88
+ The generated [API reference](./docs/API.md) lists the supported declarations. Deep imports from `dist/` are unsupported.
183
89
 
184
- ```ts
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
- `/v1/info` publishes the list as `capabilities.<name>.pricing.rates`, exactly as you wrote it: same order, same strings. A capability without `rates` publishes nothing extra. For a quoted capability the rates describe how your quote is computed; the SDK does not compute or check quotes against them, so keep the two in step yourself.
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
- The server refuses to start, naming the capability and the entry, when a rate card breaks one of these limits:
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
- **This repository does not accept unsolicited pull requests.** One opened without an invitation is closed automatically, with a pointer to where the report belongs.
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
- **Issues and discussions are welcome and read.** We do not merge a change we cannot reproduce, and once we can reproduce a problem our own pipeline writes and reviews the fix. That makes a precise reproduction worth more to us than a patch, so that is what we ask for.
102
+ ## Security
214
103
 
215
- [CONTRIBUTING.md](./CONTRIBUTING.md) is the full policy. [SUPPORT.md](./SUPPORT.md) routes a question to the right place, [SECURITY.md](./SECURITY.md) covers vulnerability reports, which never go in a public issue, and the [Code of Conduct](./CODE_OF_CONDUCT.md) governs both trackers.
104
+ Report vulnerabilities privately as described in [SECURITY.md](./SECURITY.md).
216
105
 
217
- ## Trademarks
106
+ ## Support
218
107
 
219
- The Apache License 2.0 covers the source. It grants no rights in the dvmkit name or branding, and nothing in this repository implies a trademark licence.
108
+ Use [SUPPORT.md](./SUPPORT.md) to find the right place for questions and bug reports.
220
109
 
221
- `dvmkit` identifies products and services of DVM Technologies Ltd. You may say truthfully that your software uses, works with, or is built on `@dvmkit/sdk`, name the package in dependency listings and documentation, and describe an unmodified official release by its name. You need written permission to name a product, service, company, domain, or account in a way likely to be read as official dvmkit work, to use dvmkit branding as your own identity, or to claim sponsorship, certification, partnership, or endorsement. A fork is yours to redistribute under the Apache licence; name and present it so that nobody mistakes it for an official release.
110
+ ## Trademarks
222
111
 
223
- None of this claims exclusive rights in the phrase "digital vending machine" or in "DVM" used descriptively. Permission requests go to `support@dvmkit.com` with the subject `dvmkit trademark use request`, and nothing here limits rights you have under applicable law, including nominative fair use.
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,6 +1,6 @@
1
1
  import {
2
2
  diagnosticConsole
3
- } from "./chunk-5PLQOC4B.js";
3
+ } from "./chunk-BFGQG4WY.js";
4
4
 
5
5
  // src/sdk/pricing/currency.ts
6
6
  var InvalidCurrencyError = class extends Error {
@@ -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
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  diagnosticConsole
3
- } from "./chunk-5PLQOC4B.js";
3
+ } from "./chunk-BFGQG4WY.js";
4
4
 
5
5
  // src/lib/pg-retry.ts
6
6
  import { AsyncLocalStorage } from "async_hooks";
@@ -15,7 +15,7 @@ import {
15
15
  lotOwedSats,
16
16
  netOwedSats,
17
17
  x402SettlementPending
18
- } from "./chunk-IOKEKMIU.js";
18
+ } from "./chunk-FM6QAGJC.js";
19
19
 
20
20
  // src/sdk/server/memory-credit-ledger.ts
21
21
  import { randomUUID } from "crypto";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  diagnosticConsole
3
- } from "./chunk-5PLQOC4B.js";
3
+ } from "./chunk-BFGQG4WY.js";
4
4
  import {
5
5
  withSdkInitLock
6
6
  } from "./chunk-COYKJRMM.js";
@@ -3,10 +3,10 @@ import {
3
3
  } from "./chunk-62FI3NJF.js";
4
4
  import {
5
5
  callerLoggers
6
- } from "./chunk-KXWROQGK.js";
6
+ } from "./chunk-66HGCPBU.js";
7
7
  import {
8
8
  diagnosticConsole
9
- } from "./chunk-5PLQOC4B.js";
9
+ } from "./chunk-BFGQG4WY.js";
10
10
 
11
11
  // src/observability/payment-diagnostics.ts
12
12
  import { AsyncLocalStorage } from "async_hooks";
@@ -412,13 +412,22 @@ 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
+ "tempo_session_close_attempts_exhausted",
418
420
  "tempo_session_close_challenge_failed",
421
+ "tempo_session_close_exit_resolved",
419
422
  "tempo_session_close_failed",
420
423
  "tempo_session_close_failure_reconcile_failed",
424
+ "tempo_session_close_reconcile_failed",
421
425
  "tempo_session_close_recovery_failed",
426
+ "tempo_session_close_recovery_search_paused",
427
+ "tempo_session_close_recovery_unmatched",
428
+ "tempo_session_close_reservation_released",
429
+ "tempo_session_close_settlement_callback_failed",
430
+ "tempo_session_close_settlement_unreported",
422
431
  "tempo_session_open_refused",
423
432
  "tempo_session_settled",
424
433
  "tempo_session_withheld",
@@ -1146,7 +1155,6 @@ var REDACTED_SEGMENT_RE = /^:(?:uuid|hex|n|b58|token)$/;
1146
1155
  var SERVICE_NAMES = /* @__PURE__ */ new Set([
1147
1156
  "platform-api",
1148
1157
  "router",
1149
- "dvmadmin",
1150
1158
  "admin",
1151
1159
  "unknown"
1152
1160
  ]);
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  applyRetryToPool
3
- } from "./chunk-QM2OS3NJ.js";
3
+ } from "./chunk-7PM4R4GD.js";
4
4
  import {
5
5
  x402NetworkByCaip2
6
6
  } from "./chunk-BPYMEMQX.js";
7
7
  import {
8
8
  diagnosticConsole
9
- } from "./chunk-5PLQOC4B.js";
9
+ } from "./chunk-BFGQG4WY.js";
10
10
  import {
11
11
  withSdkInitLock
12
12
  } from "./chunk-COYKJRMM.js";
@@ -45,30 +45,7 @@ function isOpaqueToken(segment) {
45
45
  return false;
46
46
  }
47
47
 
48
- // src/lib/caller-loggers.ts
49
- var callerLoggers = buildNoopLoggers();
50
- function initCallerLoggers(nextLoggers) {
51
- callerLoggers = nextLoggers;
52
- }
53
- function buildNoopLoggers() {
54
- const noopLogger = {
55
- // eslint-disable-next-line @typescript-eslint/no-empty-function -- standalone caller has no exporter
56
- info() {
57
- },
58
- async span(_name, _attrs, fn) {
59
- return fn();
60
- }
61
- };
62
- return {
63
- cashu: noopLogger,
64
- lightning: noopLogger,
65
- x402: noopLogger
66
- };
67
- }
68
-
69
48
  export {
70
49
  redactUrl,
71
- redactUrlsInText,
72
- callerLoggers,
73
- initCallerLoggers
50
+ redactUrlsInText
74
51
  };
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-AFV77S4C.js";
5
5
  import {
6
6
  diagnosticConsole
7
- } from "./chunk-5PLQOC4B.js";
7
+ } from "./chunk-BFGQG4WY.js";
8
8
  import {
9
9
  withSdkInitLock
10
10
  } from "./chunk-COYKJRMM.js";
@@ -5,10 +5,10 @@ import {
5
5
  } from "./chunk-AFV77S4C.js";
6
6
  import {
7
7
  loggers
8
- } from "./chunk-RGSWVNQG.js";
8
+ } from "./chunk-IOQIXRGM.js";
9
9
  import {
10
10
  redactUrl
11
- } from "./chunk-KXWROQGK.js";
11
+ } from "./chunk-FUJ36YDV.js";
12
12
 
13
13
  // src/sdk/server/job-store.ts
14
14
  function accumulateAskedTopUp(prior, ask) {
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-UKD3R2KU.js";
8
8
  import {
9
9
  diagnosticConsole
10
- } from "./chunk-5PLQOC4B.js";
10
+ } from "./chunk-BFGQG4WY.js";
11
11
 
12
12
  // src/sdk/server/zod-error.ts
13
13
  function humanizeZodError(err, opts) {