@hraness/sponge 0.5.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +8 -11
  2. package/dist/capture/NOTICES.txt +472 -0
  3. package/dist/capture/worker.js +553 -0
  4. package/dist/src/acquisition/capture.d.ts +3 -10
  5. package/dist/src/acquisition/capture.js +28 -202
  6. package/dist/src/acquisition/child-request.d.ts +40 -0
  7. package/dist/src/acquisition/child.d.ts +31 -0
  8. package/dist/src/acquisition/child.js +130 -0
  9. package/dist/src/acquisition/errors.d.ts +2 -2
  10. package/dist/src/acquisition/errors.js +15 -4
  11. package/dist/src/acquisition/index.d.ts +0 -3
  12. package/dist/src/acquisition/index.js +0 -2
  13. package/dist/src/acquisition/normalize.d.ts +1 -3
  14. package/dist/src/acquisition/normalize.js +20 -22
  15. package/dist/src/app/article.d.ts +1 -2
  16. package/dist/src/app/article.js +1 -2
  17. package/dist/src/cli/commands.d.ts +0 -1
  18. package/dist/src/cli/commands.js +8 -35
  19. package/dist/src/cli/import.d.ts +1 -3
  20. package/dist/src/cli/import.js +3 -3
  21. package/dist/src/cli/main.js +0 -2
  22. package/dist/src/cli/runtimes.js +2 -3
  23. package/dist/src/cli/worker.d.ts +1 -4
  24. package/dist/src/cli/worker.js +0 -4
  25. package/dist/src/importers/apply.d.ts +0 -2
  26. package/dist/src/importers/apply.js +2 -2
  27. package/dist/src/importers/inspect.d.ts +2 -2
  28. package/dist/src/importers/inspect.js +24 -23
  29. package/dist/src/metadata/article.d.ts +0 -1
  30. package/dist/src/metadata/article.js +3 -2
  31. package/dist/src/pdf/capture.d.ts +7 -16
  32. package/dist/src/pdf/capture.js +44 -198
  33. package/dist/src/pdf/handler.d.ts +1 -2
  34. package/dist/src/pdf/handler.js +0 -1
  35. package/dist/src/pdf/normalize.d.ts +2 -3
  36. package/dist/src/pdf/normalize.js +34 -38
  37. package/dist/src/pdf/tools.d.ts +2 -0
  38. package/dist/src/pdf/tools.js +21 -0
  39. package/native/runtime-manifest.json +5 -5
  40. package/package.json +11 -2
  41. package/dist/src/acquisition/clip-helper.js +0 -14
  42. package/dist/src/acquisition/lock.d.ts +0 -1
  43. package/dist/src/acquisition/lock.js +0 -2
  44. package/dist/src/acquisition/pins.d.ts +0 -3
  45. package/dist/src/acquisition/pins.js +0 -21
  46. package/dist/src/acquisition/runtime.d.ts +0 -48
  47. package/dist/src/acquisition/runtime.js +0 -273
  48. package/dist/src/acquisition/verify-helper.d.ts +0 -1
  49. package/dist/src/acquisition/verify-helper.js +0 -26
  50. package/dist/src/importers/verify-helper.d.ts +0 -1
  51. package/dist/src/importers/verify-helper.js +0 -41
  52. package/dist/src/pdf/capture-helper.d.ts +0 -1
  53. package/dist/src/pdf/capture-helper.js +0 -15
  54. package/dist/src/pdf/errors.d.ts +0 -2
  55. package/dist/src/pdf/errors.js +0 -23
  56. package/dist/src/pdf/source-identity.d.ts +0 -4
  57. package/dist/src/pdf/source-identity.js +0 -13
  58. package/dist/src/pdf/verify-helper.d.ts +0 -1
  59. package/dist/src/pdf/verify-helper.js +0 -29
  60. /package/dist/src/acquisition/{clip-helper.d.ts → child-request.js} +0 -0
