@hraness/sponge 0.2.6 → 0.3.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.
Files changed (227) hide show
  1. package/README.md +21 -9
  2. package/bin/sponge.js +8 -7
  3. package/dist/contracts/index.js +24 -6
  4. package/dist/contracts/validate.js +106 -29
  5. package/dist/migrations/008.d.ts +3 -0
  6. package/dist/migrations/008.js +5 -0
  7. package/dist/migrations/009.d.ts +3 -0
  8. package/dist/migrations/009.js +8 -0
  9. package/dist/migrations/010.d.ts +3 -0
  10. package/dist/migrations/010.js +11 -0
  11. package/dist/migrations/index.d.ts +15 -0
  12. package/dist/migrations/index.js +39 -0
  13. package/dist/src/acquisition/capture.js +87 -28
  14. package/dist/src/acquisition/cleanup.d.ts +0 -4
  15. package/dist/src/acquisition/cleanup.js +3 -5
  16. package/dist/src/acquisition/errors.js +17 -2
  17. package/dist/src/acquisition/files.d.ts +0 -1
  18. package/dist/src/acquisition/files.js +14 -11
  19. package/dist/src/acquisition/index.d.ts +2 -3
  20. package/dist/src/acquisition/index.js +1 -2
  21. package/dist/src/acquisition/lock.js +1 -1
  22. package/dist/src/acquisition/normalize.js +57 -18
  23. package/dist/src/acquisition/pins.js +22 -4
  24. package/dist/src/acquisition/process.js +67 -29
  25. package/dist/src/acquisition/runtime.d.ts +5 -7
  26. package/dist/src/acquisition/runtime.js +129 -68
  27. package/dist/src/acquisition/verify-helper.js +10 -3
  28. package/dist/src/annotations/quote.js +40 -12
  29. package/dist/src/annotations/store.d.ts +0 -1
  30. package/dist/src/annotations/store.js +66 -14
  31. package/dist/src/app/article.d.ts +2 -1
  32. package/dist/src/app/article.js +66 -70
  33. package/dist/src/app/bundle-files.d.ts +0 -4
  34. package/dist/src/app/bundle-files.js +11 -14
  35. package/dist/src/app/markdown.js +18 -6
  36. package/dist/src/app/source.d.ts +0 -4
  37. package/dist/src/app/source.js +18 -14
  38. package/dist/src/cli/arguments.d.ts +2 -8
  39. package/dist/src/cli/arguments.js +67 -80
  40. package/dist/src/cli/commands.d.ts +42 -0
  41. package/dist/src/cli/commands.js +795 -0
  42. package/dist/src/cli/config.js +12 -9
  43. package/dist/src/cli/digest.d.ts +1 -1
  44. package/dist/src/cli/digest.js +38 -16
  45. package/dist/src/cli/import.js +25 -4
  46. package/dist/src/cli/indexing.d.ts +2 -0
  47. package/dist/src/cli/indexing.js +26 -9
  48. package/dist/src/cli/main.js +47 -386
  49. package/dist/src/cli/portability.d.ts +0 -2
  50. package/dist/src/cli/portability.js +13 -8
  51. package/dist/src/cli/queue.js +12 -8
  52. package/dist/src/cli/runtimes.d.ts +1 -3
  53. package/dist/src/cli/runtimes.js +13 -6
  54. package/dist/src/cli/terminal.js +1 -1
  55. package/dist/src/cli/worker.d.ts +7 -0
  56. package/dist/src/cli/worker.js +54 -15
  57. package/dist/src/derivations/kinds.d.ts +34 -0
  58. package/dist/src/derivations/kinds.js +14 -0
  59. package/dist/src/derivations/types.d.ts +48 -0
  60. package/dist/src/derivations/types.js +1 -0
  61. package/dist/src/digest/derivation.d.ts +25 -0
  62. package/dist/src/digest/derivation.js +124 -0
  63. package/dist/src/digest/editorial.d.ts +9 -46
  64. package/dist/src/digest/editorial.js +57 -49
  65. package/dist/src/digest/endpoint.js +63 -27
  66. package/dist/src/digest/pipeline.d.ts +49 -28
  67. package/dist/src/digest/pipeline.js +343 -100
  68. package/dist/src/digest/schema.d.ts +11 -25
  69. package/dist/src/digest/schema.js +86 -18
  70. package/dist/src/errors.d.ts +46 -0
  71. package/dist/src/errors.js +117 -0
  72. package/dist/src/identity/query-policy.d.ts +0 -5
  73. package/dist/src/identity/query-policy.js +61 -9
  74. package/dist/src/identity/uri.d.ts +6 -5
  75. package/dist/src/identity/uri.js +37 -18
  76. package/dist/src/identity/visibility.d.ts +11 -1
  77. package/dist/src/identity/visibility.js +81 -17
  78. package/dist/src/importers/apply.js +104 -36
  79. package/dist/src/importers/custody.js +27 -9
  80. package/dist/src/importers/inspect.js +125 -38
  81. package/dist/src/importers/jungle-digest.d.ts +3 -6
  82. package/dist/src/importers/jungle-digest.js +196 -69
  83. package/dist/src/importers/jungle-store.d.ts +15 -6
  84. package/dist/src/importers/jungle-store.js +122 -28
  85. package/dist/src/importers/jungle.d.ts +14 -14
  86. package/dist/src/importers/jungle.js +136 -31
  87. package/dist/src/importers/manifest.js +57 -21
  88. package/dist/src/importers/verify-helper.js +18 -4
  89. package/dist/src/indexing/image.js +29 -10
  90. package/dist/src/indexing/index.d.ts +37 -6
  91. package/dist/src/indexing/index.js +360 -124
  92. package/dist/src/indexing/jobs.js +156 -47
  93. package/dist/src/indexing/prune-run.d.ts +2 -0
  94. package/dist/src/indexing/prune-run.js +69 -26
  95. package/dist/src/indexing/prune.d.ts +12 -3
  96. package/dist/src/indexing/prune.js +85 -35
  97. package/dist/src/indexing/runner.d.ts +2 -1
  98. package/dist/src/indexing/runner.js +83 -40
  99. package/dist/src/indexing/upgrade.d.ts +9 -0
  100. package/dist/src/indexing/upgrade.js +79 -0
  101. package/dist/src/inference/asr/bridge.js +6 -2
  102. package/dist/src/inference/asr/protocol.js +109 -19
  103. package/dist/src/inference/asr/runtime.js +58 -21
  104. package/dist/src/inference/asr/windows.js +9 -5
  105. package/dist/src/inference/embedding/index.d.ts +2 -6
  106. package/dist/src/inference/embedding/index.js +89 -29
  107. package/dist/src/ingest/attempt.d.ts +29 -0
  108. package/dist/src/ingest/attempt.js +55 -0
  109. package/dist/src/media/capture-helper.js +7 -2
  110. package/dist/src/media/capture.d.ts +2 -1
  111. package/dist/src/media/capture.js +124 -43
  112. package/dist/src/media/handler.d.ts +4 -2
  113. package/dist/src/media/handler.js +88 -70
  114. package/dist/src/media/transcribe.js +63 -21
  115. package/dist/src/metadata/article.js +49 -17
  116. package/dist/src/models/artifacts.d.ts +10 -8
  117. package/dist/src/models/artifacts.js +237 -106
  118. package/dist/src/models/catalog.d.ts +2 -5
  119. package/dist/src/models/catalog.js +94 -25
  120. package/dist/src/models/download.js +55 -15
  121. package/dist/src/models/runtime.d.ts +0 -2
  122. package/dist/src/models/runtime.js +69 -33
  123. package/dist/src/pdf/capture-helper.js +3 -1
  124. package/dist/src/pdf/capture.d.ts +2 -1
  125. package/dist/src/pdf/capture.js +95 -27
  126. package/dist/src/pdf/errors.js +12 -1
  127. package/dist/src/pdf/handler.d.ts +2 -1
  128. package/dist/src/pdf/handler.js +70 -71
  129. package/dist/src/pdf/index.d.ts +1 -3
  130. package/dist/src/pdf/index.js +0 -2
  131. package/dist/src/pdf/input.js +12 -7
  132. package/dist/src/pdf/normalize.js +80 -21
  133. package/dist/src/pdf/source-identity.js +3 -2
  134. package/dist/src/pdf/source.js +12 -8
  135. package/dist/src/pdf/verify-helper.js +15 -3
  136. package/dist/src/portability/backup.d.ts +2 -9
  137. package/dist/src/portability/backup.js +106 -22
  138. package/dist/src/portability/database.js +51 -17
  139. package/dist/src/portability/export.js +35 -9
  140. package/dist/src/portability/files.d.ts +1 -4
  141. package/dist/src/portability/files.js +127 -67
  142. package/dist/src/portability/gc.js +65 -22
  143. package/dist/src/publish/component.js +1 -1
  144. package/dist/src/publish/feed.d.ts +2 -2
  145. package/dist/src/publish/feed.js +75 -29
  146. package/dist/src/publish/identity.d.ts +5 -1
  147. package/dist/src/publish/identity.js +31 -11
  148. package/dist/src/publish/index.d.ts +2 -3
  149. package/dist/src/publish/index.js +34 -21
  150. package/dist/src/publish/projection.d.ts +2 -9
  151. package/dist/src/publish/projection.js +21 -14
  152. package/dist/src/search/extension.js +3 -1
  153. package/dist/src/search/hybrid.d.ts +3 -8
  154. package/dist/src/search/hybrid.js +45 -11
  155. package/dist/src/search/lexical.js +18 -4
  156. package/dist/src/search/query.js +56 -25
  157. package/dist/src/search/vector.js +37 -10
  158. package/dist/src/service/launch-agent.d.ts +5 -3
  159. package/dist/src/service/launch-agent.js +272 -83
  160. package/dist/src/service/lifetime.js +47 -14
  161. package/dist/src/service/loop.d.ts +3 -3
  162. package/dist/src/service/loop.js +47 -19
  163. package/dist/src/store/annotations.d.ts +32 -0
  164. package/dist/src/store/annotations.js +44 -0
  165. package/dist/src/store/backups.d.ts +18 -0
  166. package/dist/src/store/backups.js +110 -0
  167. package/dist/src/store/blobs.d.ts +24 -0
  168. package/dist/src/store/blobs.js +139 -0
  169. package/dist/src/store/changes.d.ts +5 -12
  170. package/dist/src/store/changes.js +108 -56
  171. package/dist/src/store/context.d.ts +15 -0
  172. package/dist/src/store/context.js +19 -0
  173. package/dist/src/store/derivations.d.ts +42 -0
  174. package/dist/src/store/derivations.js +104 -0
  175. package/dist/src/store/files.d.ts +0 -2
  176. package/dist/src/store/files.js +19 -24
  177. package/dist/src/store/ids.d.ts +30 -0
  178. package/dist/src/store/ids.js +34 -0
  179. package/dist/src/store/imports.d.ts +26 -0
  180. package/dist/src/store/imports.js +57 -0
  181. package/dist/src/store/index.d.ts +3 -186
  182. package/dist/src/store/index.js +2 -809
  183. package/dist/src/store/lease.d.ts +20 -0
  184. package/dist/src/store/lease.js +62 -0
  185. package/dist/src/store/library.d.ts +75 -0
  186. package/dist/src/store/library.js +174 -0
  187. package/dist/src/store/open.d.ts +7 -0
  188. package/dist/src/store/open.js +183 -0
  189. package/dist/src/store/queue.d.ts +65 -0
  190. package/dist/src/store/queue.js +327 -0
  191. package/dist/src/store/revisions.d.ts +12 -0
  192. package/dist/src/store/revisions.js +189 -0
  193. package/dist/src/store/search-index.d.ts +63 -0
  194. package/dist/src/store/search-index.js +190 -0
  195. package/dist/src/store/sync-ready.d.ts +6 -0
  196. package/dist/src/store/sync-ready.js +121 -0
  197. package/dist/src/store/types.d.ts +1 -5
  198. package/dist/src/store/types.js +1 -8
  199. package/dist/src/util/canonical.d.ts +2 -0
  200. package/dist/src/util/canonical.js +17 -0
  201. package/dist/src/util/exact.d.ts +2 -0
  202. package/dist/src/util/exact.js +9 -0
  203. package/dist/src/util/fsync.d.ts +4 -0
  204. package/dist/src/util/fsync.js +22 -0
  205. package/dist/src/util/hash.d.ts +4 -0
  206. package/dist/src/util/hash.js +8 -0
  207. package/dist/src/worker/basic.d.ts +0 -1
  208. package/dist/src/worker/basic.js +23 -7
  209. package/dist/src/worker/custody.d.ts +4 -1
  210. package/dist/src/worker/custody.js +111 -38
  211. package/dist/src/worker/engine.d.ts +2 -1
  212. package/dist/src/worker/engine.js +42 -20
  213. package/dist/src/worker/identity.js +13 -5
  214. package/dist/src/worker/journal.d.ts +1 -1
  215. package/dist/src/worker/journal.js +54 -24
  216. package/dist/src/worker/lock.d.ts +18 -0
  217. package/dist/src/worker/lock.js +196 -0
  218. package/dist/src/worker/runtime-gc.d.ts +4 -3
  219. package/dist/src/worker/runtime-gc.js +68 -41
  220. package/dist/src/worker/supervisor-child.js +19 -6
  221. package/dist/src/worker/supervisor.js +41 -8
  222. package/native/runtime-manifest.json +4 -4
  223. package/package.json +10 -2
  224. package/dist/src/digest/store.d.ts +0 -57
  225. package/dist/src/digest/store.js +0 -79
  226. package/dist/src/store/foundation.d.ts +0 -7
  227. package/dist/src/store/foundation.js +0 -7
