@warmhub/sdk-ts 0.127.0 → 0.129.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/CHANGELOG.md CHANGED
@@ -1,5 +1,443 @@
1
1
  # @warmhub/sdk-ts changelog
2
2
 
3
+ <!-- warmhub-changelog-artifact:v1 -->
4
+
5
+ ## 0.129.0
6
+
7
+ No new release notes.
8
+
9
+ ## 0.128.0
10
+
11
+ ### Documentation
12
+
13
+ Versioned release notes are available as [HTML, Markdown, and JSON](https://docs.warmhub.ai/sdk/typescript/changelog/).
14
+
15
+ ---
16
+
17
+ Historical changelog shipped with @warmhub/sdk-ts 0.127.0:
18
+
19
+ # @warmhub/sdk-ts changelog
20
+
3
21
  Release 0.127.0.
4
22
 
5
23
  Changelog generation is temporarily disabled while the release integration is repaired.
24
+
25
+
26
+ Earlier changelog shipped with @warmhub/sdk-ts 0.126.0:
27
+
28
+ # @warmhub/sdk-ts changelog
29
+
30
+ Release 0.126.0.
31
+
32
+ Changelog generation is temporarily disabled while the release integration is repaired.
33
+
34
+
35
+ Earlier changelog shipped with @warmhub/sdk-ts 0.125.0:
36
+
37
+ # @warmhub/sdk-ts changelog
38
+
39
+ Release 0.125.0.
40
+
41
+ Changelog generation is temporarily disabled while the release integration is repaired.
42
+
43
+
44
+ Earlier changelog shipped with @warmhub/sdk-ts 0.124.0:
45
+
46
+ # @warmhub/sdk-ts changelog
47
+
48
+ Release 0.124.0.
49
+
50
+ Changelog generation is temporarily disabled while the release integration is repaired.
51
+
52
+
53
+ Earlier changelog shipped with @warmhub/sdk-ts 0.123.0:
54
+
55
+ # @warmhub/sdk-ts changelog
56
+
57
+ Release 0.123.0.
58
+
59
+ Changelog generation is temporarily disabled while the release integration is repaired.
60
+
61
+
62
+ Earlier changelog shipped with @warmhub/sdk-ts 0.122.3:
63
+
64
+ # @warmhub/sdk-ts changelog
65
+
66
+ Release 0.122.3.
67
+
68
+ Changelog generation is temporarily disabled while the release integration is repaired.
69
+
70
+
71
+ Earlier changelog shipped with @warmhub/sdk-ts 0.120.0:
72
+
73
+ # Changelog — `@warmhub/sdk-ts`
74
+
75
+ ## How to read this file
76
+
77
+ Entries are grouped by release. `## Unreleased` collects everything merged but
78
+ not yet published; the release pipeline is the sole version authority and
79
+ stamps the exact version at publish time, so no heading here ever invents one.
80
+
81
+ Within a release, four change sections appear, in this order. A section is
82
+ present only when it has entries; a release may add plain narrative sections
83
+ after them ("Behavior worth knowing", "Still supported") when the change needs
84
+ context that is not itself a change entry.
85
+
86
+ - **Breaking** — your code must change. Every item is written as a migration:
87
+ what it was → what it is now → the exact edit, with before/after code → why,
88
+ in one line, citing the ruling.
89
+ - **Added** — new surface. Nothing to do unless you want it.
90
+ - **Fixed** — behavior that was wrong and now is not.
91
+ - **Removed** — surface that is gone with nothing to put in its place. Anything
92
+ a caller can migrate to is filed under Breaking instead, so this section is
93
+ often absent.
94
+
95
+ WarmHub's source repository is private, so rulings are cited by pull-request
96
+ number rather than linked. Release notes live at
97
+ <https://docs.warmhub.ai/releases/overview/>.
98
+
99
+ The Python SDK, `warmhub`, keeps the same file in the same shape.
100
+
101
+ ---
102
+
103
+ ## Unreleased
104
+
105
+ ## 0.120.0
106
+
107
+ Repository export v3. The exact version is stamped at publish time by the
108
+ release pipeline; source manifests stay at `0.0.0-development`.
109
+
110
+ ### Breaking
111
+
112
+ #### Checkpoint generation is retired — take an export instead
113
+
114
+ **What it was.** Producing a fresh snapshot of a repository meant asking the
115
+ server to mint a checkpoint, polling until the job completed, requesting a
116
+ short-lived signed URL for the archive, downloading it outside the SDK, and
117
+ verifying the bytes locally.
118
+
119
+ **What it is now.** One call that streams verified rows, with the header,
120
+ per-row canonical decode, ordering and trailer checks applied as the bytes
121
+ arrive. There is no job to start, no state to poll, and no separate verify
122
+ step — a row that reaches you has already been proven.
123
+
124
+ **Migration.**
125
+
126
+ Before:
127
+
128
+ ```ts
129
+ import { createReadStream } from 'node:fs'
130
+ import { writeFile } from 'node:fs/promises'
131
+ import { Readable } from 'node:stream'
132
+ import { verifyRepositoryCheckpointArchive } from '@warmhub/sdk-ts/checkpoint'
133
+
134
+ let status = await client.repo.checkpoint.generate('acme', 'catalog')
135
+ while (status.state !== 'complete') {
136
+ await new Promise((resolve) => setTimeout(resolve, 1_000))
137
+ status = await client.repo.checkpoint.status('acme', 'catalog', {
138
+ checkpointId: status.checkpointId,
139
+ })
140
+ }
141
+
142
+ const access = await client.repo.checkpoint.getAccess('acme', 'catalog', {
143
+ checkpoint: 'latest',
144
+ artifact: 'archive',
145
+ })
146
+ // Signed URL: fetched WITHOUT the WarmHub bearer token.
147
+ const archive = await fetch(access.url)
148
+ await writeFile('catalog.zip', Readable.fromWeb(archive.body))
149
+ await verifyRepositoryCheckpointArchive(createReadStream('catalog.zip'))
150
+ ```
151
+
152
+ After:
153
+
154
+ ```ts
155
+ for await (const row of client.repo.export('acme', 'catalog')) {
156
+ console.log(row.kind, row.durableId, row.version)
157
+ }
158
+ ```
159
+
160
+ If you were relying on `generate` returning before the download — starting the
161
+ build in one process and fetching it in another, or on another machine — that
162
+ shape survives as the async token flow:
163
+
164
+ ```ts
165
+ // Process A: ask for it, get a token, do not read a byte.
166
+ const prepared = await client.repo.exportPrepare('acme', 'catalog')
167
+ console.log(prepared.exportToken, prepared.atRepoSeq)
168
+
169
+ // Process B: redeem it. `export` polls while the fold is still running.
170
+ for await (const row of client.repo.export('acme', 'catalog', {
171
+ token: prepared.exportToken,
172
+ })) {
173
+ // ...
174
+ }
175
+ ```
176
+
177
+ The token is redeemable only at that repository's URL, by a principal holding
178
+ checkpoint read there.
179
+
180
+ **Why.** Export format v3 replaces checkpoint generation outright; the whole
181
+ generation pipeline is deleted server-side (PR #10020).
182
+
183
+ #### `client.repo.checkpoint.generate` and `.retry` are gone
184
+
185
+ **What it was.** `generate(orgName, repoName, { atLeastRepoSeq })` minted a
186
+ checkpoint; `retry(orgName, repoName, checkpointId)` re-ran a failed one. Both
187
+ returned a `RepositoryCheckpointStatus`.
188
+
189
+ **What it is now.** Neither method exists, and the tRPC procedures behind them
190
+ (`repo.checkpoint.generate`, `repo.checkpoint.retry`) are no longer served. The
191
+ `repo:checkpoint-generate` scope no longer buys anything.
192
+
193
+ **Migration.** Replace both with `client.repo.export`.
194
+
195
+ Before:
196
+
197
+ ```ts
198
+ const fresh = await client.repo.checkpoint.generate('acme', 'catalog', {
199
+ atLeastRepoSeq: 42,
200
+ })
201
+ const recovered = await client.repo.checkpoint.retry(
202
+ 'acme',
203
+ 'catalog',
204
+ failed.checkpointId,
205
+ )
206
+ ```
207
+
208
+ After:
209
+
210
+ ```ts
211
+ // `atRepoSeq` pins the fence the way `atLeastRepoSeq` asked for a floor.
212
+ // Omit it and the server pins current and echoes it back in the header.
213
+ for await (const row of client.repo.export('acme', 'catalog', {
214
+ atRepoSeq: 42,
215
+ })) {
216
+ // ...
217
+ }
218
+ ```
219
+
220
+ There is no retry to replace: an export is a read, so a failed one is re-read,
221
+ not re-queued.
222
+
223
+ **Why.** Same retirement (PR #10020).
224
+
225
+ #### Failed checkpoint statuses now all answer `nextAction: 'none'`
226
+
227
+ **What it was.** `RepositoryCheckpointStatus` carried `nextAction: 'retry'` for
228
+ `deadline_exceeded` and `attempts_exhausted`, and `nextAction: 'generate'` for
229
+ `invalid_source`. Each named a procedure a reader could call.
230
+
231
+ **What it is now.** Those three failure codes join `repository_deleted` in a
232
+ single variant with `nextAction: 'none'`. The `'retry'` and `'generate'`
233
+ members are gone from the union, so a `switch` over `nextAction` that had cases
234
+ for them will no longer typecheck against them.
235
+
236
+ **Migration.**
237
+
238
+ Before:
239
+
240
+ ```ts
241
+ if (status.state === 'failed') {
242
+ switch (status.nextAction) {
243
+ case 'retry':
244
+ await client.repo.checkpoint.retry('acme', 'catalog', status.checkpointId)
245
+ break
246
+ case 'generate':
247
+ await client.repo.checkpoint.generate('acme', 'catalog')
248
+ break
249
+ case 'contact_support':
250
+ report(status.checkpointId, status.failureCode)
251
+ break
252
+ }
253
+ }
254
+ ```
255
+
256
+ After:
257
+
258
+ ```ts
259
+ if (status.state === 'failed') {
260
+ if (status.nextAction === 'contact_support') {
261
+ report(status.checkpointId, status.failureCode)
262
+ } else {
263
+ // Terminal. Take an export instead of trying to revive the checkpoint.
264
+ for await (const row of client.repo.export('acme', 'catalog')) {
265
+ // ...
266
+ }
267
+ }
268
+ }
269
+ ```
270
+
271
+ **Why.** Each retired value pointed at a route the server no longer serves;
272
+ leaving them in the vocabulary would send readers to a 404 (PR #10020).
273
+
274
+ ### Added
275
+
276
+ - **`client.repo.export(orgName, repoName, options?)`** — an
277
+ `AsyncGenerator<RepositoryExportRow>` over a verified export stream. Options:
278
+ `mode` (`'heads'`, the default, or `'ops'`), `sinceRepoSeq` (delta base;
279
+ `0` means a full export), `atRepoSeq` (pin the fence), `prefer`
280
+ (`'sync'` | `'async'`), `token`, `session`, `afterDurableId`,
281
+ `pollIntervalMs`, and `signal`.
282
+
283
+ - **`client.repo.exportPrepare(orgName, repoName, options?)`** — returns
284
+ `{ exportToken, atRepoSeq }` without waiting for the fold or reading the
285
+ body. Takes `mode`, `sinceRepoSeq`, `atRepoSeq`, and `signal`.
286
+
287
+ - **`RepositoryExportSession` / `createRepositoryExportSession()`** — the
288
+ rolling verification state of one logical export: the opening `header`, the
289
+ resume cursor `lastDurableId`, `rowCount`, `complete`, and — once the stream
290
+ is finalized — `contentSha256`. Pass the same session back into `export` to
291
+ resume a stream that broke:
292
+
293
+ ```ts
294
+ import {
295
+ createRepositoryExportSession,
296
+ RepositoryExportError,
297
+ } from '@warmhub/sdk-ts'
298
+
299
+ const session = createRepositoryExportSession()
300
+ const rows: unknown[] = []
301
+ for (let attempt = 0; attempt < 3 && !session.complete; attempt += 1) {
302
+ try {
303
+ // The session's own cursor and fence pin the resumed request; do not
304
+ // recompute them.
305
+ for await (const row of client.repo.export('acme', 'catalog', {
306
+ session,
307
+ })) {
308
+ rows.push(row)
309
+ }
310
+ } catch (error) {
311
+ if (
312
+ !(error instanceof RepositoryExportError) ||
313
+ error.reason !== 'truncated'
314
+ ) {
315
+ throw error
316
+ }
317
+ }
318
+ }
319
+ console.log(session.rowCount, session.contentSha256)
320
+ ```
321
+
322
+ - **`readRepositoryExportStream(source, session, expected, options?)`** — read
323
+ a response body you fetched with your own transport, verified against a
324
+ session as it goes. `client.repo.export` is the usual entry point.
325
+
326
+ - **`restoreRepositoryExportSession(source, options?)`** — the offline twin:
327
+ replay a saved export's bytes back through the same header, canonical-row,
328
+ ordering and trailer checks, and get the session back rather than the rows.
329
+ Pass `{ partial: true }` for a download that was interrupted, then resume
330
+ from the session it returns.
331
+
332
+ ```ts
333
+ import { createReadStream } from 'node:fs'
334
+ import { restoreRepositoryExportSession } from '@warmhub/sdk-ts'
335
+
336
+ const session = await restoreRepositoryExportSession(
337
+ createReadStream('catalog.ndjson'),
338
+ { partial: true },
339
+ )
340
+ for await (const row of client.repo.export('acme', 'catalog', { session })) {
341
+ // continues from session.lastDurableId
342
+ }
343
+ ```
344
+
345
+ - **`applyRepositoryExportDelta(base, delta)`** — fold a delta export onto a
346
+ base by `durableId`: active rows upsert, tombstones remove. Pure; neither
347
+ argument is mutated. Both arguments accept either an iterable of rows or a
348
+ `Map` returned by a previous fold, so folds chain.
349
+
350
+ ```ts
351
+ import { applyRepositoryExportDelta } from '@warmhub/sdk-ts'
352
+
353
+ const session = createRepositoryExportSession()
354
+ const base = []
355
+ for await (const row of client.repo.export('acme', 'catalog', { session })) {
356
+ base.push(row)
357
+ }
358
+ const fence = session.header?.atRepoSeq ?? 0
359
+
360
+ // Later: only what changed since that fence.
361
+ const delta = []
362
+ for await (const row of client.repo.export('acme', 'catalog', {
363
+ sinceRepoSeq: fence,
364
+ })) {
365
+ delta.push(row)
366
+ }
367
+
368
+ const current = applyRepositoryExportDelta(base, delta)
369
+ ```
370
+
371
+ `wref` is an opaque rendering and is never keyed on, so the fold survives a
372
+ rename.
373
+
374
+ - **`RepositoryExportError`** with a typed `reason`:
375
+ `'count_mismatch'`, `'digest_mismatch'`, `'fence_mismatch'`,
376
+ `'header_invalid'`, `'row_invalid'`, `'row_out_of_order'`,
377
+ `'since_below_epoch_floor'`, `'trailer_invalid'`, `'truncated'`. Every reason
378
+ except `'since_below_epoch_floor'` is a local verification verdict — the
379
+ bytes did not prove what the trailer claimed. `'since_below_epoch_floor'` is
380
+ the server refusing a delta base it no longer retains; recover by re-running
381
+ the export with `sinceRepoSeq: 0`.
382
+
383
+ - **`RepositoryExportRowMap` / `RepositoryExportRows`** — the collection types
384
+ `applyRepositoryExportDelta` accepts and returns.
385
+
386
+ ### Behavior worth knowing
387
+
388
+ - **A redeemed token's bytes come from storage, not the API, and the client
389
+ checks what the API said.** A complete redemption answers with a short-lived
390
+ presigned URL rather than the export bytes, and the SDK fetches that URL
391
+ *without* your WarmHub bearer: the signature in the URL is the whole
392
+ capability, and the storage host has no business seeing a credential. The
393
+ record count and content digest the session derives are then checked against
394
+ the ones the API named — a download that does not match raises
395
+ `count_mismatch` or `digest_mismatch` rather than handing you rows nothing
396
+ independent vouched for. A refused or expired URL re-redeems the token, which
397
+ is a deterministic lookup, and retries; a break after rows have already been
398
+ yielded surfaces as an error instead, because a silent restart would hand you
399
+ those rows twice.
400
+
401
+ - **Ordering is verified, in `heads` mode only.** A heads export is a set,
402
+ emitted in ascending `durableId` by unsigned UTF-8 bytes, and that order is
403
+ part of what the digest proves. A row that does not advance the cursor raises
404
+ `row_out_of_order`. An `ops` export is a log — it repeats identities by
405
+ design — and is not order-checked.
406
+
407
+ - **Resume trailers are segment-scoped.** A resumed request carries no header,
408
+ and its trailer covers only that segment's rows. The session keeps two
409
+ hashers: a per-segment one the trailer is checked against, and a whole-stream
410
+ one that accumulates across segments. `session.contentSha256` is the
411
+ whole-stream value, and is what you should record as the export's digest.
412
+
413
+ - **A trailer failure poisons the session.** `truncated` is the resumable
414
+ failure. `count_mismatch`, `digest_mismatch`, `fence_mismatch` and
415
+ `trailer_invalid` are not: the whole-stream digest has already absorbed that
416
+ segment's rows, so the session refuses to resume and you must start a new
417
+ one.
418
+
419
+ - **`afterDurableId` requires a session with a header.** A resumed request gets
420
+ no header back, so with nothing to check the repository and fence against the
421
+ call is refused before it is sent. Reuse the session the broken stream wrote,
422
+ or rebuild one with `restoreRepositoryExportSession`.
423
+
424
+ - **The async redemption poll has a floor of 50 ms**, and defaults to 1000 ms.
425
+ Each poll re-runs the full authorization gate, so `pollIntervalMs: 0` — or a
426
+ `NaN` from an unvalidated config value — is clamped rather than honored.
427
+
428
+ - **Flat server error bodies keep their message.** The export route answers
429
+ with a flat `{ code, message }` body rather than the client's usual nested
430
+ `{ error: { ... } }` envelope. Those responses are re-wrapped before the
431
+ standard mapping runs, so classification stays standard and the server's
432
+ message is not discarded.
433
+
434
+ ### Still supported
435
+
436
+ The checkpoint **read** plane is untouched: `client.repo.checkpoint.status`,
437
+ `.latest`, `.getAccess`, and `verifyRepositoryCheckpointArchive` from
438
+ `@warmhub/sdk-ts/checkpoint` all continue to work against archives already in
439
+ object storage. Nothing mints new ones.
440
+
441
+ **Deprecation horizon:** those read surfaces are supported until the announced
442
+ cleanup, 30 days after GA. Their removal is a separately-filed post-GA change.
443
+ Move download-and-verify workflows to `client.repo.export` before then.
package/changelog.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "sdk": "sdk",
4
+ "baseline": {
5
+ "version": "0.127.0",
6
+ "markdown": "# @warmhub/sdk-ts changelog\n\nRelease 0.127.0.\n\nChangelog generation is temporarily disabled while the release integration is repaired.\n\n\nEarlier changelog shipped with @warmhub/sdk-ts 0.126.0:\n\n# @warmhub/sdk-ts changelog\n\nRelease 0.126.0.\n\nChangelog generation is temporarily disabled while the release integration is repaired.\n\n\nEarlier changelog shipped with @warmhub/sdk-ts 0.125.0:\n\n# @warmhub/sdk-ts changelog\n\nRelease 0.125.0.\n\nChangelog generation is temporarily disabled while the release integration is repaired.\n\n\nEarlier changelog shipped with @warmhub/sdk-ts 0.124.0:\n\n# @warmhub/sdk-ts changelog\n\nRelease 0.124.0.\n\nChangelog generation is temporarily disabled while the release integration is repaired.\n\n\nEarlier changelog shipped with @warmhub/sdk-ts 0.123.0:\n\n# @warmhub/sdk-ts changelog\n\nRelease 0.123.0.\n\nChangelog generation is temporarily disabled while the release integration is repaired.\n\n\nEarlier changelog shipped with @warmhub/sdk-ts 0.122.3:\n\n# @warmhub/sdk-ts changelog\n\nRelease 0.122.3.\n\nChangelog generation is temporarily disabled while the release integration is repaired.\n\n\nEarlier changelog shipped with @warmhub/sdk-ts 0.120.0:\n\n# Changelog — `@warmhub/sdk-ts`\n\n## How to read this file\n\nEntries are grouped by release. `## Unreleased` collects everything merged but\nnot yet published; the release pipeline is the sole version authority and\nstamps the exact version at publish time, so no heading here ever invents one.\n\nWithin a release, four change sections appear, in this order. A section is\npresent only when it has entries; a release may add plain narrative sections\nafter them (\"Behavior worth knowing\", \"Still supported\") when the change needs\ncontext that is not itself a change entry.\n\n- **Breaking** — your code must change. Every item is written as a migration:\n what it was → what it is now → the exact edit, with before/after code → why,\n in one line, citing the ruling.\n- **Added** — new surface. Nothing to do unless you want it.\n- **Fixed** — behavior that was wrong and now is not.\n- **Removed** — surface that is gone with nothing to put in its place. Anything\n a caller can migrate to is filed under Breaking instead, so this section is\n often absent.\n\nWarmHub's source repository is private, so rulings are cited by pull-request\nnumber rather than linked. Release notes live at\n<https://docs.warmhub.ai/releases/overview/>.\n\nThe Python SDK, `warmhub`, keeps the same file in the same shape.\n\n---\n\n## Unreleased\n\n## 0.120.0\n\nRepository export v3. The exact version is stamped at publish time by the\nrelease pipeline; source manifests stay at `0.0.0-development`.\n\n### Breaking\n\n#### Checkpoint generation is retired — take an export instead\n\n**What it was.** Producing a fresh snapshot of a repository meant asking the\nserver to mint a checkpoint, polling until the job completed, requesting a\nshort-lived signed URL for the archive, downloading it outside the SDK, and\nverifying the bytes locally.\n\n**What it is now.** One call that streams verified rows, with the header,\nper-row canonical decode, ordering and trailer checks applied as the bytes\narrive. There is no job to start, no state to poll, and no separate verify\nstep — a row that reaches you has already been proven.\n\n**Migration.**\n\nBefore:\n\n```ts\nimport { createReadStream } from 'node:fs'\nimport { writeFile } from 'node:fs/promises'\nimport { Readable } from 'node:stream'\nimport { verifyRepositoryCheckpointArchive } from '@warmhub/sdk-ts/checkpoint'\n\nlet status = await client.repo.checkpoint.generate('acme', 'catalog')\nwhile (status.state !== 'complete') {\n await new Promise((resolve) => setTimeout(resolve, 1_000))\n status = await client.repo.checkpoint.status('acme', 'catalog', {\n checkpointId: status.checkpointId,\n })\n}\n\nconst access = await client.repo.checkpoint.getAccess('acme', 'catalog', {\n checkpoint: 'latest',\n artifact: 'archive',\n})\n// Signed URL: fetched WITHOUT the WarmHub bearer token.\nconst archive = await fetch(access.url)\nawait writeFile('catalog.zip', Readable.fromWeb(archive.body))\nawait verifyRepositoryCheckpointArchive(createReadStream('catalog.zip'))\n```\n\nAfter:\n\n```ts\nfor await (const row of client.repo.export('acme', 'catalog')) {\n console.log(row.kind, row.durableId, row.version)\n}\n```\n\nIf you were relying on `generate` returning before the download — starting the\nbuild in one process and fetching it in another, or on another machine — that\nshape survives as the async token flow:\n\n```ts\n// Process A: ask for it, get a token, do not read a byte.\nconst prepared = await client.repo.exportPrepare('acme', 'catalog')\nconsole.log(prepared.exportToken, prepared.atRepoSeq)\n\n// Process B: redeem it. `export` polls while the fold is still running.\nfor await (const row of client.repo.export('acme', 'catalog', {\n token: prepared.exportToken,\n})) {\n // ...\n}\n```\n\nThe token is redeemable only at that repository's URL, by a principal holding\ncheckpoint read there.\n\n**Why.** Export format v3 replaces checkpoint generation outright; the whole\ngeneration pipeline is deleted server-side (PR #10020).\n\n#### `client.repo.checkpoint.generate` and `.retry` are gone\n\n**What it was.** `generate(orgName, repoName, { atLeastRepoSeq })` minted a\ncheckpoint; `retry(orgName, repoName, checkpointId)` re-ran a failed one. Both\nreturned a `RepositoryCheckpointStatus`.\n\n**What it is now.** Neither method exists, and the tRPC procedures behind them\n(`repo.checkpoint.generate`, `repo.checkpoint.retry`) are no longer served. The\n`repo:checkpoint-generate` scope no longer buys anything.\n\n**Migration.** Replace both with `client.repo.export`.\n\nBefore:\n\n```ts\nconst fresh = await client.repo.checkpoint.generate('acme', 'catalog', {\n atLeastRepoSeq: 42,\n})\nconst recovered = await client.repo.checkpoint.retry(\n 'acme',\n 'catalog',\n failed.checkpointId,\n)\n```\n\nAfter:\n\n```ts\n// `atRepoSeq` pins the fence the way `atLeastRepoSeq` asked for a floor.\n// Omit it and the server pins current and echoes it back in the header.\nfor await (const row of client.repo.export('acme', 'catalog', {\n atRepoSeq: 42,\n})) {\n // ...\n}\n```\n\nThere is no retry to replace: an export is a read, so a failed one is re-read,\nnot re-queued.\n\n**Why.** Same retirement (PR #10020).\n\n#### Failed checkpoint statuses now all answer `nextAction: 'none'`\n\n**What it was.** `RepositoryCheckpointStatus` carried `nextAction: 'retry'` for\n`deadline_exceeded` and `attempts_exhausted`, and `nextAction: 'generate'` for\n`invalid_source`. Each named a procedure a reader could call.\n\n**What it is now.** Those three failure codes join `repository_deleted` in a\nsingle variant with `nextAction: 'none'`. The `'retry'` and `'generate'`\nmembers are gone from the union, so a `switch` over `nextAction` that had cases\nfor them will no longer typecheck against them.\n\n**Migration.**\n\nBefore:\n\n```ts\nif (status.state === 'failed') {\n switch (status.nextAction) {\n case 'retry':\n await client.repo.checkpoint.retry('acme', 'catalog', status.checkpointId)\n break\n case 'generate':\n await client.repo.checkpoint.generate('acme', 'catalog')\n break\n case 'contact_support':\n report(status.checkpointId, status.failureCode)\n break\n }\n}\n```\n\nAfter:\n\n```ts\nif (status.state === 'failed') {\n if (status.nextAction === 'contact_support') {\n report(status.checkpointId, status.failureCode)\n } else {\n // Terminal. Take an export instead of trying to revive the checkpoint.\n for await (const row of client.repo.export('acme', 'catalog')) {\n // ...\n }\n }\n}\n```\n\n**Why.** Each retired value pointed at a route the server no longer serves;\nleaving them in the vocabulary would send readers to a 404 (PR #10020).\n\n### Added\n\n- **`client.repo.export(orgName, repoName, options?)`** — an\n `AsyncGenerator<RepositoryExportRow>` over a verified export stream. Options:\n `mode` (`'heads'`, the default, or `'ops'`), `sinceRepoSeq` (delta base;\n `0` means a full export), `atRepoSeq` (pin the fence), `prefer`\n (`'sync'` | `'async'`), `token`, `session`, `afterDurableId`,\n `pollIntervalMs`, and `signal`.\n\n- **`client.repo.exportPrepare(orgName, repoName, options?)`** — returns\n `{ exportToken, atRepoSeq }` without waiting for the fold or reading the\n body. Takes `mode`, `sinceRepoSeq`, `atRepoSeq`, and `signal`.\n\n- **`RepositoryExportSession` / `createRepositoryExportSession()`** — the\n rolling verification state of one logical export: the opening `header`, the\n resume cursor `lastDurableId`, `rowCount`, `complete`, and — once the stream\n is finalized — `contentSha256`. Pass the same session back into `export` to\n resume a stream that broke:\n\n ```ts\n import {\n createRepositoryExportSession,\n RepositoryExportError,\n } from '@warmhub/sdk-ts'\n\n const session = createRepositoryExportSession()\n const rows: unknown[] = []\n for (let attempt = 0; attempt < 3 && !session.complete; attempt += 1) {\n try {\n // The session's own cursor and fence pin the resumed request; do not\n // recompute them.\n for await (const row of client.repo.export('acme', 'catalog', {\n session,\n })) {\n rows.push(row)\n }\n } catch (error) {\n if (\n !(error instanceof RepositoryExportError) ||\n error.reason !== 'truncated'\n ) {\n throw error\n }\n }\n }\n console.log(session.rowCount, session.contentSha256)\n ```\n\n- **`readRepositoryExportStream(source, session, expected, options?)`** — read\n a response body you fetched with your own transport, verified against a\n session as it goes. `client.repo.export` is the usual entry point.\n\n- **`restoreRepositoryExportSession(source, options?)`** — the offline twin:\n replay a saved export's bytes back through the same header, canonical-row,\n ordering and trailer checks, and get the session back rather than the rows.\n Pass `{ partial: true }` for a download that was interrupted, then resume\n from the session it returns.\n\n ```ts\n import { createReadStream } from 'node:fs'\n import { restoreRepositoryExportSession } from '@warmhub/sdk-ts'\n\n const session = await restoreRepositoryExportSession(\n createReadStream('catalog.ndjson'),\n { partial: true },\n )\n for await (const row of client.repo.export('acme', 'catalog', { session })) {\n // continues from session.lastDurableId\n }\n ```\n\n- **`applyRepositoryExportDelta(base, delta)`** — fold a delta export onto a\n base by `durableId`: active rows upsert, tombstones remove. Pure; neither\n argument is mutated. Both arguments accept either an iterable of rows or a\n `Map` returned by a previous fold, so folds chain.\n\n ```ts\n import { applyRepositoryExportDelta } from '@warmhub/sdk-ts'\n\n const session = createRepositoryExportSession()\n const base = []\n for await (const row of client.repo.export('acme', 'catalog', { session })) {\n base.push(row)\n }\n const fence = session.header?.atRepoSeq ?? 0\n\n // Later: only what changed since that fence.\n const delta = []\n for await (const row of client.repo.export('acme', 'catalog', {\n sinceRepoSeq: fence,\n })) {\n delta.push(row)\n }\n\n const current = applyRepositoryExportDelta(base, delta)\n ```\n\n `wref` is an opaque rendering and is never keyed on, so the fold survives a\n rename.\n\n- **`RepositoryExportError`** with a typed `reason`:\n `'count_mismatch'`, `'digest_mismatch'`, `'fence_mismatch'`,\n `'header_invalid'`, `'row_invalid'`, `'row_out_of_order'`,\n `'since_below_epoch_floor'`, `'trailer_invalid'`, `'truncated'`. Every reason\n except `'since_below_epoch_floor'` is a local verification verdict — the\n bytes did not prove what the trailer claimed. `'since_below_epoch_floor'` is\n the server refusing a delta base it no longer retains; recover by re-running\n the export with `sinceRepoSeq: 0`.\n\n- **`RepositoryExportRowMap` / `RepositoryExportRows`** — the collection types\n `applyRepositoryExportDelta` accepts and returns.\n\n### Behavior worth knowing\n\n- **A redeemed token's bytes come from storage, not the API, and the client\n checks what the API said.** A complete redemption answers with a short-lived\n presigned URL rather than the export bytes, and the SDK fetches that URL\n *without* your WarmHub bearer: the signature in the URL is the whole\n capability, and the storage host has no business seeing a credential. The\n record count and content digest the session derives are then checked against\n the ones the API named — a download that does not match raises\n `count_mismatch` or `digest_mismatch` rather than handing you rows nothing\n independent vouched for. A refused or expired URL re-redeems the token, which\n is a deterministic lookup, and retries; a break after rows have already been\n yielded surfaces as an error instead, because a silent restart would hand you\n those rows twice.\n\n- **Ordering is verified, in `heads` mode only.** A heads export is a set,\n emitted in ascending `durableId` by unsigned UTF-8 bytes, and that order is\n part of what the digest proves. A row that does not advance the cursor raises\n `row_out_of_order`. An `ops` export is a log — it repeats identities by\n design — and is not order-checked.\n\n- **Resume trailers are segment-scoped.** A resumed request carries no header,\n and its trailer covers only that segment's rows. The session keeps two\n hashers: a per-segment one the trailer is checked against, and a whole-stream\n one that accumulates across segments. `session.contentSha256` is the\n whole-stream value, and is what you should record as the export's digest.\n\n- **A trailer failure poisons the session.** `truncated` is the resumable\n failure. `count_mismatch`, `digest_mismatch`, `fence_mismatch` and\n `trailer_invalid` are not: the whole-stream digest has already absorbed that\n segment's rows, so the session refuses to resume and you must start a new\n one.\n\n- **`afterDurableId` requires a session with a header.** A resumed request gets\n no header back, so with nothing to check the repository and fence against the\n call is refused before it is sent. Reuse the session the broken stream wrote,\n or rebuild one with `restoreRepositoryExportSession`.\n\n- **The async redemption poll has a floor of 50 ms**, and defaults to 1000 ms.\n Each poll re-runs the full authorization gate, so `pollIntervalMs: 0` — or a\n `NaN` from an unvalidated config value — is clamped rather than honored.\n\n- **Flat server error bodies keep their message.** The export route answers\n with a flat `{ code, message }` body rather than the client's usual nested\n `{ error: { ... } }` envelope. Those responses are re-wrapped before the\n standard mapping runs, so classification stays standard and the server's\n message is not discarded.\n\n### Still supported\n\nThe checkpoint **read** plane is untouched: `client.repo.checkpoint.status`,\n`.latest`, `.getAccess`, and `verifyRepositoryCheckpointArchive` from\n`@warmhub/sdk-ts/checkpoint` all continue to work against archives already in\nobject storage. Nothing mints new ones.\n\n**Deprecation horizon:** those read surfaces are supported until the announced\ncleanup, 30 days after GA. Their removal is a separately-filed post-GA change.\nMove download-and-verify workflows to `client.repo.export` before then.\n"
7
+ },
8
+ "releases": [
9
+ {
10
+ "version": "0.128.0",
11
+ "sourceSha": "f990f080d6f8b7310700b1d430ee1aa11dcaaf23",
12
+ "notes": [
13
+ {
14
+ "id": "published-changelog-artifacts",
15
+ "body": "### Documentation\n\nVersioned release notes are available as [HTML, Markdown, and JSON](https://docs.warmhub.ai/sdk/typescript/changelog/)."
16
+ }
17
+ ]
18
+ },
19
+ {
20
+ "version": "0.129.0",
21
+ "sourceSha": "e3b82b4b08936c141a727fc4f072a5d9ee1eeb31",
22
+ "notes": []
23
+ }
24
+ ]
25
+ }
@@ -1,2 +1,2 @@
1
- import {K as K$1,J as J$1,L as L$1,P as P$1,H as H$1,M,N as N$1,O,I}from'./chunk-4OSQJILK.js';import {mkdtemp,chmod,rm,open}from'fs/promises';import {tmpdir}from'os';import {join}from'path';import {createHash}from'crypto';import {Writable}from'stream';import {pipeline}from'stream/promises';import {callbackify}from'util';import {createInflateRaw,crc32}from'zlib';import {once}from'events';import {createWriteStream,createReadStream}from'fs';var d=class extends Error{constructor(e){super(rt(e));this.reason=e;}reason;name="RepositoryCheckpointVerificationError"};function rt(r){switch(r){case "source_failed":return "Repository checkpoint source could not be read";case "archive_invalid":return "Repository checkpoint archive is invalid";case "archive_integrity_mismatch":return "Repository checkpoint archive integrity does not match";case "manifest_invalid":return "Repository checkpoint manifest is invalid";case "chunk_invalid":return "Repository checkpoint chunk is invalid";case "duplicate_durable_id":return "Repository checkpoint contains a duplicate durable identity";case "scratch_cleanup_failed":return "Repository checkpoint verifier scratch cleanup failed"}}var H=67324752,ht=33639248,dt=134695760,ut=101075792,ft=117853008,lt=101010256,Y=2056,Z=8,R=45,q=813,z=65535,m=4294967295,W=33,yt=33188,S=64*1024,pt=new RegExp(`^records/[0-9a-f]{64}/part-\\d{${I}}\\.jsonl$`),mt=new TextDecoder("utf-8",{fatal:true}),E=class extends Error{};async function G(r,t){let e=new C(r);try{let i=[],n=new Set,s=!1;for(;await e.peekU32()===H;){if(s)throw new Error("manifest must be the final entry");let c=BigInt(e.position),h=await e.readExact(30);It(h);let f=h.readUInt16LE(26),p=h.readUInt16LE(28);if(f===0||p!==20)throw new Error("local entry metadata is invalid");let a=K(await e.readExact(f));if(St(a),n.has(a))throw new Error("archive entry is duplicated");n.add(a),s=a===J$1,bt(await e.readExact(p));let y=t(a),l=await wt(e,y),et=await e.readExact(24);kt(et,l),await y.finish(),i.push({name:a,localOffset:c,...l});}if(!s||i.length===0)throw new Error("archive manifest entry is missing");return await vt(e,i),{...await e.finish(),entryNames:i.map(c=>c.name)}}finally{await e.close();}}async function wt(r,t){let e=createInflateRaw({chunkSize:S}),i=0n,n=0n,s=0,o=callbackify(async a=>{try{await t.write(a);}catch(y){throw y instanceof Error?y:new Error("checkpoint sink failed")}s=crc32(a,s),n+=BigInt(a.byteLength);}),c=new Writable({highWaterMark:S,write(a,y,l){o(a,l);}}),h=pipeline(e,c),f=new A,p=Et(h,f);try{for(;!e.readableEnded;){let a=await r.readSome(S);if(!a)throw new Error("archive entry is truncated");let y=e.bytesWritten;await gt(e,a,f);let l=e.bytesWritten-y;if(!Number.isSafeInteger(l)||l<0||l>a.byteLength)throw new Error("archive inflater consumed invalid input");if(i+=BigInt(l),l<a.byteLength&&r.unread(a.subarray(l)),e.readableEnded)break;if(l===0)throw new Error("archive inflater made no progress")}return await h,{crc:s>>>0,compressedSize:i,uncompressedSize:n}}finally{e.destroyed||e.destroy(),await p;}}var A=class{#i;#t;fail(t){let e=t instanceof Error?t:new Error("archive inflater failed");this.#i=e,this.#t?.(e),this.#t=void 0;}async race(t){if(this.#i)throw this.#i;let e,i=new Promise((n,s)=>{e=o=>s(o),this.#t=e;});try{return await Promise.race([t,i])}finally{e&&this.#t===e&&(this.#t=void 0);}}};async function Et(r,t){try{await r;}catch(e){t.fail(e);}}async function gt(r,t,e){let i=new Promise((n,s)=>{r.write(t,o=>{o?s(o):n();});});await e.race(i);}async function vt(r,t){let e=BigInt(r.position);for(let h of t){let f=await r.readExact(46);Ut(f,h);let p=f.readUInt16LE(28),a=f.readUInt16LE(30);if(a!==28||f.readUInt16LE(32)!==0)throw new Error("central entry metadata is invalid");if(K(await r.readExact(p))!==h.name)throw new Error("central entry order differs");Lt(await r.readExact(a),h);}let i=BigInt(r.position)-e,n=BigInt(r.position),s=await r.readExact(56);if(w(s,0,ut),s.readBigUInt64LE(4)!==44n||s.readUInt16LE(12)!==q||s.readUInt16LE(14)!==R||s.readUInt32LE(16)!==0||s.readUInt32LE(20)!==0||s.readBigUInt64LE(24)!==BigInt(t.length)||s.readBigUInt64LE(32)!==BigInt(t.length)||s.readBigUInt64LE(40)!==i||s.readBigUInt64LE(48)!==e)throw new Error("ZIP64 end record is invalid");let o=await r.readExact(20);if(w(o,0,ft),o.readUInt32LE(4)!==0||o.readBigUInt64LE(8)!==n||o.readUInt32LE(16)!==1)throw new Error("ZIP64 locator is invalid");let c=await r.readExact(22);if(w(c,0,lt),c.readUInt16LE(4)!==0||c.readUInt16LE(6)!==0||c.readUInt16LE(8)!==z||c.readUInt16LE(10)!==z||c.readUInt32LE(12)!==m||c.readUInt32LE(16)!==m||c.readUInt16LE(20)!==0)throw new Error("classic ZIP end record is invalid")}function It(r){if(w(r,0,H),r.readUInt16LE(4)!==R||r.readUInt16LE(6)!==Y||r.readUInt16LE(8)!==Z||r.readUInt16LE(10)!==0||r.readUInt16LE(12)!==W||r.readUInt32LE(14)!==0||r.readUInt32LE(18)!==m||r.readUInt32LE(22)!==m)throw new Error("local entry header is invalid")}function bt(r){if(r.readUInt16LE(0)!==1||r.readUInt16LE(2)!==16||r.readBigUInt64LE(4)!==0n||r.readBigUInt64LE(12)!==0n)throw new Error("local ZIP64 extra is invalid")}function kt(r,t){if(w(r,0,dt),r.readUInt32LE(4)!==t.crc||r.readBigUInt64LE(8)!==t.compressedSize||r.readBigUInt64LE(16)!==t.uncompressedSize)throw new Error("archive data descriptor is invalid")}function Ut(r,t){if(w(r,0,ht),r.readUInt16LE(4)!==q||r.readUInt16LE(6)!==R||r.readUInt16LE(8)!==Y||r.readUInt16LE(10)!==Z||r.readUInt16LE(12)!==0||r.readUInt16LE(14)!==W||r.readUInt32LE(16)!==t.crc||r.readUInt32LE(20)!==m||r.readUInt32LE(24)!==m||r.readUInt16LE(34)!==0||r.readUInt16LE(36)!==0||(r.readUInt32LE(38)>>>16&65535)!==yt||r.readUInt32LE(42)!==m)throw new Error("central entry header is invalid")}function Lt(r,t){if(r.readUInt16LE(0)!==1||r.readUInt16LE(2)!==24||r.readBigUInt64LE(4)!==t.uncompressedSize||r.readBigUInt64LE(12)!==t.compressedSize||r.readBigUInt64LE(20)!==t.localOffset)throw new Error("central ZIP64 extra is invalid")}function St(r){if(r!==J$1&&!pt.test(r))throw new Error("archive entry name is invalid")}function K(r){let t=mt.decode(r);if(Buffer.byteLength(t,"utf8")!==r.byteLength)throw new Error("archive entry name is not canonical UTF-8");return t}function w(r,t,e){if(r.readUInt32LE(t)!==e)throw new Error("archive signature is invalid")}var C=class{#i;#t=createHash("sha256");#e=[];#r=0;#n=false;#o=0;position=0;constructor(t){this.#i=At(t)[Symbol.asyncIterator]();}async peekU32(){let t=await this.readExact(4);return this.unread(t),t.readUInt32LE(0)}async readExact(t){let e=Buffer.allocUnsafe(t),i=0;for(;i<t;){let n=await this.readSome(t-i);if(!n)throw new Error("archive is truncated");e.set(n,i),i+=n.byteLength;}return e}async readSome(t){for(;this.#e.length===0;)if(!await this.#s())return;let e=this.#e[0];if(!e)return;let i=e.byteLength-this.#r,n=Math.min(t,i),s=e.subarray(this.#r,this.#r+n);return this.#r+=n,this.position+=n,this.#r===e.byteLength&&(this.#e.shift(),this.#r=0),s}unread(t){if(t.byteLength!==0){if(this.#r>0){let e=this.#e[0];if(!e)throw new Error("archive reader queue is inconsistent");let i=e.subarray(this.#r);this.#e[0]=i,this.#r=0;}this.#e.unshift(t),this.position-=t.byteLength;}}async finish(){if(this.#e.length>0||await this.#s())throw new Error("archive contains trailing bytes");return {byteLength:this.#o,sha256:this.#t.digest("hex")}}async close(){try{await this.#i.return?.();}catch{}}async#s(){if(this.#n)return false;for(;;){let t;try{t=await this.#i.next();}catch{throw new E}if(t.done)return this.#n=true,false;if(!(t.value instanceof Uint8Array))throw new E;if(t.value.byteLength!==0){if(this.#t.update(t.value),this.#o+=t.value.byteLength,!Number.isSafeInteger(this.#o))throw new Error("archive byte length is unsafe");return this.#e.push(t.value),true}}}};function At(r){return Symbol.asyncIterator in r?r:{async*[Symbol.asyncIterator](){let t=r.getReader(),e=false;try{for(;;){let{value:i,done:n}=await t.read();if(n){e=!0;return}yield i;}}finally{if(!e)try{await t.cancel();}catch{}t.releaseLock();}}}}var xt=4*1024*1024,j=8,J=64*1024,Tt=16*1024,u=4,$=4,Ot=1,Vt=u+Ot,X=Vt+$,g=class extends Error{},k=class{#i;#t;#e=[];#r=0;#n;constructor(t,e={}){this.#i=t;let i=e.sortBudgetBytes??xt;if(this.#n=e.mergeFanIn??j,!Number.isSafeInteger(i)||i<X||!Number.isSafeInteger(this.#n)||this.#n<2||this.#n>j)throw new Error("checkpoint identity sort limits are invalid");this.#t=new _(i);}add(t){let e=Buffer.byteLength(t,"utf8");if(!this.#t.canFit(e))throw new Error("checkpoint durable identity exceeds the sort budget");if(this.#t.canAdd(e)){this.#t.add(t,e);return}return this.#o(t,e)}async finish(){if(this.#e.length===0){this.#t.sortAndAssertUnique(),this.#t.clear();return}for(this.#t.isEmpty||await this.#o();this.#e.length>this.#n;){let e=[];for(let i=0;i<this.#e.length;i+=this.#n){let n=this.#e.slice(i,i+this.#n),s=this.#s();await Ft(n,s),await Promise.all(n.map(o=>rm(o,{force:true}))),e.push(s);}this.#e=e;}let t;await tt(this.#e,e=>{if(t&&P(t,e)===0)throw new g;t=e;});}async#o(t,e){let i=this.#s();this.#t.sortAndAssertUnique(),await Mt(i,this.#t),this.#e.push(i),this.#t.clear(),t!==void 0&&e!==void 0&&this.#t.add(t,e);}#s(){let t=join(this.#i,`identity-run-${String(this.#r).padStart(8,"0")}.bin`);return this.#r+=1,t}},_=class{byteBudget;#i;#t;#e=0;#r=0;constructor(t){this.byteBudget=t;let e=Math.floor(t/X);this.#t=new Uint32Array(e),this.#i=Buffer.allocUnsafe(t-e*$);}get isEmpty(){return this.#r===0}canAdd(t){return this.canFit(t)&&this.#r<this.#t.length&&this.#e+u+t<=this.#i.length}canFit(t){return t<=4294967295&&this.#t.length>0&&u+t<=this.#i.length}add(t,e){if(!this.canAdd(e))throw new Error("checkpoint durable identity exceeds the sort budget");let i=this.#e;this.#t[this.#r]=i,this.#i.writeUInt32BE(e,i),this.#i.write(t,i+u,e,"utf8"),this.#e+=u+e,this.#r+=1;}sortAndAssertUnique(){let t=this.#t.subarray(0,this.#r);t.sort((e,i)=>this.#n(e,i));for(let e=1;e<t.length;e+=1){let i=t[e-1],n=t[e];if(i===void 0||n===void 0)throw new Error("checkpoint identity offset is missing");if(this.#n(i,n)===0)throw new g}}clear(){this.#e=0,this.#r=0;}forEachSorted(t){return Dt(this.#t.subarray(0,this.#r),e=>t(this.#s(e)))}#n(t,e){return P(this.#o(t),this.#o(e))}#o(t){let e=this.#i.readUInt32BE(t);return this.#i.subarray(t+u,t+u+e)}#s(t){let e=this.#i.readUInt32BE(t);return this.#i.subarray(t,t+u+e)}};async function Mt(r,t){await Q(r,async e=>{let i=new U(e);await t.forEachSorted(n=>i.write(n)),await i.finish();});}async function Ft(r,t){await Q(t,async e=>{let i=new U(e);await tt(r,n=>i.writeValue(n)),await i.finish();});}async function Q(r,t){await(await open(r,"wx",384)).close();let i=createWriteStream(r,{flags:"a",mode:384,highWaterMark:J});try{await t(i),i.end(),await once(i,"close");}catch(n){throw i.destroy(),n}}var U=class{#i;#t=Buffer.allocUnsafe(J);#e=0;constructor(t){this.#i=t;}write(t){if(t.byteLength>this.#t.length)return this.#r(t);if(this.#e+t.byteLength>this.#t.length)return this.#n(t);this.#t.set(t,this.#e),this.#e+=t.byteLength;}writeValue(t){let e=u+t.byteLength;if(e>this.#t.length)return this.#o(t);if(this.#e+e>this.#t.length)return this.#s(t);this.#c(t);}async finish(){await this.#a();}async#r(t){await this.#a(),await b(this.#i,t);}async#n(t){await this.#a(),this.#t.set(t,this.#e),this.#e+=t.byteLength;}async#o(t){await this.#a();let e=Buffer.allocUnsafe(u);e.writeUInt32BE(t.byteLength,0),await b(this.#i,e),await b(this.#i,t);}async#s(t){await this.#a(),this.#c(t);}async#a(){if(this.#e===0)return;let t=this.#t.subarray(0,this.#e);await b(this.#i,t),this.#e=0;}#c(t){this.#t.writeUInt32BE(t.byteLength,this.#e),this.#t.set(t,this.#e+u),this.#e+=u+t.byteLength;}};async function tt(r,t){let e=r.map(i=>new B(i));try{for(await Promise.all(e.map(i=>i.advance()));;){let i;for(let s of e)s.current&&(!i||P(s.current,i.current)<0)&&(i=s);if(!i?.current)return;let n=t(i.current);n&&await n,await i.advance();}}finally{for(let i of e)i.close();}}var B=class{#i;#t;#e=Buffer.alloc(0);#r=0;current;constructor(t){this.#i=createReadStream(t,{highWaterMark:Tt}),this.#t=this.#i[Symbol.asyncIterator]();}async advance(){let t=await this.#n(u);if(!t){this.current=void 0;return}let e=t.readUInt32BE(0);if(e===0)throw new Error("checkpoint identity run is invalid");let i=await this.#n(e);if(!i)throw new Error("checkpoint identity run is truncated");this.current=i;}close(){this.#i.destroy();}async#n(t){if(t===0)return Buffer.alloc(0);if(this.#e.length-this.#r>=t){let n=this.#e.subarray(this.#r,this.#r+t);return this.#r+=t,n}let e=Buffer.allocUnsafe(t),i=0;for(;i<t;){if(this.#r<this.#e.length){let s=Math.min(t-i,this.#e.length-this.#r);this.#e.copy(e,i,this.#r,this.#r+s),this.#r+=s,i+=s;continue}let n=await this.#t.next();if(n.done){if(i===0)return;throw new Error("checkpoint identity run is truncated")}this.#e=n.value,this.#r=0;}return e}};async function Dt(r,t){for(let e of r){let i=t(e);i&&await i;}}async function b(r,t){await new Promise((e,i)=>{r.write(t,n=>{n?i(n):e();});});}function P(r,t){let e=Math.min(r.length,t.length);for(let i=0;i<e;i+=1){let n=r[i],s=t[i];if(n===void 0||s===void 0)throw new Error("checkpoint identity byte is missing");let o=n-s;if(o!==0)return o}return r.length-t.length}var L=class{#i;#t=new Map;#e=new Map;#r=[];#n;constructor(t,e={}){this.#i=new k(t,{sortBudgetBytes:e.identitySortBudgetBytes,mergeFanIn:e.identityMergeFanIn});}openEntry(t){if(t===J$1){let e=[];return {write:async i=>{e.push(i.slice());},finish:async()=>{this.#n=Buffer.concat(e);}}}return new N(t,this.#t,this.#e,this.#i,e=>this.#r.push(e))}async finish(){if(!this.#n)throw new Error("checkpoint manifest is missing");let t=await L$1(this.#n),e=t.streams.flatMap(i=>i.chunks.map(n=>({...n,shapeName:i.shapeName})));if(e.length!==this.#r.length)throw new Error("checkpoint chunk inventory does not match");for(let[i,n]of e.entries()){let s=this.#r[i];if(s.path!==n.path||s.shapeName!==n.shapeName||s.recordCount!==n.recordCount||s.byteLength!==n.byteLength||s.sha256!==n.sha256)throw new Error("checkpoint chunk integrity does not match")}for(let i of t.streams){let n=this.#t.get(i.shapeName);if(!n||P$1(n)!==i.recordCount)throw new Error("checkpoint stream does not match its manifest")}if(this.#t.size!==t.streams.length)throw new Error("checkpoint contains an undeclared stream");return await this.#i.finish(),{manifest:t,chunkCount:e.length}}},N=class{#i;#t;#e;#r;#n;#o=createHash("sha256");#s=[];#a=0;#c;#h=0;#d=0;constructor(t,e,i,n,s){this.#i=t,this.#t=e,this.#e=i,this.#r=n,this.#n=s;}async write(t){if(this.#o.update(t),this.#d+=t.byteLength,!Number.isSafeInteger(this.#d))throw new Error("checkpoint chunk byte length is unsafe");let e=0;for(;e<t.byteLength;){let i=t.indexOf(10,e);if(i<0)break;let n=this.#u(this.#l(t.subarray(e,i+1)));n&&await n,e=i+1;}e<t.byteLength&&this.#f(t.subarray(e));}async finish(){if(this.#s.length!==0||this.#h===0||!this.#c)throw new Error("checkpoint chunk framing is invalid");if(this.#d>H$1&&this.#h!==1)throw new Error("oversized checkpoint chunk has multiple rows");this.#n({path:this.#i,shapeName:this.#c,recordCount:this.#h,byteLength:this.#d,sha256:this.#o.digest("hex")});let t=this.#e.get(this.#c);if(!t)throw new Error("checkpoint stream chunk state is missing");t.previous={byteLength:this.#d};}#u(t){let e=M(t),i=e.kind==="shape"?e.wref:e.shapeName;if(this.#c!==void 0&&this.#c!==i)throw new Error("checkpoint chunk mixes Shape streams");this.#c=i,this.#h===0&&this.#y(i,t.byteLength);let n=this.#t.get(i)??N$1(i);this.#t.set(i,n),O(n,e);let s=this.#r.add(e.durableId);if(this.#h+=1,!Number.isSafeInteger(this.#h))throw new Error("checkpoint chunk row count is unsafe");return s}#f(t){if(t.byteLength!==0&&(this.#s.push(t.slice()),this.#a+=t.byteLength,!Number.isSafeInteger(this.#a)))throw new Error("checkpoint row byte length is unsafe")}#l(t){if(this.#s.length===0)return t;let e=Buffer.concat([...this.#s,t],this.#a+t.byteLength);return this.#s=[],this.#a=0,e}#y(t,e){let i=Number(this.#i.match(/part-(\d{10})\.jsonl$/)?.[1]??-1),n=this.#e.get(t)??{nextPart:0};if(i!==n.nextPart)throw new Error("checkpoint stream chunk parts are not contiguous");if(n.previous&&n.previous.byteLength<=H$1&&n.previous.byteLength+e<=H$1)throw new Error("checkpoint stream chunk closed before the byte limit");n.nextPart+=1,this.#e.set(t,n);}};async function Gt(r,t){return Kt(r,t)}async function Kt(r,t,e={}){let i=await mkdtemp(join(e.scratchParent??tmpdir(),"warmhub-checkpoint-verify-"));await chmod(i,448);let n,s;try{let o=new L(i,e),c=await G(r,p=>o.openEntry(p)),{manifest:h,chunkCount:f}=await o.finish();jt(t,c,h),n={valid:!0,checkpointId:h.checkpointId,repoSeq:h.repoSeq,recordCount:h.recordCount,chunkCount:f,totalBytes:c.byteLength,rootDigest:h.contentSha256,archiveDigest:c.sha256};}catch(o){s=Jt(o);}try{await rm(i,{recursive:!0,force:!0});}catch{s??=new d("scratch_cleanup_failed");}if(s)throw s;if(!n)throw new d("archive_invalid");return n}function jt(r,t,e){if(r&&(K$1.parse(r.sha256),r.checkpointId!==e.checkpointId||r.repoSeq!==e.repoSeq||r.byteLength!==t.byteLength||r.sha256!==t.sha256))throw new d("archive_integrity_mismatch")}function Jt(r){if(r instanceof d)return r;if(r instanceof E)return new d("source_failed");if(r instanceof g)return new d("duplicate_durable_id");let t=r instanceof Error?r.message:"";return t.includes("manifest")?new d("manifest_invalid"):t.includes("chunk")||t.includes("row")||t.includes("Shape stream")?new d("chunk_invalid"):new d("archive_invalid")}export{d as RepositoryCheckpointVerificationError,Gt as verifyRepositoryCheckpointArchive};//# sourceMappingURL=checkpoint.js.map
1
+ import {M,L as L$1,N as N$1,R as R$1,J as J$1,O,P as P$1,Q as Q$1,K as K$1}from'./chunk-UCK4ZIVG.js';import {mkdtemp,chmod,rm,open}from'fs/promises';import {tmpdir}from'os';import {join}from'path';import {createHash}from'crypto';import {Writable}from'stream';import {pipeline}from'stream/promises';import {callbackify}from'util';import {createInflateRaw,crc32}from'zlib';import {once}from'events';import {createWriteStream,createReadStream}from'fs';var d=class extends Error{constructor(e){super(rt(e));this.reason=e;}reason;name="RepositoryCheckpointVerificationError"};function rt(r){switch(r){case "source_failed":return "Repository checkpoint source could not be read";case "archive_invalid":return "Repository checkpoint archive is invalid";case "archive_integrity_mismatch":return "Repository checkpoint archive integrity does not match";case "manifest_invalid":return "Repository checkpoint manifest is invalid";case "chunk_invalid":return "Repository checkpoint chunk is invalid";case "duplicate_durable_id":return "Repository checkpoint contains a duplicate durable identity";case "scratch_cleanup_failed":return "Repository checkpoint verifier scratch cleanup failed"}}var H=67324752,ht=33639248,dt=134695760,ut=101075792,ft=117853008,lt=101010256,Y=2056,Z=8,R=45,q=813,z=65535,m=4294967295,W=33,yt=33188,S=64*1024,pt=new RegExp(`^records/[0-9a-f]{64}/part-\\d{${K$1}}\\.jsonl$`),mt=new TextDecoder("utf-8",{fatal:true}),E=class extends Error{};async function G(r,t){let e=new C(r);try{let i=[],n=new Set,s=!1;for(;await e.peekU32()===H;){if(s)throw new Error("manifest must be the final entry");let c=BigInt(e.position),h=await e.readExact(30);It(h);let f=h.readUInt16LE(26),p=h.readUInt16LE(28);if(f===0||p!==20)throw new Error("local entry metadata is invalid");let a=K(await e.readExact(f));if(St(a),n.has(a))throw new Error("archive entry is duplicated");n.add(a),s=a===L$1,bt(await e.readExact(p));let y=t(a),l=await wt(e,y),et=await e.readExact(24);kt(et,l),await y.finish(),i.push({name:a,localOffset:c,...l});}if(!s||i.length===0)throw new Error("archive manifest entry is missing");return await vt(e,i),{...await e.finish(),entryNames:i.map(c=>c.name)}}finally{await e.close();}}async function wt(r,t){let e=createInflateRaw({chunkSize:S}),i=0n,n=0n,s=0,o=callbackify(async a=>{try{await t.write(a);}catch(y){throw y instanceof Error?y:new Error("checkpoint sink failed")}s=crc32(a,s),n+=BigInt(a.byteLength);}),c=new Writable({highWaterMark:S,write(a,y,l){o(a,l);}}),h=pipeline(e,c),f=new A,p=Et(h,f);try{for(;!e.readableEnded;){let a=await r.readSome(S);if(!a)throw new Error("archive entry is truncated");let y=e.bytesWritten;await gt(e,a,f);let l=e.bytesWritten-y;if(!Number.isSafeInteger(l)||l<0||l>a.byteLength)throw new Error("archive inflater consumed invalid input");if(i+=BigInt(l),l<a.byteLength&&r.unread(a.subarray(l)),e.readableEnded)break;if(l===0)throw new Error("archive inflater made no progress")}return await h,{crc:s>>>0,compressedSize:i,uncompressedSize:n}}finally{e.destroyed||e.destroy(),await p;}}var A=class{#i;#t;fail(t){let e=t instanceof Error?t:new Error("archive inflater failed");this.#i=e,this.#t?.(e),this.#t=void 0;}async race(t){if(this.#i)throw this.#i;let e,i=new Promise((n,s)=>{e=o=>s(o),this.#t=e;});try{return await Promise.race([t,i])}finally{e&&this.#t===e&&(this.#t=void 0);}}};async function Et(r,t){try{await r;}catch(e){t.fail(e);}}async function gt(r,t,e){let i=new Promise((n,s)=>{r.write(t,o=>{o?s(o):n();});});await e.race(i);}async function vt(r,t){let e=BigInt(r.position);for(let h of t){let f=await r.readExact(46);Ut(f,h);let p=f.readUInt16LE(28),a=f.readUInt16LE(30);if(a!==28||f.readUInt16LE(32)!==0)throw new Error("central entry metadata is invalid");if(K(await r.readExact(p))!==h.name)throw new Error("central entry order differs");Lt(await r.readExact(a),h);}let i=BigInt(r.position)-e,n=BigInt(r.position),s=await r.readExact(56);if(w(s,0,ut),s.readBigUInt64LE(4)!==44n||s.readUInt16LE(12)!==q||s.readUInt16LE(14)!==R||s.readUInt32LE(16)!==0||s.readUInt32LE(20)!==0||s.readBigUInt64LE(24)!==BigInt(t.length)||s.readBigUInt64LE(32)!==BigInt(t.length)||s.readBigUInt64LE(40)!==i||s.readBigUInt64LE(48)!==e)throw new Error("ZIP64 end record is invalid");let o=await r.readExact(20);if(w(o,0,ft),o.readUInt32LE(4)!==0||o.readBigUInt64LE(8)!==n||o.readUInt32LE(16)!==1)throw new Error("ZIP64 locator is invalid");let c=await r.readExact(22);if(w(c,0,lt),c.readUInt16LE(4)!==0||c.readUInt16LE(6)!==0||c.readUInt16LE(8)!==z||c.readUInt16LE(10)!==z||c.readUInt32LE(12)!==m||c.readUInt32LE(16)!==m||c.readUInt16LE(20)!==0)throw new Error("classic ZIP end record is invalid")}function It(r){if(w(r,0,H),r.readUInt16LE(4)!==R||r.readUInt16LE(6)!==Y||r.readUInt16LE(8)!==Z||r.readUInt16LE(10)!==0||r.readUInt16LE(12)!==W||r.readUInt32LE(14)!==0||r.readUInt32LE(18)!==m||r.readUInt32LE(22)!==m)throw new Error("local entry header is invalid")}function bt(r){if(r.readUInt16LE(0)!==1||r.readUInt16LE(2)!==16||r.readBigUInt64LE(4)!==0n||r.readBigUInt64LE(12)!==0n)throw new Error("local ZIP64 extra is invalid")}function kt(r,t){if(w(r,0,dt),r.readUInt32LE(4)!==t.crc||r.readBigUInt64LE(8)!==t.compressedSize||r.readBigUInt64LE(16)!==t.uncompressedSize)throw new Error("archive data descriptor is invalid")}function Ut(r,t){if(w(r,0,ht),r.readUInt16LE(4)!==q||r.readUInt16LE(6)!==R||r.readUInt16LE(8)!==Y||r.readUInt16LE(10)!==Z||r.readUInt16LE(12)!==0||r.readUInt16LE(14)!==W||r.readUInt32LE(16)!==t.crc||r.readUInt32LE(20)!==m||r.readUInt32LE(24)!==m||r.readUInt16LE(34)!==0||r.readUInt16LE(36)!==0||(r.readUInt32LE(38)>>>16&65535)!==yt||r.readUInt32LE(42)!==m)throw new Error("central entry header is invalid")}function Lt(r,t){if(r.readUInt16LE(0)!==1||r.readUInt16LE(2)!==24||r.readBigUInt64LE(4)!==t.uncompressedSize||r.readBigUInt64LE(12)!==t.compressedSize||r.readBigUInt64LE(20)!==t.localOffset)throw new Error("central ZIP64 extra is invalid")}function St(r){if(r!==L$1&&!pt.test(r))throw new Error("archive entry name is invalid")}function K(r){let t=mt.decode(r);if(Buffer.byteLength(t,"utf8")!==r.byteLength)throw new Error("archive entry name is not canonical UTF-8");return t}function w(r,t,e){if(r.readUInt32LE(t)!==e)throw new Error("archive signature is invalid")}var C=class{#i;#t=createHash("sha256");#e=[];#r=0;#n=false;#o=0;position=0;constructor(t){this.#i=At(t)[Symbol.asyncIterator]();}async peekU32(){let t=await this.readExact(4);return this.unread(t),t.readUInt32LE(0)}async readExact(t){let e=Buffer.allocUnsafe(t),i=0;for(;i<t;){let n=await this.readSome(t-i);if(!n)throw new Error("archive is truncated");e.set(n,i),i+=n.byteLength;}return e}async readSome(t){for(;this.#e.length===0;)if(!await this.#s())return;let e=this.#e[0];if(!e)return;let i=e.byteLength-this.#r,n=Math.min(t,i),s=e.subarray(this.#r,this.#r+n);return this.#r+=n,this.position+=n,this.#r===e.byteLength&&(this.#e.shift(),this.#r=0),s}unread(t){if(t.byteLength!==0){if(this.#r>0){let e=this.#e[0];if(!e)throw new Error("archive reader queue is inconsistent");let i=e.subarray(this.#r);this.#e[0]=i,this.#r=0;}this.#e.unshift(t),this.position-=t.byteLength;}}async finish(){if(this.#e.length>0||await this.#s())throw new Error("archive contains trailing bytes");return {byteLength:this.#o,sha256:this.#t.digest("hex")}}async close(){try{await this.#i.return?.();}catch{}}async#s(){if(this.#n)return false;for(;;){let t;try{t=await this.#i.next();}catch{throw new E}if(t.done)return this.#n=true,false;if(!(t.value instanceof Uint8Array))throw new E;if(t.value.byteLength!==0){if(this.#t.update(t.value),this.#o+=t.value.byteLength,!Number.isSafeInteger(this.#o))throw new Error("archive byte length is unsafe");return this.#e.push(t.value),true}}}};function At(r){return Symbol.asyncIterator in r?r:{async*[Symbol.asyncIterator](){let t=r.getReader(),e=false;try{for(;;){let{value:i,done:n}=await t.read();if(n){e=!0;return}yield i;}}finally{if(!e)try{await t.cancel();}catch{}t.releaseLock();}}}}var xt=4*1024*1024,j=8,J=64*1024,Tt=16*1024,u=4,$=4,Ot=1,Vt=u+Ot,X=Vt+$,g=class extends Error{},k=class{#i;#t;#e=[];#r=0;#n;constructor(t,e={}){this.#i=t;let i=e.sortBudgetBytes??xt;if(this.#n=e.mergeFanIn??j,!Number.isSafeInteger(i)||i<X||!Number.isSafeInteger(this.#n)||this.#n<2||this.#n>j)throw new Error("checkpoint identity sort limits are invalid");this.#t=new _(i);}add(t){let e=Buffer.byteLength(t,"utf8");if(!this.#t.canFit(e))throw new Error("checkpoint durable identity exceeds the sort budget");if(this.#t.canAdd(e)){this.#t.add(t,e);return}return this.#o(t,e)}async finish(){if(this.#e.length===0){this.#t.sortAndAssertUnique(),this.#t.clear();return}for(this.#t.isEmpty||await this.#o();this.#e.length>this.#n;){let e=[];for(let i=0;i<this.#e.length;i+=this.#n){let n=this.#e.slice(i,i+this.#n),s=this.#s();await Ft(n,s),await Promise.all(n.map(o=>rm(o,{force:true}))),e.push(s);}this.#e=e;}let t;await tt(this.#e,e=>{if(t&&P(t,e)===0)throw new g;t=e;});}async#o(t,e){let i=this.#s();this.#t.sortAndAssertUnique(),await Mt(i,this.#t),this.#e.push(i),this.#t.clear(),t!==void 0&&e!==void 0&&this.#t.add(t,e);}#s(){let t=join(this.#i,`identity-run-${String(this.#r).padStart(8,"0")}.bin`);return this.#r+=1,t}},_=class{byteBudget;#i;#t;#e=0;#r=0;constructor(t){this.byteBudget=t;let e=Math.floor(t/X);this.#t=new Uint32Array(e),this.#i=Buffer.allocUnsafe(t-e*$);}get isEmpty(){return this.#r===0}canAdd(t){return this.canFit(t)&&this.#r<this.#t.length&&this.#e+u+t<=this.#i.length}canFit(t){return t<=4294967295&&this.#t.length>0&&u+t<=this.#i.length}add(t,e){if(!this.canAdd(e))throw new Error("checkpoint durable identity exceeds the sort budget");let i=this.#e;this.#t[this.#r]=i,this.#i.writeUInt32BE(e,i),this.#i.write(t,i+u,e,"utf8"),this.#e+=u+e,this.#r+=1;}sortAndAssertUnique(){let t=this.#t.subarray(0,this.#r);t.sort((e,i)=>this.#n(e,i));for(let e=1;e<t.length;e+=1){let i=t[e-1],n=t[e];if(i===void 0||n===void 0)throw new Error("checkpoint identity offset is missing");if(this.#n(i,n)===0)throw new g}}clear(){this.#e=0,this.#r=0;}forEachSorted(t){return Dt(this.#t.subarray(0,this.#r),e=>t(this.#s(e)))}#n(t,e){return P(this.#o(t),this.#o(e))}#o(t){let e=this.#i.readUInt32BE(t);return this.#i.subarray(t+u,t+u+e)}#s(t){let e=this.#i.readUInt32BE(t);return this.#i.subarray(t,t+u+e)}};async function Mt(r,t){await Q(r,async e=>{let i=new U(e);await t.forEachSorted(n=>i.write(n)),await i.finish();});}async function Ft(r,t){await Q(t,async e=>{let i=new U(e);await tt(r,n=>i.writeValue(n)),await i.finish();});}async function Q(r,t){await(await open(r,"wx",384)).close();let i=createWriteStream(r,{flags:"a",mode:384,highWaterMark:J});try{await t(i),i.end(),await once(i,"close");}catch(n){throw i.destroy(),n}}var U=class{#i;#t=Buffer.allocUnsafe(J);#e=0;constructor(t){this.#i=t;}write(t){if(t.byteLength>this.#t.length)return this.#r(t);if(this.#e+t.byteLength>this.#t.length)return this.#n(t);this.#t.set(t,this.#e),this.#e+=t.byteLength;}writeValue(t){let e=u+t.byteLength;if(e>this.#t.length)return this.#o(t);if(this.#e+e>this.#t.length)return this.#s(t);this.#c(t);}async finish(){await this.#a();}async#r(t){await this.#a(),await b(this.#i,t);}async#n(t){await this.#a(),this.#t.set(t,this.#e),this.#e+=t.byteLength;}async#o(t){await this.#a();let e=Buffer.allocUnsafe(u);e.writeUInt32BE(t.byteLength,0),await b(this.#i,e),await b(this.#i,t);}async#s(t){await this.#a(),this.#c(t);}async#a(){if(this.#e===0)return;let t=this.#t.subarray(0,this.#e);await b(this.#i,t),this.#e=0;}#c(t){this.#t.writeUInt32BE(t.byteLength,this.#e),this.#t.set(t,this.#e+u),this.#e+=u+t.byteLength;}};async function tt(r,t){let e=r.map(i=>new B(i));try{for(await Promise.all(e.map(i=>i.advance()));;){let i;for(let s of e)s.current&&(!i||P(s.current,i.current)<0)&&(i=s);if(!i?.current)return;let n=t(i.current);n&&await n,await i.advance();}}finally{for(let i of e)i.close();}}var B=class{#i;#t;#e=Buffer.alloc(0);#r=0;current;constructor(t){this.#i=createReadStream(t,{highWaterMark:Tt}),this.#t=this.#i[Symbol.asyncIterator]();}async advance(){let t=await this.#n(u);if(!t){this.current=void 0;return}let e=t.readUInt32BE(0);if(e===0)throw new Error("checkpoint identity run is invalid");let i=await this.#n(e);if(!i)throw new Error("checkpoint identity run is truncated");this.current=i;}close(){this.#i.destroy();}async#n(t){if(t===0)return Buffer.alloc(0);if(this.#e.length-this.#r>=t){let n=this.#e.subarray(this.#r,this.#r+t);return this.#r+=t,n}let e=Buffer.allocUnsafe(t),i=0;for(;i<t;){if(this.#r<this.#e.length){let s=Math.min(t-i,this.#e.length-this.#r);this.#e.copy(e,i,this.#r,this.#r+s),this.#r+=s,i+=s;continue}let n=await this.#t.next();if(n.done){if(i===0)return;throw new Error("checkpoint identity run is truncated")}this.#e=n.value,this.#r=0;}return e}};async function Dt(r,t){for(let e of r){let i=t(e);i&&await i;}}async function b(r,t){await new Promise((e,i)=>{r.write(t,n=>{n?i(n):e();});});}function P(r,t){let e=Math.min(r.length,t.length);for(let i=0;i<e;i+=1){let n=r[i],s=t[i];if(n===void 0||s===void 0)throw new Error("checkpoint identity byte is missing");let o=n-s;if(o!==0)return o}return r.length-t.length}var L=class{#i;#t=new Map;#e=new Map;#r=[];#n;constructor(t,e={}){this.#i=new k(t,{sortBudgetBytes:e.identitySortBudgetBytes,mergeFanIn:e.identityMergeFanIn});}openEntry(t){if(t===L$1){let e=[];return {write:async i=>{e.push(i.slice());},finish:async()=>{this.#n=Buffer.concat(e);}}}return new N(t,this.#t,this.#e,this.#i,e=>this.#r.push(e))}async finish(){if(!this.#n)throw new Error("checkpoint manifest is missing");let t=await N$1(this.#n),e=t.streams.flatMap(i=>i.chunks.map(n=>({...n,shapeName:i.shapeName})));if(e.length!==this.#r.length)throw new Error("checkpoint chunk inventory does not match");for(let[i,n]of e.entries()){let s=this.#r[i];if(s.path!==n.path||s.shapeName!==n.shapeName||s.recordCount!==n.recordCount||s.byteLength!==n.byteLength||s.sha256!==n.sha256)throw new Error("checkpoint chunk integrity does not match")}for(let i of t.streams){let n=this.#t.get(i.shapeName);if(!n||R$1(n)!==i.recordCount)throw new Error("checkpoint stream does not match its manifest")}if(this.#t.size!==t.streams.length)throw new Error("checkpoint contains an undeclared stream");return await this.#i.finish(),{manifest:t,chunkCount:e.length}}},N=class{#i;#t;#e;#r;#n;#o=createHash("sha256");#s=[];#a=0;#c;#h=0;#d=0;constructor(t,e,i,n,s){this.#i=t,this.#t=e,this.#e=i,this.#r=n,this.#n=s;}async write(t){if(this.#o.update(t),this.#d+=t.byteLength,!Number.isSafeInteger(this.#d))throw new Error("checkpoint chunk byte length is unsafe");let e=0;for(;e<t.byteLength;){let i=t.indexOf(10,e);if(i<0)break;let n=this.#u(this.#l(t.subarray(e,i+1)));n&&await n,e=i+1;}e<t.byteLength&&this.#f(t.subarray(e));}async finish(){if(this.#s.length!==0||this.#h===0||!this.#c)throw new Error("checkpoint chunk framing is invalid");if(this.#d>J$1&&this.#h!==1)throw new Error("oversized checkpoint chunk has multiple rows");this.#n({path:this.#i,shapeName:this.#c,recordCount:this.#h,byteLength:this.#d,sha256:this.#o.digest("hex")});let t=this.#e.get(this.#c);if(!t)throw new Error("checkpoint stream chunk state is missing");t.previous={byteLength:this.#d};}#u(t){let e=O(t),i=e.kind==="shape"?e.wref:e.shapeName;if(this.#c!==void 0&&this.#c!==i)throw new Error("checkpoint chunk mixes Shape streams");this.#c=i,this.#h===0&&this.#y(i,t.byteLength);let n=this.#t.get(i)??P$1(i);this.#t.set(i,n),Q$1(n,e);let s=this.#r.add(e.durableId);if(this.#h+=1,!Number.isSafeInteger(this.#h))throw new Error("checkpoint chunk row count is unsafe");return s}#f(t){if(t.byteLength!==0&&(this.#s.push(t.slice()),this.#a+=t.byteLength,!Number.isSafeInteger(this.#a)))throw new Error("checkpoint row byte length is unsafe")}#l(t){if(this.#s.length===0)return t;let e=Buffer.concat([...this.#s,t],this.#a+t.byteLength);return this.#s=[],this.#a=0,e}#y(t,e){let i=Number(this.#i.match(/part-(\d{10})\.jsonl$/)?.[1]??-1),n=this.#e.get(t)??{nextPart:0};if(i!==n.nextPart)throw new Error("checkpoint stream chunk parts are not contiguous");if(n.previous&&n.previous.byteLength<=J$1&&n.previous.byteLength+e<=J$1)throw new Error("checkpoint stream chunk closed before the byte limit");n.nextPart+=1,this.#e.set(t,n);}};async function Gt(r,t){return Kt(r,t)}async function Kt(r,t,e={}){let i=await mkdtemp(join(e.scratchParent??tmpdir(),"warmhub-checkpoint-verify-"));await chmod(i,448);let n,s;try{let o=new L(i,e),c=await G(r,p=>o.openEntry(p)),{manifest:h,chunkCount:f}=await o.finish();jt(t,c,h),n={valid:!0,checkpointId:h.checkpointId,repoSeq:h.repoSeq,recordCount:h.recordCount,chunkCount:f,totalBytes:c.byteLength,rootDigest:h.contentSha256,archiveDigest:c.sha256};}catch(o){s=Jt(o);}try{await rm(i,{recursive:!0,force:!0});}catch{s??=new d("scratch_cleanup_failed");}if(s)throw s;if(!n)throw new d("archive_invalid");return n}function jt(r,t,e){if(r&&(M.parse(r.sha256),r.checkpointId!==e.checkpointId||r.repoSeq!==e.repoSeq||r.byteLength!==t.byteLength||r.sha256!==t.sha256))throw new d("archive_integrity_mismatch")}function Jt(r){if(r instanceof d)return r;if(r instanceof E)return new d("source_failed");if(r instanceof g)return new d("duplicate_durable_id");let t=r instanceof Error?r.message:"";return t.includes("manifest")?new d("manifest_invalid"):t.includes("chunk")||t.includes("row")||t.includes("Shape stream")?new d("chunk_invalid"):new d("archive_invalid")}export{d as RepositoryCheckpointVerificationError,Gt as verifyRepositoryCheckpointArchive};//# sourceMappingURL=checkpoint.js.map
2
2
  //# sourceMappingURL=checkpoint.js.map