@@ -1,273 +0,0 @@
1
- import { mkdir, mkdtemp, writeFile, rename, rm, chmod, statfs, lstat, readdir, readlink, realpath, } from 'node:fs/promises';
2
- import { join, relative } from 'node:path';
3
- import { runOwned, isolatedEnvironment } from './process.js';
4
- import { privateRoot, readConfined, physicalBytes } from './files.js';
5
- import { sha256 as hash } from '../util/hash.js';
6
- import { SpongeError } from '../errors.js';
7
- // Retained inventory excludes the isolated Bun cache and temporary Bun zip.
8
- const budget = 1024 ** 3;
9
- const stagingBudget = 2 * budget;
10
- // The frozen dependency install measured 460 s on a slow link; 300 s aborted valid setups.
11
- const installDeadlineMs = 30 * 60 * 1000;
12
- function checkPins(pins) {
13
- if (!/^[a-z0-9][a-z0-9.-]{0,99}$/.test(pins.id) || hash(pins.lockfile) !== pins.lockSha256)
14
- throw new SpongeError('invalid-input');
15
- for (const pin of [pins.bun, pins.wordcell]) {
16
- let url;
17
- try {
18
- url = new URL(pin.url);
19
- }
20
- catch {
21
- throw new SpongeError('invalid-input');
22
- }
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
- }
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');
36
- }
37
- export function captureSetupPlan(pins) {
38
- checkPins(pins);
39
- const artifacts = [pins.bun, pins.wordcell], downloadBytes = artifacts.reduce((n, a) => n + a.bytes, 0);
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-attached-browser', 'verified-offline-images'],
50
- };
51
- }
52
- async function download(pin, path, signal) {
53
- const boundedSignal = signal ? AbortSignal.any([signal, AbortSignal.timeout(120000)]) : AbortSignal.timeout(120000);
54
- const response = await fetch(pin.url, { signal: boundedSignal });
55
- if (!response.ok || !response.body || new URL(response.url).protocol !== 'https:')
56
- throw new SpongeError('unavailable');
57
- const declared = response.headers.get('content-length');
58
- if (declared !== null && Number(declared) !== pin.bytes) {
59
- await response.body.cancel();
60
- throw new SpongeError('integrity');
61
- }
62
- // Primary release artifacts are small, fixed byte records. Never write an unchecked chunk.
63
- const chunks = [];
64
- let bytes = 0;
65
- for await (const chunk of response.body) {
66
- bytes += chunk.length;
67
- if (bytes > pin.bytes)
68
- throw new SpongeError('integrity');
69
- chunks.push(chunk);
70
- }
71
- const data = Buffer.concat(chunks);
72
- if (bytes !== pin.bytes || hash(data) !== pin.sha256)
73
- throw new SpongeError('integrity');
74
- await writeFile(path, data, { mode: 0o600, flag: 'wx' });
75
- }
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 = [];
79
- let total = 0, count = 0;
80
- async function visit(dir) {
81
- for (const name of (await readdir(dir)).sort()) {
82
- const path = join(dir, name), rel = relative(root, path);
83
- if (rel === 'runtime-receipt.json')
84
- continue;
85
- if (++count > 60000)
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);
90
- if (total > budget)
91
- throw new SpongeError('limit-exceeded');
92
- if (stat.isDirectory())
93
- await visit(path);
94
- else if (!full)
95
- continue;
96
- else if (stat.isSymbolicLink()) {
97
- const target = await realpath(path);
98
- if (!target.startsWith(root + '/'))
99
- throw new SpongeError('integrity');
100
- entries.push(`${rel}\0link\0${await readlink(path)}`);
101
- }
102
- else if (stat.isFile())
103
- entries.push(`${rel}\0${hash(await readConfined(root, rel, budget - total + Number(stat.size)))}`);
104
- else
105
- throw new SpongeError('integrity');
106
- }
107
- }
108
- await visit(root);
109
- return { sha256: hash(entries.join('\n')), stats };
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 };
116
- async function inspectRuntime(root, pins) {
117
- checkPins(pins);
118
- await privateRoot(root);
119
- const key = JSON.stringify([root, pins.id, pins.lockSha256, pins.bun.archiveExecutable, pins.bun.executableSha256]), bun = join(root, pins.bun.archiveExecutable);
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, 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');
136
- if (hash(await readConfined(root, pins.bun.archiveExecutable, 128 * 1024 ** 2)) !== pins.bun.executableSha256)
137
- throw new SpongeError('integrity');
138
- verified.set(key, hash([...head, ...walked.stats].join('\n')));
139
- return { root, bun, pins };
140
- }
141
- async function provisionRuntime(options) {
142
- const { pins, signal } = options, plan = captureSetupPlan(pins);
143
- options.onPlan?.(plan);
144
- if (process.platform !== 'darwin' || process.arch !== 'arm64')
145
- throw new SpongeError('unsupported');
146
- await mkdir(options.runtimeRoot, { recursive: true, mode: 0o700 });
147
- const base = await privateRoot(await realpath(options.runtimeRoot));
148
- const destination = join(base, pins.id);
149
- try {
150
- await lstat(destination);
151
- return await inspectCaptureRuntime(destination, pins);
152
- }
153
- catch (e) {
154
- if (e.code !== 'ENOENT')
155
- throw e;
156
- }
157
- const disk = await statfs(base);
158
- if (disk.bavail * disk.bsize < plan.requiredFreeBytes)
159
- throw new SpongeError('limit-exceeded');
160
- const stage = await mkdtemp(join(base, '.install-'));
161
- await chmod(stage, 0o700);
162
- const home = join(stage, 'home');
163
- await mkdir(home, { mode: 0o700 });
164
- const controller = new AbortController();
165
- const abort = () => controller.abort();
166
- signal?.addEventListener('abort', abort, { once: true });
167
- if (signal?.aborted)
168
- abort();
169
- let cleanup = 'settled', checking = false, limitExceeded = false;
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 {
181
- controller.abort();
182
- }
183
- finally {
184
- checking = false;
185
- }
186
- }, 100);
187
- try {
188
- await download(pins.bun, join(stage, 'bun.zip'), controller.signal);
189
- const unpack = await runOwned('/usr/bin/unzip', ['-q', join(stage, 'bun.zip'), '-d', stage], {
190
- cwd: stage,
191
- env: isolatedEnvironment(home, home),
192
- signal: controller.signal,
193
- deadlineMs: 30000,
194
- maxOutputBytes: 65536,
195
- });
196
- cleanup = unpack.cleanup;
197
- if (unpack.code !== 0 || unpack.stopped || cleanup !== 'settled')
198
- throw new SpongeError(limitExceeded ? 'limit-exceeded' : (unpack.stopped ?? 'unavailable'));
199
- const bun = join(stage, pins.bun.archiveExecutable);
200
- if (hash(await readConfined(stage, pins.bun.archiveExecutable, 128 * 1024 ** 2)) !== pins.bun.executableSha256)
201
- throw new SpongeError('integrity');
202
- await chmod(bun, 0o700);
203
- await download(pins.wordcell, join(stage, `hraness-wordcell-${pins.wordcell.version}.tgz`), controller.signal);
204
- await writeFile(join(stage, 'package.json'), JSON.stringify({
205
- private: true,
206
- dependencies: { '@hraness/wordcell': `./hraness-wordcell-${pins.wordcell.version}.tgz` },
207
- }), { mode: 0o600 });
208
- await writeFile(join(stage, 'bun.lock'), pins.lockfile, { mode: 0o600 });
209
- const install = await runOwned(bun, ['install', '--frozen-lockfile', '--ignore-scripts'], {
210
- cwd: stage,
211
- env: isolatedEnvironment(home, home),
212
- signal: controller.signal,
213
- deadlineMs: installDeadlineMs,
214
- maxOutputBytes: 65536,
215
- });
216
- cleanup = install.cleanup;
217
- if (install.code !== 0 || install.stopped || cleanup !== 'settled')
218
- throw new SpongeError(limitExceeded ? 'limit-exceeded' : (install.stopped ?? 'unavailable'));
219
- await rm(home, { recursive: true, force: true });
220
- await rm(join(stage, 'bun.zip'));
221
- const pkg = JSON.parse((await readConfined(stage, 'node_modules/@hraness/wordcell/package.json', 65536)).toString());
222
- if (pkg.version !== pins.wordcell.version)
223
- throw new SpongeError('integrity');
224
- await writeFile(join(stage, 'runtime-receipt.json'), JSON.stringify({
225
- schemaVersion: 1,
226
- pinId: pins.id,
227
- lockSha256: pins.lockSha256,
228
- inventorySha256: (await inventory(stage)).sha256,
229
- }), { mode: 0o600 });
230
- if (controller.signal.aborted)
231
- throw new SpongeError('cancelled');
232
- await rename(stage, destination);
233
- return await inspectCaptureRuntime(destination, pins);
234
- }
235
- finally {
236
- clearInterval(monitor);
237
- while (checking)
238
- await new Promise((r) => setTimeout(r, 5));
239
- signal?.removeEventListener('abort', abort);
240
- if (cleanup === 'settled') {
241
- try {
242
- await rm(stage, { recursive: true, force: true });
243
- }
244
- catch {
245
- throw new SpongeError('cleanup-required', { detail: { stage } });
246
- }
247
- }
248
- else
249
- throw new SpongeError('cleanup-required', { detail: { stage } });
250
- }
251
- }
252
- export async function inspectCaptureRuntime(root, pins) {
253
- try {
254
- return await inspectRuntime(root, pins);
255
- }
256
- catch (error) {
257
- if (error instanceof SpongeError)
258
- throw error;
259
- throw new SpongeError('runtime-missing');
260
- }
261
- }
262
- export async function provisionCapture(options) {
263
- try {
264
- return await provisionRuntime(options);
265
- }
266
- catch (error) {
267
- if (error instanceof SpongeError)
268
- throw error;
269
- if (options.signal?.aborted)
270
- throw new SpongeError('cancelled');
271
- throw new SpongeError('unavailable');
272
- }
273
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,26 +0,0 @@
1
- import { SpongeError } from '../errors.js';
2
- // Executed only by the pinned Bun runtime. The import is Wordcell's public API.
3
- const runtime = process.argv[2], bundle = process.argv[3];
4
- try {
5
- const { verifyCaptureBundle } = await import(import.meta.resolve('@hraness/wordcell/clip/bundle-reader', `${runtime}/package.json`));
6
- const result = await verifyCaptureBundle(bundle, {
7
- verifyAssets: true,
8
- maxDocumentBytes: 16 * 1024 ** 2,
9
- maxVerifiedAssets: 200,
10
- maxAssetBytes: 20 * 1024 ** 2,
11
- maxTotalAssetBytes: 500 * 1024 ** 2,
12
- maxAssetVerificationMs: 30000,
13
- });
14
- // The document travels via the verified file, never a giant protocol frame.
15
- const { markdown: _, ...document } = result.inspection.document;
16
- // Metadata is validated upstream and read from the confined manifest by the parent.
17
- const { metadata: _metadata, ...inspection } = result.inspection;
18
- const output = JSON.stringify({ ...result, inspection: { ...inspection, document } });
19
- if (Buffer.byteLength(output) > 65536)
20
- throw new SpongeError('limit-exceeded');
21
- process.stdout.write(output);
22
- }
23
- catch {
24
- process.stdout.write(JSON.stringify({ ok: false }));
25
- process.exitCode = 1;
26
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,41 +0,0 @@
1
- import { SpongeError } from '../errors.js';
2
- /** Only released Wordcell public verifiers, with no source or document text in output. */
3
- const runtime = process.argv[2], bundle = process.argv[3], kind = process.argv[4];
4
- try {
5
- let result;
6
- if (kind === 'pdf') {
7
- const { verifyPdfCaptureBundle } = await import(import.meta.resolve('@hraness/wordcell/pdf', `${runtime}/package.json`));
8
- result = await verifyPdfCaptureBundle(bundle, {
9
- maxDocumentBytes: 16 * 1024 ** 2,
10
- maxSourceBytes: 100 * 1024 ** 2,
11
- maxVerifiedAssets: 200,
12
- maxAssetBytes: 20 * 1024 ** 2,
13
- maxTotalAssetBytes: 500 * 1024 ** 2,
14
- maxVerificationMs: 30000,
15
- });
16
- const { markdown, ...document } = result.inspection.markdown;
17
- result = { ...result, inspection: { ...result.inspection, markdown: document } };
18
- }
19
- else {
20
- const { verifyCaptureBundle } = await import(import.meta.resolve('@hraness/wordcell/clip/bundle-reader', `${runtime}/package.json`));
21
- result = await verifyCaptureBundle(bundle, {
22
- verifyAssets: true,
23
- maxDocumentBytes: 16 * 1024 ** 2,
24
- maxVerifiedAssets: 200,
25
- maxAssetBytes: 20 * 1024 ** 2,
26
- maxTotalAssetBytes: 500 * 1024 ** 2,
27
- maxAssetVerificationMs: 30000,
28
- });
29
- const { markdown, ...document } = result.inspection.document;
30
- const { metadata, ...inspection } = result.inspection;
31
- result = { ...result, inspection: { ...inspection, document } };
32
- }
33
- const output = JSON.stringify(result);
34
- if (Buffer.byteLength(output) > 65536)
35
- throw new SpongeError('limit-exceeded');
36
- process.stdout.write(output);
37
- }
38
- catch {
39
- process.stdout.write(JSON.stringify({ ok: false, code: 'unsupported' }));
40
- process.exitCode = 1;
41
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,15 +0,0 @@
1
- // Pinned public Wordcell bridge. Native subprocesses inherit the supervisor's group.
2
- const runtime = process.argv[2], args = process.argv.slice(3);
3
- try {
4
- const { runPdfCommand, runPdfToolCommand } = await import(import.meta.resolve('@hraness/wordcell/pdf', `${runtime}/package.json`));
5
- process.exitCode = await runPdfCommand(args, process.env, { stdout: (value) => process.stdout.write(value), stderr: (value) => process.stderr.write(value) }, {
6
- captureDependencies: {
7
- runTool: (specification) => runPdfToolCommand({ ...specification, processGroup: 'inherit' }),
8
- },
9
- });
10
- }
11
- catch {
12
- process.stdout.write(JSON.stringify({ ok: false, code: 'unknown' }));
13
- process.exitCode = 1;
14
- }
15
- export {};
@@ -1,2 +0,0 @@
1
- /** Wordcell's pinned PDF code field is authoritative; prose is never classified. */
2
- export declare function pdfFailure(value: unknown, requestId: string): import("../../contracts/index.js").ErrorEnvelope;
@@ -1,23 +0,0 @@
1
- import { failure } from '../acquisition/errors.js';
2
- /** Wordcell's pinned PDF code field is authoritative; prose is never classified. */
3
- export function pdfFailure(value, requestId) {
4
- if (!value || typeof value !== 'object' || Array.isArray(value))
5
- return failure(requestId, 'unclassified');
6
- const record = value;
7
- if (record.ok !== false)
8
- return failure(requestId, 'unclassified');
9
- const codes = {
10
- 'invalid-input': 'invalid-input',
11
- encrypted: 'unsupported',
12
- 'limit-exceeded': 'limit-exceeded',
13
- 'tool-unavailable': 'runtime-missing',
14
- 'extraction-failed': 'unavailable',
15
- 'fetch-failed': 'unavailable',
16
- timeout: 'unavailable',
17
- cancelled: 'cancelled',
18
- integrity: 'integrity',
19
- 'unsupported-schema': 'schema-newer',
20
- unknown: 'unclassified',
21
- };
22
- return failure(requestId, typeof record.code === 'string' && Object.hasOwn(codes, record.code) ? codes[record.code] : 'unclassified');
23
- }
@@ -1,4 +0,0 @@
1
- /** Local PDFs are bound to copied bytes; only remote sources use Wordcell's URL sanitizer. */
2
- export declare function pdfSourceIdentity(sourceUrl: string, sanitize: (url: string) => string): {
3
- expectedSourceUrl?: string;
4
- };
@@ -1,13 +0,0 @@
1
- import { SpongeError } from '../errors.js';
2
- /** Local PDFs are bound to copied bytes; only remote sources use Wordcell's URL sanitizer. */
3
- export function pdfSourceIdentity(sourceUrl, sanitize) {
4
- const url = new URL(sourceUrl);
5
- if (url.protocol === 'file:') {
6
- if (url.host || url.username || url.password || url.search || url.hash)
7
- throw new SpongeError('integrity');
8
- return {};
9
- }
10
- if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password)
11
- throw new SpongeError('integrity');
12
- return { expectedSourceUrl: sanitize(sourceUrl) };
13
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,29 +0,0 @@
1
- // Public PDF verifier, executed only by the integrity-checked pinned Bun runtime.
2
- import { pdfSourceIdentity } from './source-identity.js';
3
- import { SpongeError } from '../errors.js';
4
- const runtime = process.argv[2], bundle = process.argv[3];
5
- try {
6
- const { sanitizeArtifactUrl } = await import(import.meta.resolve('@hraness/wordcell/clip/persist', `${runtime}/package.json`));
7
- const { verifyPdfCaptureBundle } = await import(import.meta.resolve('@hraness/wordcell/pdf', `${runtime}/package.json`));
8
- const result = await verifyPdfCaptureBundle(bundle, {
9
- maxDocumentBytes: 16 * 1024 ** 2,
10
- maxSourceBytes: 100 * 1024 ** 2,
11
- maxVerifiedAssets: 200,
12
- maxAssetBytes: 20 * 1024 ** 2,
13
- maxTotalAssetBytes: 500 * 1024 ** 2,
14
- maxVerificationMs: 30000,
15
- });
16
- const { markdown, ...document } = result.inspection.markdown;
17
- const output = JSON.stringify({
18
- ...result,
19
- ...pdfSourceIdentity(process.argv[4], sanitizeArtifactUrl),
20
- inspection: { ...result.inspection, markdown: document },
21
- });
22
- if (Buffer.byteLength(output) > 65536)
23
- throw new SpongeError('limit-exceeded');
24
- process.stdout.write(output);
25
- }
26
- catch {
27
- process.stdout.write(JSON.stringify({ ok: false }));
28
- process.exitCode = 1;
29
- }