@@ -1,15 +1,20 @@
1
1
  import { articleMetadata, articleMetadataWarnings } from '../metadata/article.js';
2
2
  import { limits } from '../../contracts/index.js';
3
3
  import { validateCapture, validateAsset } from '../../contracts/validate.js';
4
- import { readConfined, hash } from './files.js';
5
- export function object(value) { if (!value || typeof value !== 'object' || Array.isArray(value))
6
- throw Error('integrity'); return value; }
4
+ import { readConfined } from './files.js';
5
+ import { sha256 as hash } from '../util/hash.js';
6
+ import { SpongeError } from '../errors.js';
7
+ export function object(value) {
8
+ if (!value || typeof value !== 'object' || Array.isArray(value))
9
+ throw new SpongeError('integrity');
10
+ return value;
11
+ }
7
12
  export function metadataFromMarkdown(markdown, observedAt) {
8
13
  if (!markdown.startsWith('---\n'))
9
14
  return [];
10
15
  const end = markdown.indexOf('\n---', 4);
11
16
  if (end < 0 || Buffer.byteLength(markdown.slice(0, end)) > limits.metadataBytes)
12
- throw Error('limit-exceeded');
17
+ throw new SpongeError('limit-exceeded');
13
18
  const claims = [];
14
19
  for (const line of markdown.slice(4, end).split('\n')) {
15
20
  const match = /^(title|author|description|published|canonical_url|language): (".*")$/.exec(line);
@@ -24,39 +29,73 @@ export function metadataFromMarkdown(markdown, observedAt) {
24
29
  }
25
30
  export async function normalizeBundle(input) {
26
31
  const verified = object(input.inspection), inspection = object(verified.inspection), document = object(inspection.document), summary = object(input.summary);
27
- if (verified.ok !== true || inspection.schemaVersion !== 4 || document.integrity !== 'verified' || !Array.isArray(inspection.assets) || inspection.assets.some(a => object(a).integrity !== 'verified'))
28
- throw Error('integrity');
32
+ if (verified.ok !== true ||
33
+ inspection.schemaVersion !== 4 ||
34
+ document.integrity !== 'verified' ||
35
+ !Array.isArray(inspection.assets) ||
36
+ inspection.assets.some((a) => object(a).integrity !== 'verified'))
37
+ throw new SpongeError('integrity');
29
38
  const manifestBytes = await readConfined(input.bundleRoot, 'capture.json', limits.metadataBytes), manifest = object(JSON.parse(manifestBytes.toString()));
30
- if (manifest.schemaVersion !== 4 || manifest.sourceUrl !== input.sourceUrl || inspection.sourceUrl !== input.sourceUrl)
31
- throw Error('integrity');
39
+ if (manifest.schemaVersion !== 4 ||
40
+ manifest.sourceUrl !== input.sourceUrl ||
41
+ inspection.sourceUrl !== input.sourceUrl)
42
+ throw new SpongeError('integrity');
32
43
  const documentRecord = object(manifest.document);
33
- const markdown = { relativePath: String(documentRecord.path), sha256: String(documentRecord.sha256), bytes: Number(documentRecord.bytes), mediaType: 'text/markdown' };
44
+ const markdown = {
45
+ relativePath: String(documentRecord.path),
46
+ sha256: String(documentRecord.sha256),
47
+ bytes: Number(documentRecord.bytes),
48
+ mediaType: 'text/markdown',
49
+ };
34
50
  validateAsset(markdown);
35
51
  const bytes = await readConfined(input.bundleRoot, markdown.relativePath, limits.markdownBytes);
36
52
  if (bytes.length !== markdown.bytes || hash(bytes) !== markdown.sha256 || document.sha256 !== markdown.sha256)
37
- throw Error('integrity');
38
- if (!Array.isArray(manifest.assets) || manifest.assets.length > limits.imagesPerItem || manifest.assets.length !== inspection.assets.length)
39
- throw Error('limit-exceeded');
53
+ throw new SpongeError('integrity');
54
+ if (!Array.isArray(manifest.assets) ||
55
+ manifest.assets.length > limits.imagesPerItem ||
56
+ manifest.assets.length !== inspection.assets.length)
57
+ throw new SpongeError('limit-exceeded');
40
58
  const assets = [];
41
59
  for (const raw of manifest.assets) {
42
60
  const item = object(raw);
43
- const asset = { relativePath: String(item.path), sha256: String(item.sha256), bytes: Number(item.bytes), mediaType: String(item.mimeType) };
61
+ const asset = {
62
+ relativePath: String(item.path),
63
+ sha256: String(item.sha256),
64
+ bytes: Number(item.bytes),
65
+ mediaType: String(item.mimeType),
66
+ };
44
67
  validateAsset(asset);
45
68
  if (!asset.mediaType.startsWith('image/') || asset.bytes > limits.imageBytes)
46
- throw Error('limit-exceeded');
47
- const checked = inspection.assets.find(a => object(a).path === asset.relativePath);
69
+ throw new SpongeError('limit-exceeded');
70
+ const checked = inspection.assets.find((a) => object(a).path === asset.relativePath);
48
71
  if (!checked || object(checked).expectedSha256 !== asset.sha256 || object(checked).expectedBytes !== asset.bytes)
49
- throw Error('integrity');
72
+ throw new SpongeError('integrity');
50
73
  assets.push(asset);
51
74
  }
52
75
  const images = object(object(manifest.artifacts).images);
53
76
  if (!['captured', 'partial', 'not-requested'].includes(String(images.status)))
54
- throw Error('integrity');
77
+ throw new SpongeError('integrity');
55
78
  const text = manifest.status === 'complete' && summary.status === 'complete' ? 'complete' : 'partial';
56
79
  const imageStatus = images.status === 'partial' ? 'partial' : images.status === 'not-requested' ? 'not-requested' : 'complete';
57
80
  const metadataWarnings = manifest.metadata === undefined ? [] : articleMetadataWarnings(manifest.metadata);
58
81
  const partial = text === 'partial' || imageStatus === 'partial' || metadataWarnings.length > 0;
59
- const capture = { schemaVersion: 1, requestId: input.requestId, kind: 'article', status: partial ? 'partial' : 'complete', sourceUrl: input.sourceUrl, markdown, assets, metadataWarnings, metadata: manifest.metadata === undefined ? metadataFromMarkdown(bytes.toString('utf8'), String(manifest.capturedAt)) : articleMetadata(manifest.metadata, String(manifest.capturedAt)), warnings: partial ? ['partial'] : [], extractor: { name: 'wordcell', version: input.wordcellVersion }, manifestSha256: hash(manifestBytes), completeness: { text, images: imageStatus } };
82
+ const capture = {
83
+ schemaVersion: 1,
84
+ requestId: input.requestId,
85
+ kind: 'article',
86
+ status: partial ? 'partial' : 'complete',
87
+ sourceUrl: input.sourceUrl,
88
+ markdown,
89
+ assets,
90
+ metadataWarnings,
91
+ metadata: manifest.metadata === undefined
92
+ ? metadataFromMarkdown(bytes.toString('utf8'), String(manifest.capturedAt))
93
+ : articleMetadata(manifest.metadata, String(manifest.capturedAt)),
94
+ warnings: partial ? ['partial'] : [],
95
+ extractor: { name: 'wordcell', version: input.wordcellVersion },
96
+ manifestSha256: hash(manifestBytes),
97
+ completeness: { text, images: imageStatus },
98
+ };
60
99
  validateCapture(capture);
61
100
  return capture;
62
101
  }
@@ -2,8 +2,26 @@ import { captureLockfile } from './lock.js';
2
2
  /** Immutable released media/PDF capability pins; installed qualification is recorded separately. */
3
3
  export const defaultCapturePins = {
4
4
  id: 'capture-ghostget-0.18.102-bun-1.3.14-darwin-arm64',
5
- ghostget: { version: '0.18.102', url: 'https://github.com/hraness/ghostget/releases/download/v0.18.102/hraness-ghostget-0.18.102.tgz', bytes: 12493537, sha256: '68edcad50b72f77b2ed73e9affba3b49e6b683622cdec9e2fabdffa90e3a0bdb' },
6
- wordcell: { version: '0.27.0', url: 'https://github.com/hraness/wordcell/releases/download/v0.27.0/hraness-wordcell-0.27.0.tgz', bytes: 1465897, sha256: 'df47868147cda46d741895d3cd1ddcbec14a563b341aa09052545be7696c9262' },
7
- bun: { version: '1.3.14', url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.3.14/bun-darwin-aarch64.zip', bytes: 23586433, sha256: 'd8b96221828ad6f97ac7ac0ab7e95872341af763001e8803e8267652c2652620', executableSha256: 'e0c90ec15d33363e6b70713d56bc3b2c7585c17f40a0fe0f8fd9305901d4e233', archiveExecutable: 'bun-darwin-aarch64/bun' },
8
- lockfile: captureLockfile, lockSha256: "456370c26a7286bab70307595745f4be382a684a11223e848dbb0032761d59d0",
5
+ ghostget: {
6
+ version: '0.18.102',
7
+ url: 'https://github.com/hraness/ghostget/releases/download/v0.18.102/hraness-ghostget-0.18.102.tgz',
8
+ bytes: 12493537,
9
+ sha256: '68edcad50b72f77b2ed73e9affba3b49e6b683622cdec9e2fabdffa90e3a0bdb',
10
+ },
11
+ wordcell: {
12
+ version: '0.27.0',
13
+ url: 'https://github.com/hraness/wordcell/releases/download/v0.27.0/hraness-wordcell-0.27.0.tgz',
14
+ bytes: 1465897,
15
+ sha256: 'df47868147cda46d741895d3cd1ddcbec14a563b341aa09052545be7696c9262',
16
+ },
17
+ bun: {
18
+ version: '1.3.14',
19
+ url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.3.14/bun-darwin-aarch64.zip',
20
+ bytes: 23586433,
21
+ sha256: 'd8b96221828ad6f97ac7ac0ab7e95872341af763001e8803e8267652c2652620',
22
+ executableSha256: 'e0c90ec15d33363e6b70713d56bc3b2c7585c17f40a0fe0f8fd9305901d4e233',
23
+ archiveExecutable: 'bun-darwin-aarch64/bun',
24
+ },
25
+ lockfile: captureLockfile,
26
+ lockSha256: '456370c26a7286bab70307595745f4be382a684a11223e848dbb0032761d59d0',
9
27
  };
@@ -3,34 +3,56 @@ import { spawn } from 'node:child_process';
3
3
  export async function runOwned(executable, args, options) {
4
4
  if (options.signal?.aborted)
5
5
  return { code: null, stdout: '', stopped: 'cancelled', cleanup: 'settled' };
6
- return new Promise(resolve => {
7
- const child = spawn(executable, [...args], { cwd: options.cwd, env: options.env, stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32' });
6
+ return new Promise((resolve) => {
7
+ const child = spawn(executable, [...args], {
8
+ cwd: options.cwd,
9
+ env: options.env,
10
+ stdio: ['ignore', 'pipe', 'pipe'],
11
+ detached: process.platform !== 'win32',
12
+ });
8
13
  const stdout = [];
9
14
  let bytes = 0, stopped, escalation, finished = false;
10
- const exists = () => { if (!child.pid)
11
- return false; try {
12
- process.kill(process.platform === 'win32' ? child.pid : -child.pid, 0);
13
- return true;
14
- }
15
- catch (e) {
16
- return e.code !== 'ESRCH';
17
- } };
18
- const send = (signal) => { if (!child.pid)
19
- return; try {
20
- process.kill(process.platform === 'win32' ? child.pid : -child.pid, signal);
21
- }
22
- catch { } };
23
- const stop = (reason) => { stopped ??= reason; if (escalation)
24
- return; send('SIGTERM'); escalation = setTimeout(() => send('SIGKILL'), options.graceMs ?? 5000); };
15
+ const exists = () => {
16
+ if (!child.pid)
17
+ return false;
18
+ try {
19
+ process.kill(process.platform === 'win32' ? child.pid : -child.pid, 0);
20
+ return true;
21
+ }
22
+ catch (e) {
23
+ return e.code !== 'ESRCH';
24
+ }
25
+ };
26
+ const send = (signal) => {
27
+ if (!child.pid)
28
+ return;
29
+ try {
30
+ process.kill(process.platform === 'win32' ? child.pid : -child.pid, signal);
31
+ }
32
+ catch { }
33
+ };
34
+ const stop = (reason) => {
35
+ stopped ??= reason;
36
+ if (escalation)
37
+ return;
38
+ send('SIGTERM');
39
+ escalation = setTimeout(() => send('SIGKILL'), options.graceMs ?? 5000);
40
+ };
25
41
  const abort = () => stop('cancelled');
26
42
  options.signal?.addEventListener('abort', abort, { once: true });
27
43
  const timer = setTimeout(() => stop('unavailable'), options.deadlineMs);
28
- child.stdout.on('data', (data) => { bytes += data.length; if (bytes > options.maxOutputBytes)
29
- stop('limit-exceeded');
30
- else
31
- stdout.push(data); });
32
- child.stderr.on('data', (data) => { bytes += data.length; if (bytes > options.maxOutputBytes)
33
- stop('limit-exceeded'); });
44
+ child.stdout.on('data', (data) => {
45
+ bytes += data.length;
46
+ if (bytes > options.maxOutputBytes)
47
+ stop('limit-exceeded');
48
+ else
49
+ stdout.push(data);
50
+ });
51
+ child.stderr.on('data', (data) => {
52
+ bytes += data.length;
53
+ if (bytes > options.maxOutputBytes)
54
+ stop('limit-exceeded');
55
+ });
34
56
  const finish = async (code) => {
35
57
  if (finished)
36
58
  return;
@@ -43,21 +65,37 @@ export async function runOwned(executable, args, options) {
43
65
  send('SIGTERM');
44
66
  const end = Date.now() + 500;
45
67
  while (exists() && Date.now() < end)
46
- await new Promise(r => setTimeout(r, 10));
68
+ await new Promise((r) => setTimeout(r, 10));
47
69
  if (exists())
48
70
  send('SIGKILL');
49
71
  }
50
72
  const end = Date.now() + 1500;
51
73
  while (exists() && Date.now() < end)
52
- await new Promise(r => setTimeout(r, 10));
53
- resolve({ code, stdout: Buffer.concat(stdout).toString('utf8'), stopped, cleanup: exists() ? 'pending' : 'settled' });
74
+ await new Promise((r) => setTimeout(r, 10));
75
+ resolve({
76
+ code,
77
+ stdout: Buffer.concat(stdout).toString('utf8'),
78
+ stopped,
79
+ cleanup: exists() ? 'pending' : 'settled',
80
+ });
54
81
  };
55
- child.once('error', () => { stopped ??= 'unavailable'; void finish(null); });
56
- child.once('close', code => void finish(code));
82
+ child.once('error', () => {
83
+ stopped ??= 'unavailable';
84
+ void finish(null);
85
+ });
86
+ child.once('close', (code) => void finish(code));
57
87
  if (options.signal?.aborted)
58
88
  abort();
59
89
  });
60
90
  }
61
91
  export function isolatedEnvironment(home, tmp, stateHome) {
62
- return { HRANESS_NO_UPDATE: '1', HOME: home, TMPDIR: tmp, GHOSTGET_STATE_HOME: stateHome, PATH: '/usr/bin:/bin:/usr/sbin:/sbin', LANG: 'en_US.UTF-8', NO_COLOR: '1' };
92
+ return {
93
+ HRANESS_NO_UPDATE: '1',
94
+ HOME: home,
95
+ TMPDIR: tmp,
96
+ GHOSTGET_STATE_HOME: stateHome,
97
+ PATH: '/usr/bin:/bin:/usr/sbin:/sbin',
98
+ LANG: 'en_US.UTF-8',
99
+ NO_COLOR: '1',
100
+ };
63
101
  }
@@ -1,5 +1,4 @@
1
- import type { ErrorCode } from '../../contracts/index.js';
2
- export interface DownloadPin {
1
+ interface DownloadPin {
3
2
  url: string;
4
3
  sha256: string;
5
4
  bytes: number;
@@ -37,12 +36,11 @@ export interface SetupPlan {
37
36
  requiredFreeBytes: number;
38
37
  capabilities: string[];
39
38
  }
40
- export declare class CaptureSetupError extends Error {
41
- readonly code: ErrorCode;
42
- readonly recoveryRoot?: string | undefined;
43
- constructor(code: ErrorCode, recoveryRoot?: string | undefined);
44
- }
45
39
  export declare function captureSetupPlan(pins: CapturePins): SetupPlan;
40
+ /** Full inventory walks performed by this process (observability for tests). */
41
+ export declare const runtimeVerification: {
42
+ fullWalks: number;
43
+ };
46
44
  declare function provisionRuntime(options: {
47
45
  runtimeRoot: string;
48
46
  pins: CapturePins;
@@ -1,16 +1,9 @@
1
- import { mkdir, mkdtemp, writeFile, rename, rm, chmod, statfs, lstat, readdir, readlink, realpath } from 'node:fs/promises';
1
+ import { mkdir, mkdtemp, writeFile, rename, rm, chmod, statfs, lstat, readdir, readlink, realpath, } from 'node:fs/promises';
2
2
  import { join, relative } from 'node:path';
3
3
  import { runOwned, isolatedEnvironment } from './process.js';
4
- import { hash, privateRoot, readConfined, physicalBytes } from './files.js';
5
- export class CaptureSetupError extends Error {
6
- code;
7
- recoveryRoot;
8
- constructor(code, recoveryRoot) {
9
- super(code);
10
- this.code = code;
11
- this.recoveryRoot = recoveryRoot;
12
- }
13
- }
4
+ import { privateRoot, readConfined, physicalBytes } from './files.js';
5
+ import { sha256 as hash } from '../util/hash.js';
6
+ import { SpongeError } from '../errors.js';
14
7
  // Retained inventory excludes the isolated Bun cache and temporary Bun zip.
15
8
  const budget = 1024 ** 3;
16
9
  const stagingBudget = 2 * budget;
@@ -18,35 +11,53 @@ const stagingBudget = 2 * budget;
18
11
  const installDeadlineMs = 30 * 60 * 1000;
19
12
  function checkPins(pins) {
20
13
  if (!/^[a-z0-9][a-z0-9.-]{0,99}$/.test(pins.id) || hash(pins.lockfile) !== pins.lockSha256)
21
- throw new CaptureSetupError('invalid-input');
14
+ throw new SpongeError('invalid-input');
22
15
  for (const pin of [pins.bun, pins.ghostget, pins.wordcell]) {
23
16
  let url;
24
17
  try {
25
18
  url = new URL(pin.url);
26
19
  }
27
20
  catch {
28
- throw new CaptureSetupError('invalid-input');
21
+ throw new SpongeError('invalid-input');
29
22
  }
30
- if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?$/.test(pin.version) || !/^[a-f0-9]{64}$/.test(pin.sha256) || !Number.isSafeInteger(pin.bytes) || pin.bytes < 1 || pin.bytes > budget || url.protocol !== 'https:' || url.username || url.password)
31
- throw new CaptureSetupError('invalid-input');
23
+ if (!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.-]+)?$/.test(pin.version) ||
24
+ !/^[a-f0-9]{64}$/.test(pin.sha256) ||
25
+ !Number.isSafeInteger(pin.bytes) ||
26
+ pin.bytes < 1 ||
27
+ pin.bytes > budget ||
28
+ url.protocol !== 'https:' ||
29
+ url.username ||
30
+ url.password)
31
+ throw new SpongeError('invalid-input');
32
32
  }
33
- if (!/^[a-z0-9][a-z0-9.-]*\/bun$/.test(pins.bun.archiveExecutable) || !/^[a-f0-9]{64}$/.test(pins.bun.executableSha256))
34
- throw new CaptureSetupError('invalid-input');
33
+ if (!/^[a-z0-9][a-z0-9.-]*\/bun$/.test(pins.bun.archiveExecutable) ||
34
+ !/^[a-f0-9]{64}$/.test(pins.bun.executableSha256))
35
+ throw new SpongeError('invalid-input');
35
36
  }
36
37
  export function captureSetupPlan(pins) {
37
38
  checkPins(pins);
38
39
  const artifacts = [pins.bun, pins.ghostget, pins.wordcell], downloadBytes = artifacts.reduce((n, a) => n + a.bytes, 0);
39
- return { artifacts, downloadBytes, installationBudgetBytes: budget, stagingBudgetBytes: stagingBudget, installationBudgetKind: 'monitored', installationDeadlineMs: installDeadlineMs, dependencyDownloads: 'frozen-lock', requiredFreeBytes: 2 * 1024 ** 3 + budget + stagingBudget + downloadBytes, capabilities: ['article-http', 'selected-ghostget-account', 'selected-attached-browser', 'verified-offline-images'] };
40
+ return {
41
+ artifacts,
42
+ downloadBytes,
43
+ installationBudgetBytes: budget,
44
+ stagingBudgetBytes: stagingBudget,
45
+ installationBudgetKind: 'monitored',
46
+ installationDeadlineMs: installDeadlineMs,
47
+ dependencyDownloads: 'frozen-lock',
48
+ requiredFreeBytes: 2 * 1024 ** 3 + budget + stagingBudget + downloadBytes,
49
+ capabilities: ['article-http', 'selected-ghostget-account', 'selected-attached-browser', 'verified-offline-images'],
50
+ };
40
51
  }
41
52
  async function download(pin, path, signal) {
42
53
  const boundedSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(120000)]) : AbortSignal.timeout(120000);
43
54
  const response = await fetch(pin.url, { signal: boundedSignal });
44
55
  if (!response.ok || !response.body || new URL(response.url).protocol !== 'https:')
45
- throw new CaptureSetupError('unavailable');
56
+ throw new SpongeError('unavailable');
46
57
  const declared = response.headers.get('content-length');
47
58
  if (declared !== null && Number(declared) !== pin.bytes) {
48
59
  await response.body.cancel();
49
- throw new CaptureSetupError('integrity');
60
+ throw new SpongeError('integrity');
50
61
  }
51
62
  // Primary release artifacts are small, fixed byte records. Never write an unchecked chunk.
52
63
  const chunks = [];
@@ -54,16 +65,17 @@ async function download(pin, path, signal) {
54
65
  for await (const chunk of response.body) {
55
66
  bytes += chunk.length;
56
67
  if (bytes > pin.bytes)
57
- throw new CaptureSetupError('integrity');
68
+ throw new SpongeError('integrity');
58
69
  chunks.push(chunk);
59
70
  }
60
71
  const data = Buffer.concat(chunks);
61
72
  if (bytes !== pin.bytes || hash(data) !== pin.sha256)
62
- throw new CaptureSetupError('integrity');
73
+ throw new SpongeError('integrity');
63
74
  await writeFile(path, data, { mode: 0o600, flag: 'wx' });
64
75
  }
65
- async function inventory(root) {
66
- const entries = [];
76
+ /** Hashes every retained byte when `full`; always records a stat line per entry (ctime cannot be set from user space). */
77
+ async function inventory(root, full = true) {
78
+ const entries = [], stats = [];
67
79
  let total = 0, count = 0;
68
80
  async function visit(dir) {
69
81
  for (const name of (await readdir(dir)).sort()) {
@@ -71,44 +83,66 @@ async function inventory(root) {
71
83
  if (rel === 'runtime-receipt.json')
72
84
  continue;
73
85
  if (++count > 60000)
74
- throw new CaptureSetupError('limit-exceeded');
75
- const stat = await lstat(path);
76
- total += stat.size;
86
+ throw new SpongeError('limit-exceeded');
87
+ const stat = await lstat(path, { bigint: true });
88
+ stats.push(statLine(rel, stat));
89
+ total += Number(stat.size);
77
90
  if (total > budget)
78
- throw new CaptureSetupError('limit-exceeded');
91
+ throw new SpongeError('limit-exceeded');
79
92
  if (stat.isDirectory())
80
93
  await visit(path);
94
+ else if (!full)
95
+ continue;
81
96
  else if (stat.isSymbolicLink()) {
82
97
  const target = await realpath(path);
83
98
  if (!target.startsWith(root + '/'))
84
- throw new CaptureSetupError('integrity');
99
+ throw new SpongeError('integrity');
85
100
  entries.push(`${rel}\0link\0${await readlink(path)}`);
86
101
  }
87
102
  else if (stat.isFile())
88
- entries.push(`${rel}\0${hash(await readConfined(root, rel, budget - total + stat.size))}`);
103
+ entries.push(`${rel}\0${hash(await readConfined(root, rel, budget - total + Number(stat.size)))}`);
89
104
  else
90
- throw new CaptureSetupError('integrity');
105
+ throw new SpongeError('integrity');
91
106
  }
92
107
  }
93
108
  await visit(root);
94
- return hash(entries.join('\n'));
109
+ return { sha256: hash(entries.join('\n')), stats };
95
110
  }
111
+ const statLine = (rel, s) => `${rel}\0${s.dev}\0${s.ino}\0${s.mode}\0${s.uid}\0${s.size}\0${s.mtimeNs}\0${s.ctimeNs}`;
112
+ // One full verify per process and runtime; later uses compare only the stat receipt and fall back to a full verify on any difference.
113
+ const verified = new Map();
114
+ /** Full inventory walks performed by this process (observability for tests). */
115
+ export const runtimeVerification = { fullWalks: 0 };
96
116
  async function inspectRuntime(root, pins) {
97
117
  checkPins(pins);
98
118
  await privateRoot(root);
99
- const receipt = JSON.parse((await readConfined(root, 'runtime-receipt.json', 4096)).toString());
100
- if (receipt.schemaVersion !== 1 || receipt.pinId !== pins.id || receipt.lockSha256 !== pins.lockSha256 || receipt.inventorySha256 !== await inventory(root))
101
- throw new CaptureSetupError('integrity');
102
- const bun = join(root, pins.bun.archiveExecutable), ghostget = join(root, 'node_modules/@hraness/ghostget/src/cli.ts');
119
+ const key = JSON.stringify([root, pins.id, pins.lockSha256, pins.bun.archiveExecutable, pins.bun.executableSha256]), bun = join(root, pins.bun.archiveExecutable), ghostget = join(root, 'node_modules/@hraness/ghostget/src/cli.ts');
120
+ // Stat receipt of the root, its receipt file and every inventoried entry. Each stat precedes any read of its entry, so a later change always shows.
121
+ const head = [
122
+ statLine('.', await lstat(root, { bigint: true })),
123
+ statLine('runtime-receipt.json', await lstat(join(root, 'runtime-receipt.json'), { bigint: true })),
124
+ ];
125
+ const cached = verified.get(key);
126
+ if (cached !== undefined && cached === hash([...head, ...(await inventory(root, false)).stats].join('\n')))
127
+ return { root, bun, ghostget, pins };
128
+ verified.delete(key);
129
+ runtimeVerification.fullWalks++;
130
+ const receipt = JSON.parse((await readConfined(root, 'runtime-receipt.json', 4096)).toString()), walked = await inventory(root);
131
+ if (receipt.schemaVersion !== 1 ||
132
+ receipt.pinId !== pins.id ||
133
+ receipt.lockSha256 !== pins.lockSha256 ||
134
+ receipt.inventorySha256 !== walked.sha256)
135
+ throw new SpongeError('integrity');
103
136
  if (hash(await readConfined(root, pins.bun.archiveExecutable, 128 * 1024 ** 2)) !== pins.bun.executableSha256)
104
- throw new CaptureSetupError('integrity');
137
+ throw new SpongeError('integrity');
138
+ verified.set(key, hash([...head, ...walked.stats].join('\n')));
105
139
  return { root, bun, ghostget, pins };
106
140
  }
107
141
  async function provisionRuntime(options) {
108
142
  const { pins, signal } = options, plan = captureSetupPlan(pins);
109
143
  options.onPlan?.(plan);
110
144
  if (process.platform !== 'darwin' || process.arch !== 'arm64')
111
- throw new CaptureSetupError('unsupported');
145
+ throw new SpongeError('unsupported');
112
146
  await mkdir(options.runtimeRoot, { recursive: true, mode: 0o700 });
113
147
  const base = await privateRoot(await realpath(options.runtimeRoot));
114
148
  const destination = join(base, pins.id);
@@ -122,7 +156,7 @@ async function provisionRuntime(options) {
122
156
  }
123
157
  const disk = await statfs(base);
124
158
  if (disk.bavail * disk.bsize < plan.requiredFreeBytes)
125
- throw new CaptureSetupError('limit-exceeded');
159
+ throw new SpongeError('limit-exceeded');
126
160
  const stage = await mkdtemp(join(base, '.install-'));
127
161
  await chmod(stage, 0o700);
128
162
  const home = join(stage, 'home');
@@ -133,65 +167,92 @@ async function provisionRuntime(options) {
133
167
  if (signal?.aborted)
134
168
  abort();
135
169
  let cleanup = 'settled', checking = false, limitExceeded = false;
136
- const monitor = setInterval(async () => { if (checking)
137
- return; checking = true; try {
138
- if (await physicalBytes(stage) > stagingBudget) {
139
- limitExceeded = true;
170
+ const monitor = setInterval(async () => {
171
+ if (checking)
172
+ return;
173
+ checking = true;
174
+ try {
175
+ if ((await physicalBytes(stage)) > stagingBudget) {
176
+ limitExceeded = true;
177
+ controller.abort();
178
+ }
179
+ }
180
+ catch {
140
181
  controller.abort();
141
182
  }
142
- }
143
- catch {
144
- controller.abort();
145
- }
146
- finally {
147
- checking = false;
148
- } }, 100);
183
+ finally {
184
+ checking = false;
185
+ }
186
+ }, 100);
149
187
  try {
150
188
  await download(pins.bun, join(stage, 'bun.zip'), controller.signal);
151
- const unpack = await runOwned('/usr/bin/unzip', ['-q', join(stage, 'bun.zip'), '-d', stage], { cwd: stage, env: isolatedEnvironment(home, home, home), signal: controller.signal, deadlineMs: 30000, maxOutputBytes: 65536 });
189
+ const unpack = await runOwned('/usr/bin/unzip', ['-q', join(stage, 'bun.zip'), '-d', stage], {
190
+ cwd: stage,
191
+ env: isolatedEnvironment(home, home, home),
192
+ signal: controller.signal,
193
+ deadlineMs: 30000,
194
+ maxOutputBytes: 65536,
195
+ });
152
196
  cleanup = unpack.cleanup;
153
197
  if (unpack.code !== 0 || unpack.stopped || cleanup !== 'settled')
154
- throw new CaptureSetupError(limitExceeded ? 'limit-exceeded' : unpack.stopped ?? 'unavailable');
198
+ throw new SpongeError(limitExceeded ? 'limit-exceeded' : (unpack.stopped ?? 'unavailable'));
155
199
  const bun = join(stage, pins.bun.archiveExecutable);
156
200
  if (hash(await readConfined(stage, pins.bun.archiveExecutable, 128 * 1024 ** 2)) !== pins.bun.executableSha256)
157
- throw new CaptureSetupError('integrity');
201
+ throw new SpongeError('integrity');
158
202
  await chmod(bun, 0o700);
159
203
  await download(pins.ghostget, join(stage, `hraness-ghostget-${pins.ghostget.version}.tgz`), controller.signal);
160
204
  await download(pins.wordcell, join(stage, `hraness-wordcell-${pins.wordcell.version}.tgz`), controller.signal);
161
- await writeFile(join(stage, 'package.json'), JSON.stringify({ private: true, dependencies: { '@hraness/ghostget': `./hraness-ghostget-${pins.ghostget.version}.tgz` } }), { mode: 0o600 });
205
+ await writeFile(join(stage, 'package.json'), JSON.stringify({
206
+ private: true,
207
+ dependencies: { '@hraness/ghostget': `./hraness-ghostget-${pins.ghostget.version}.tgz` },
208
+ }), { mode: 0o600 });
162
209
  await writeFile(join(stage, 'bun.lock'), pins.lockfile, { mode: 0o600 });
163
- const install = await runOwned(bun, ['install', '--frozen-lockfile', '--ignore-scripts'], { cwd: stage, env: isolatedEnvironment(home, home, home), signal: controller.signal, deadlineMs: installDeadlineMs, maxOutputBytes: 65536 });
210
+ const install = await runOwned(bun, ['install', '--frozen-lockfile', '--ignore-scripts'], {
211
+ cwd: stage,
212
+ env: isolatedEnvironment(home, home, home),
213
+ signal: controller.signal,
214
+ deadlineMs: installDeadlineMs,
215
+ maxOutputBytes: 65536,
216
+ });
164
217
  cleanup = install.cleanup;
165
218
  if (install.code !== 0 || install.stopped || cleanup !== 'settled')
166
- throw new CaptureSetupError(limitExceeded ? 'limit-exceeded' : install.stopped ?? 'unavailable');
219
+ throw new SpongeError(limitExceeded ? 'limit-exceeded' : (install.stopped ?? 'unavailable'));
167
220
  await rm(home, { recursive: true, force: true });
168
221
  await rm(join(stage, 'bun.zip'));
169
- for (const [name, pin] of [['ghostget', pins.ghostget], ['wordcell', pins.wordcell]]) {
222
+ for (const [name, pin] of [
223
+ ['ghostget', pins.ghostget],
224
+ ['wordcell', pins.wordcell],
225
+ ]) {
170
226
  const pkg = JSON.parse((await readConfined(stage, `node_modules/@hraness/${name}/package.json`, 65536)).toString());
171
227
  if (pkg.version !== pin.version)
172
- throw new CaptureSetupError('integrity');
228
+ throw new SpongeError('integrity');
173
229
  }
174
- await writeFile(join(stage, 'runtime-receipt.json'), JSON.stringify({ schemaVersion: 1, pinId: pins.id, lockSha256: pins.lockSha256, inventorySha256: await inventory(stage) }), { mode: 0o600 });
230
+ await writeFile(join(stage, 'runtime-receipt.json'), JSON.stringify({
231
+ schemaVersion: 1,
232
+ pinId: pins.id,
233
+ lockSha256: pins.lockSha256,
234
+ inventorySha256: (await inventory(stage)).sha256,
235
+ }), { mode: 0o600 });
175
236
  if (controller.signal.aborted)
176
- throw new CaptureSetupError('cancelled');
237
+ throw new SpongeError('cancelled');
177
238
  await rename(stage, destination);
178
239
  return await inspectCaptureRuntime(destination, pins);
179
240
  }
180
241
  finally {
181
242
  clearInterval(monitor);
182
243
  while (checking)
183
- await new Promise(r => setTimeout(r, 5));
244
+ await new Promise((r) => setTimeout(r, 5));
184
245
  signal?.removeEventListener('abort', abort);
185
246
  if (cleanup === 'settled') {
186
247
  try {
187
248
  await rm(stage, { recursive: true, force: true });
188
249
  }
189
250
  catch {
190
- throw new CaptureSetupError('cleanup-required', stage);
251
+ throw new SpongeError('cleanup-required', { detail: { stage } });
191
252
  }
192
253
  }
193
254
  else
194
- throw new CaptureSetupError('cleanup-required', stage);
255
+ throw new SpongeError('cleanup-required', { detail: { stage } });
195
256
  }
196
257
  }
197
258
  export async function inspectCaptureRuntime(root, pins) {
@@ -199,9 +260,9 @@ export async function inspectCaptureRuntime(root, pins) {
199
260
  return await inspectRuntime(root, pins);
200
261
  }
201
262
  catch (error) {
202
- if (error instanceof CaptureSetupError)
263
+ if (error instanceof SpongeError)
203
264
  throw error;
204
- throw new CaptureSetupError('runtime-missing');
265
+ throw new SpongeError('runtime-missing');
205
266
  }
206
267
  }
207
268
  export async function provisionCapture(options) {
@@ -209,10 +270,10 @@ export async function provisionCapture(options) {
209
270
  return await provisionRuntime(options);
210
271
  }
211
272
  catch (error) {
212
- if (error instanceof CaptureSetupError)
273
+ if (error instanceof SpongeError)
213
274
  throw error;
214
275
  if (options.signal?.aborted)
215
- throw new CaptureSetupError('cancelled');
216
- throw new CaptureSetupError('unavailable');
276
+ throw new SpongeError('cancelled');
277
+ throw new SpongeError('unavailable');
217
278
  }
218
279
  }