@dvmkit/sdk 0.4.0-rc.1 → 0.6.0-rc.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/README.md +100 -0
- package/dist/{chunk-TXX4GOUB.js → chunk-7ZUKL4IV.js} +1 -1
- package/dist/chunk-AFV77S4C.js +258 -0
- package/dist/{chunk-2VGWYG4P.js → chunk-B2IROPQF.js} +446 -81
- package/dist/chunk-BQJIFRAZ.js +36 -0
- package/dist/{chunk-PB4FYZET.js → chunk-EQLA4FFY.js} +54 -4
- package/dist/{chunk-74PUEZJ4.js → chunk-GFY5FRX3.js} +1 -1
- package/dist/{chunk-O3QDFPKM.js → chunk-HCLVYZUZ.js} +2 -2
- package/dist/{chunk-K2YX2VRA.js → chunk-HHBDBMEO.js} +170 -43
- package/dist/{chunk-O5AT6S5T.js → chunk-IV6Q7NRW.js} +25 -195
- package/dist/{chunk-W2EQGDF5.js → chunk-LE74UEV7.js} +49 -8
- package/dist/{chunk-CE7XEHYC.js → chunk-N5RZ5HVB.js} +1 -1
- package/dist/chunk-OFVV37XC.js +57 -0
- package/dist/{chunk-5LR6P7WN.js → chunk-OQA7IHI4.js} +133 -213
- package/dist/{chunk-3MHE725Z.js → chunk-PAIM66YA.js} +90 -1
- package/dist/{chunk-6DOGTAHA.js → chunk-QR2EF2NI.js} +9 -43
- package/dist/{chunk-KXZUCCEY.js → chunk-RCM3P2HE.js} +5 -56
- package/dist/{chunk-R4DQZFAL.js → chunk-UOCZXO2Z.js} +3 -3
- package/dist/content-retention-BTGSQRfP.d.ts +78 -0
- package/dist/{credit-menu-B8_p_vUz.d.ts → credit-menu-BAsrRu3z.d.ts} +98 -5
- package/dist/{fx-DuaYHvVn.d.ts → fx-BgDmyTdd.d.ts} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -3
- package/dist/internal/caller.d.ts +90 -22
- package/dist/internal/caller.js +40 -40
- package/dist/internal/index.js +1 -1
- package/dist/internal/server.d.ts +8 -99
- package/dist/internal/server.js +15 -11
- package/dist/{job-store-D6Rjj5Oi.d.ts → job-store-BVeF5_Jq.d.ts} +1 -1
- package/dist/{lightning-backend-BaeCPmx7.d.ts → lightning-backend-40fIzz1W.d.ts} +1 -1
- package/dist/{postgres-consumed-credential-store-CUQJKRSL.js → postgres-consumed-credential-store-BUVCAPLS.js} +13 -0
- package/dist/{postgres-job-store-VLXLWXUQ.js → postgres-job-store-VHLQBJZC.js} +3 -1
- package/dist/server/index.d.ts +28 -10
- package/dist/server/index.js +24 -10
- package/dist/{step-cache-Bi43A0JN.d.ts → step-cache-CPdH1hei.d.ts} +3 -3
- package/dist/{tempo-lifecycle-OTHV757Y.js → tempo-lifecycle-ZBBLFBMH.js} +5 -4
- package/dist/{tempo-wallet-32JWQZKQ.js → tempo-wallet-B5XXV3YH.js} +1 -1
- package/dist/testing/index.d.ts +9 -2
- package/dist/testing/index.js +6 -3
- package/dist/{usd-AZPVq4eU.d.ts → usd-AWlrwlM5.d.ts} +1 -1
- package/dist/{x402-CEDHGZDI.js → x402-R6HPBY5D.js} +5 -4
- package/package.json +3 -2
- package/recipes/postgres/financial-export-v1.sql +137 -0
package/README.md
CHANGED
|
@@ -96,6 +96,12 @@ For Bitcoin credit monitoring, `GET /admin/credit/bitcoin-liability` reports exa
|
|
|
96
96
|
|
|
97
97
|
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.
|
|
98
98
|
|
|
99
|
+
## Diagnostic retention
|
|
100
|
+
|
|
101
|
+
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`.
|
|
102
|
+
|
|
103
|
+
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.
|
|
104
|
+
|
|
99
105
|
## Repository development
|
|
100
106
|
|
|
101
107
|
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.
|
|
@@ -182,3 +188,97 @@ None of this claims exclusive rights in the phrase "digital vending machine" or
|
|
|
182
188
|
## License
|
|
183
189
|
|
|
184
190
|
Apache-2.0. See [LICENSE](./LICENSE) and [NOTICE](./NOTICE).
|
|
191
|
+
|
|
192
|
+
## Job content retention and case holds
|
|
193
|
+
|
|
194
|
+
`configureDVM` defaults `jobRetentionDays` to 730. Set a shorter or longer whole-day
|
|
195
|
+
window explicitly, or `0` to disable automatic age expiry. Only completed, failed,
|
|
196
|
+
and cancelled jobs are eligible. The terminal `lastActivityAt` is frozen: reads,
|
|
197
|
+
receipt delivery, late messages, and re-saving an outcome do not renew it. The
|
|
198
|
+
sweep removes input, params, summary, messages, state, caches, and bearer proofs;
|
|
199
|
+
it preserves durable status, signed receipts, payment amounts, and recovery linkage.
|
|
200
|
+
|
|
201
|
+
Authenticated job submit, request-result, status and JSON message responses expose
|
|
202
|
+
`content_expires_at` (epoch milliseconds or `null`) and `content_available` (boolean).
|
|
203
|
+
A null deadline means the job is active, expiry is disabled, or an active hold
|
|
204
|
+
suppresses it. A past deadline can still have available content until the sweep
|
|
205
|
+
runs. Missing fields on an older service or unsupported custom store mean unknown.
|
|
206
|
+
SSE messages retain their existing event format; read job status for expiry.
|
|
207
|
+
|
|
208
|
+
With durable service-admin configuration, use signed `Authorization: P2PK`
|
|
209
|
+
requests on the service-local routes:
|
|
210
|
+
|
|
211
|
+
| Method | Path | JSON body |
|
|
212
|
+
| --- | --- | --- |
|
|
213
|
+
| GET | `/admin/jobs/:jobId/content-holds` | none |
|
|
214
|
+
| POST | `/admin/jobs/:jobId/content-holds` | `purpose`, `reason`, `review_due_at` |
|
|
215
|
+
| POST | `/admin/jobs/:jobId/content-holds/:holdId/renew` | `reason`, `review_due_at` |
|
|
216
|
+
| POST | `/admin/jobs/:jobId/content-holds/:holdId/clear` | `reason` |
|
|
217
|
+
|
|
218
|
+
Purpose is `support` or `dispute`; reason is 1–1000 characters and review dates
|
|
219
|
+
are future epoch millisecond integers. Unknown body fields are rejected. Hold IDs,
|
|
220
|
+
actor public keys, and creation/clear times are server-derived. Authentication uses
|
|
221
|
+
the existing method/path/service-bound admin challenge, current and grace-period
|
|
222
|
+
keys, and atomic durable nonce consumption. A hold request does not grant access
|
|
223
|
+
to job content. Authentication failure returns 401 with a reason; an unsupported
|
|
224
|
+
store returns `content_holds_unavailable` (503), missing jobs return 404, and a
|
|
225
|
+
redacted job or inactive hold returns 409. Holds cannot restore redacted content.
|
|
226
|
+
|
|
227
|
+
Responses include `job_id`, caller content metadata, `baseline_content_expires_at`,
|
|
228
|
+
`holds`, and append-only `events`. Each hold contains `id`, `job_id`, `purpose`,
|
|
229
|
+
`reason`, original `actor`, `created_at`, `review_due_at`, `cleared_at`, and computed
|
|
230
|
+
`review_overdue`. Each event contains `id`, `hold_id`, `job_id`, `action`, `actor`,
|
|
231
|
+
`reason`, `at`, `review_due_at`. Renew and clear preserve the original actor and
|
|
232
|
+
creation date; their authenticated actors are in history. Any active hold suppresses
|
|
233
|
+
the effective deadline. Overdue review flags work; it does not release the hold.
|
|
234
|
+
The baseline remains visible for review. Clearing the last hold restores the
|
|
235
|
+
baseline and can make content immediately eligible for the next sweep.
|
|
236
|
+
|
|
237
|
+
`PostgresJobStore.init()` adds `job_content_holds` and `job_content_hold_events`
|
|
238
|
+
with job-scoped indexes; existing jobs need no expiry column or backfill. Placement
|
|
239
|
+
and sweep lock the same job row and check redaction/terminal activity/active holds
|
|
240
|
+
inside the transaction. Admin operations refuse jobs bound to another service;
|
|
241
|
+
legacy unbound rows belong to the local service database. A committed hold wins any later competing sweep; if
|
|
242
|
+
redaction wins first, placement fails. `MemoryJobStore` supplies the same contract
|
|
243
|
+
for development. Custom stores must implement `ContentHoldStore` and atomic
|
|
244
|
+
redaction fencing to support holds and truthful metadata.
|
|
245
|
+
|
|
246
|
+
Before explicitly destroying a service database, use your existing database
|
|
247
|
+
connection/proxy and the packaged `recipes/postgres/financial-export-v1.sql`:
|
|
248
|
+
|
|
249
|
+
```sh
|
|
250
|
+
psql "$DATABASE_URL" -X -q -t -A -v ON_ERROR_STOP=1 \
|
|
251
|
+
-f node_modules/@dvmkit/sdk/recipes/postgres/financial-export-v1.sql \
|
|
252
|
+
> financial-evidence.json
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The versioned JSON export takes a consistent read-only snapshot and includes signed
|
|
256
|
+
receipt tuples, ledger balances/amounts/currencies, job/payment links, and recovery
|
|
257
|
+
outcomes. Funding lots preserve every deposit's original sats and credit basis,
|
|
258
|
+
including depleted lots. Draws retain both the original allocation and settled
|
|
259
|
+
amount. Refunds retain fulfilment costs and funding-lot linkage. Optional channel
|
|
260
|
+
projections preserve amounts and settlement links while excluding authorization
|
|
261
|
+
signatures and vouchers; Tempo bigint amounts are decimal strings.
|
|
262
|
+
Unsigned JSON blocks use typed financial projections: unsupported fields and
|
|
263
|
+
objects or arrays in scalar positions are excluded. Optional ledger tables absent from a service become empty arrays. It
|
|
264
|
+
excludes raw input/output, bearer tokens/proofs, payout descriptors, and operational
|
|
265
|
+
credentials. Signed receipts may retain their signed terminal reason and result
|
|
266
|
+
hash; supported version-1 job, funding, and drain receipts are returned unchanged.
|
|
267
|
+
Unsupported receipt versions, kinds, fields, or nested extensions fail the entire
|
|
268
|
+
export before any JSON is emitted. Update the recipe for that schema before
|
|
269
|
+
destruction; a failed export is not retained financial evidence. Verify receipt
|
|
270
|
+
signatures and check the file before destruction, then keep it securely with this recipe
|
|
271
|
+
and the service's public receipt-key attestation. The builder owns this export;
|
|
272
|
+
the platform retains its separate financial history. An export does not settle
|
|
273
|
+
caller balances or refunds: resolve liabilities through their existing lifecycle.
|
|
274
|
+
Case holds affect age expiry, not explicit erasure or contract/lifecycle destruction.
|
|
275
|
+
The active-service window does not extend any contractual maximum for copies after
|
|
276
|
+
explicit destruction, including a 90-day post-destruction limit where applicable.
|
|
277
|
+
|
|
278
|
+
The wire fields and routes are additive within protocol version 1. Existing explicit
|
|
279
|
+
retention settings retain their value. The new default also applies to existing
|
|
280
|
+
unredacted terminal rows; previously removed content stays unavailable. Upgrade
|
|
281
|
+
all replicas before relying on holds: older SDKs do not recognize the hold fence.
|
|
282
|
+
Do not downgrade or mix old replicas with active holds unless automatic sweeping
|
|
283
|
+
is disabled on those replicas. The SQL export format is versioned independently
|
|
284
|
+
of the package and is not a raw database backup or a content archive.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import {
|
|
2
|
+
StepCache
|
|
3
|
+
} from "./chunk-BQJIFRAZ.js";
|
|
4
|
+
|
|
5
|
+
// src/sdk/server/job.ts
|
|
6
|
+
function toJobRecord(job) {
|
|
7
|
+
return {
|
|
8
|
+
id: job.id,
|
|
9
|
+
tags: job.tags,
|
|
10
|
+
capability: job.capability,
|
|
11
|
+
input: job.input,
|
|
12
|
+
params: job.params,
|
|
13
|
+
requesterId: job.requesterId,
|
|
14
|
+
requesterTokenHash: job.requesterTokenHash,
|
|
15
|
+
requesterToken: job.requesterToken,
|
|
16
|
+
requestFingerprint: job.requestFingerprint,
|
|
17
|
+
requesterPubkey: job.requesterPubkey,
|
|
18
|
+
requestId: job.requestId,
|
|
19
|
+
authRequestPath: job.authRequestPath,
|
|
20
|
+
status: job.status,
|
|
21
|
+
summary: job.summary,
|
|
22
|
+
timingKnown: job.timingKnown,
|
|
23
|
+
endedBy: job.endedBy,
|
|
24
|
+
terminalAtMs: job.terminalAtMs,
|
|
25
|
+
callerWaitMs: job.callerWaitMs,
|
|
26
|
+
awaitingInputAtMs: job.awaitingInputAtMs,
|
|
27
|
+
messages: job.messages,
|
|
28
|
+
seq: job.seq,
|
|
29
|
+
paidMsats: job.paidMsats,
|
|
30
|
+
paymentMint: job.paymentMint,
|
|
31
|
+
paymentRail: job.paymentRail,
|
|
32
|
+
paymentTxHash: job.paymentTxHash,
|
|
33
|
+
paymentTransactionHash: job.paymentTransactionHash,
|
|
34
|
+
nativeAmount: job.nativeAmount,
|
|
35
|
+
nativeAsset: job.nativeAsset,
|
|
36
|
+
cashuFlow: job.cashuFlow,
|
|
37
|
+
costAmountMicro: job.costAmountMicro,
|
|
38
|
+
costCurrency: job.costCurrency,
|
|
39
|
+
costRevision: job.costRevision,
|
|
40
|
+
creditId: job.creditId,
|
|
41
|
+
drawId: job.drawId,
|
|
42
|
+
fundingReceipt: job.fundingReceipt,
|
|
43
|
+
fundingCredit: job.fundingCredit,
|
|
44
|
+
receivedProofs: job.receivedProofs,
|
|
45
|
+
pendingPaymentMsats: job.pendingPaymentMsats,
|
|
46
|
+
pendingMppChallengeIds: job.pendingMppChallengeIds,
|
|
47
|
+
pendingX402Nonce: job.pendingX402Nonce,
|
|
48
|
+
pendingX402AmountUsdcMicro: job.pendingX402AmountUsdcMicro,
|
|
49
|
+
pendingPaymentFiatMicro: job.pendingPaymentFiatMicro,
|
|
50
|
+
pendingPaymentFiatCurrency: job.pendingPaymentFiatCurrency,
|
|
51
|
+
askedTopUpMicro: job.askedTopUpMicro,
|
|
52
|
+
askedTopUpCurrency: job.askedTopUpCurrency,
|
|
53
|
+
topUpCapUnenforcedReason: job.topUpCapUnenforcedReason,
|
|
54
|
+
requiredMsats: job.requiredMsats,
|
|
55
|
+
receipt: job.receipt,
|
|
56
|
+
stepCache: job.stepCache.serialize(),
|
|
57
|
+
state: job.state,
|
|
58
|
+
createdAt: job.createdAt,
|
|
59
|
+
lastActivityAt: job.lastActivityAt
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function fromJobRecord(record) {
|
|
63
|
+
return {
|
|
64
|
+
id: record.id,
|
|
65
|
+
tags: record.tags,
|
|
66
|
+
capability: record.capability,
|
|
67
|
+
input: record.input,
|
|
68
|
+
params: record.params,
|
|
69
|
+
requesterId: record.requesterId,
|
|
70
|
+
requesterTokenHash: record.requesterTokenHash,
|
|
71
|
+
status: record.status,
|
|
72
|
+
summary: record.summary,
|
|
73
|
+
timingKnown: record.timingKnown,
|
|
74
|
+
endedBy: record.endedBy,
|
|
75
|
+
terminalAtMs: record.terminalAtMs,
|
|
76
|
+
callerWaitMs: record.callerWaitMs,
|
|
77
|
+
awaitingInputAtMs: record.awaitingInputAtMs,
|
|
78
|
+
messages: [...record.messages],
|
|
79
|
+
seq: record.seq,
|
|
80
|
+
paidMsats: record.paidMsats,
|
|
81
|
+
paymentMint: record.paymentMint,
|
|
82
|
+
paymentRail: record.paymentRail,
|
|
83
|
+
paymentTxHash: record.paymentTxHash,
|
|
84
|
+
paymentTransactionHash: record.paymentTransactionHash,
|
|
85
|
+
nativeAmount: record.nativeAmount,
|
|
86
|
+
nativeAsset: record.nativeAsset,
|
|
87
|
+
cashuFlow: record.cashuFlow,
|
|
88
|
+
costAmountMicro: record.costAmountMicro,
|
|
89
|
+
costCurrency: record.costCurrency,
|
|
90
|
+
costRevision: record.costRevision,
|
|
91
|
+
creditId: record.creditId,
|
|
92
|
+
drawId: record.drawId,
|
|
93
|
+
fundingReceipt: record.fundingReceipt,
|
|
94
|
+
fundingCredit: record.fundingCredit,
|
|
95
|
+
receivedProofs: [...record.receivedProofs],
|
|
96
|
+
pendingPaymentMsats: record.pendingPaymentMsats,
|
|
97
|
+
pendingMppChallengeIds: record.pendingMppChallengeIds ? [...record.pendingMppChallengeIds] : void 0,
|
|
98
|
+
requesterToken: record.requesterToken,
|
|
99
|
+
requestFingerprint: record.requestFingerprint,
|
|
100
|
+
requesterPubkey: record.requesterPubkey,
|
|
101
|
+
requestId: record.requestId,
|
|
102
|
+
authRequestPath: record.authRequestPath,
|
|
103
|
+
pendingX402Nonce: record.pendingX402Nonce,
|
|
104
|
+
pendingX402AmountUsdcMicro: record.pendingX402AmountUsdcMicro,
|
|
105
|
+
pendingPaymentFiatMicro: record.pendingPaymentFiatMicro,
|
|
106
|
+
pendingPaymentFiatCurrency: record.pendingPaymentFiatCurrency,
|
|
107
|
+
askedTopUpMicro: record.askedTopUpMicro,
|
|
108
|
+
askedTopUpCurrency: record.askedTopUpCurrency,
|
|
109
|
+
topUpCapUnenforcedReason: record.topUpCapUnenforcedReason,
|
|
110
|
+
requiredMsats: record.requiredMsats,
|
|
111
|
+
receipt: record.receipt,
|
|
112
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
113
|
+
abort: new AbortController(),
|
|
114
|
+
messageAppender: void 0,
|
|
115
|
+
messageAppenderTail: void 0,
|
|
116
|
+
stepCache: StepCache.deserialize(record.stepCache),
|
|
117
|
+
state: record.state,
|
|
118
|
+
pendingPrompts: /* @__PURE__ */ new Map(),
|
|
119
|
+
pendingPayment: null,
|
|
120
|
+
replayHighSeq: null,
|
|
121
|
+
replayProviderSkip: 0,
|
|
122
|
+
replayPaymentIndex: 0,
|
|
123
|
+
createdAt: record.createdAt,
|
|
124
|
+
lastActivityAt: record.lastActivityAt
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function abortJob(job, reason) {
|
|
128
|
+
job.abort.abort(new JobCancelledError(reason));
|
|
129
|
+
}
|
|
130
|
+
var JobCancelledError = class extends Error {
|
|
131
|
+
constructor(message = "Job cancelled") {
|
|
132
|
+
super(message);
|
|
133
|
+
this.name = "JobCancelledError";
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
function providerMessage(job, type, content) {
|
|
137
|
+
if (job.replayProviderSkip > 0) {
|
|
138
|
+
job.replayProviderSkip--;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const msg = localMessage(job, "provider", type, content);
|
|
142
|
+
if (type === "payment-request") {
|
|
143
|
+
const paymentRequest = content;
|
|
144
|
+
const pendingPaymentDelta = typeof paymentRequest.amount_msats === "number" ? paymentRequest.amount_msats : void 0;
|
|
145
|
+
const pendingMppChallengeIds = paymentRequest.tempo?.challenges?.map((challenge) => challenge.id).filter((id) => typeof id === "string");
|
|
146
|
+
job.messageAppender?.(msg, {
|
|
147
|
+
awaitingInput: job.status === "awaiting-input",
|
|
148
|
+
pendingPaymentDelta,
|
|
149
|
+
pendingMppChallengeIds,
|
|
150
|
+
pendingX402Nonce: typeof paymentRequest.x402?.nonce === "string" ? paymentRequest.x402.nonce : void 0,
|
|
151
|
+
pendingX402AmountUsdcMicro: typeof paymentRequest.x402?.required_usdc_micro === "string" ? paymentRequest.x402.required_usdc_micro : void 0,
|
|
152
|
+
// read off the job rather than the content — the fiat envelope
|
|
153
|
+
// is ledger state, not part of the wire shape, and `requestPayment` has
|
|
154
|
+
// already pinned it on the job by the time this fires (same ordering the
|
|
155
|
+
// `pendingPaymentMsats` comment above describes).
|
|
156
|
+
pendingPaymentFiatMicro: job.pendingPaymentFiatMicro,
|
|
157
|
+
pendingPaymentFiatCurrency: job.pendingPaymentFiatCurrency
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
job.messageAppender?.(msg, type === "prompt" ? { awaitingInput: job.status === "awaiting-input" } : void 0);
|
|
162
|
+
}
|
|
163
|
+
function localMessage(job, from, type, content) {
|
|
164
|
+
job.seq++;
|
|
165
|
+
const msg = {
|
|
166
|
+
seq: job.seq,
|
|
167
|
+
from,
|
|
168
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
169
|
+
type,
|
|
170
|
+
content
|
|
171
|
+
};
|
|
172
|
+
job.messages.push(msg);
|
|
173
|
+
for (const listener of job.listeners) {
|
|
174
|
+
listener(msg);
|
|
175
|
+
}
|
|
176
|
+
return msg;
|
|
177
|
+
}
|
|
178
|
+
function isTerminal(status) {
|
|
179
|
+
return status === "completed" || status === "failed" || status === "cancelled";
|
|
180
|
+
}
|
|
181
|
+
function isYieldMessage(msg) {
|
|
182
|
+
return msg.type === "prompt" || msg.type === "payment-request" || msg.type === "working" || msg.type === "complete" || msg.type === "cancel";
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// src/sdk/server/content-retention.ts
|
|
186
|
+
import { randomUUID } from "crypto";
|
|
187
|
+
var ContentHoldError = class extends Error {
|
|
188
|
+
constructor(code) {
|
|
189
|
+
super(code);
|
|
190
|
+
this.code = code;
|
|
191
|
+
}
|
|
192
|
+
code;
|
|
193
|
+
};
|
|
194
|
+
function isContentHoldStore(store) {
|
|
195
|
+
return "getContentSnapshot" in store && "mutateContentHold" in store;
|
|
196
|
+
}
|
|
197
|
+
function contentMetadata(job, retentionDays, holds = []) {
|
|
198
|
+
const baseline = baselineContentExpiry(job, retentionDays);
|
|
199
|
+
return {
|
|
200
|
+
content_expires_at: holds.some((hold) => hold.cleared_at === null) ? null : baseline,
|
|
201
|
+
content_available: job.redactedAt === void 0
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function baselineContentExpiry(job, retentionDays) {
|
|
205
|
+
return retentionDays > 0 && isTerminal(job.status) ? job.lastActivityAt + retentionDays * 864e5 : null;
|
|
206
|
+
}
|
|
207
|
+
function applyContentHoldMutation(job, history, mutation) {
|
|
208
|
+
if (job.redactedAt !== void 0) throw new ContentHoldError("content_unavailable");
|
|
209
|
+
const result = structuredClone(history);
|
|
210
|
+
let hold;
|
|
211
|
+
if (mutation.action === "placed") {
|
|
212
|
+
hold = {
|
|
213
|
+
id: randomUUID(),
|
|
214
|
+
job_id: job.id,
|
|
215
|
+
purpose: mutation.purpose,
|
|
216
|
+
reason: mutation.reason,
|
|
217
|
+
actor: mutation.actor,
|
|
218
|
+
created_at: mutation.at,
|
|
219
|
+
review_due_at: mutation.review_due_at,
|
|
220
|
+
cleared_at: null
|
|
221
|
+
};
|
|
222
|
+
result.holds.push(hold);
|
|
223
|
+
} else {
|
|
224
|
+
const existing = result.holds.find((item) => item.id === mutation.hold_id && item.cleared_at === null);
|
|
225
|
+
if (!existing) throw new ContentHoldError("hold_not_active");
|
|
226
|
+
hold = existing;
|
|
227
|
+
if (mutation.action === "cleared") hold.cleared_at = mutation.at;
|
|
228
|
+
else hold.review_due_at = mutation.review_due_at;
|
|
229
|
+
hold.reason = mutation.reason;
|
|
230
|
+
}
|
|
231
|
+
result.events.push({
|
|
232
|
+
id: randomUUID(),
|
|
233
|
+
hold_id: hold.id,
|
|
234
|
+
job_id: job.id,
|
|
235
|
+
action: mutation.action,
|
|
236
|
+
actor: mutation.actor,
|
|
237
|
+
reason: mutation.reason,
|
|
238
|
+
at: mutation.at,
|
|
239
|
+
review_due_at: hold.review_due_at
|
|
240
|
+
});
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export {
|
|
245
|
+
toJobRecord,
|
|
246
|
+
fromJobRecord,
|
|
247
|
+
abortJob,
|
|
248
|
+
JobCancelledError,
|
|
249
|
+
providerMessage,
|
|
250
|
+
localMessage,
|
|
251
|
+
isTerminal,
|
|
252
|
+
isYieldMessage,
|
|
253
|
+
ContentHoldError,
|
|
254
|
+
isContentHoldStore,
|
|
255
|
+
contentMetadata,
|
|
256
|
+
baselineContentExpiry,
|
|
257
|
+
applyContentHoldMutation
|
|
258
|
+
};
|