@hraness/sponge 0.5.2 → 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 (62) 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 +10 -46
  19. package/dist/src/cli/config.d.ts +5 -0
  20. package/dist/src/cli/config.js +14 -0
  21. package/dist/src/cli/import.d.ts +1 -3
  22. package/dist/src/cli/import.js +3 -3
  23. package/dist/src/cli/main.js +0 -2
  24. package/dist/src/cli/runtimes.js +2 -3
  25. package/dist/src/cli/worker.d.ts +1 -4
  26. package/dist/src/cli/worker.js +0 -4
  27. package/dist/src/importers/apply.d.ts +0 -2
  28. package/dist/src/importers/apply.js +2 -2
  29. package/dist/src/importers/inspect.d.ts +2 -2
  30. package/dist/src/importers/inspect.js +24 -23
  31. package/dist/src/metadata/article.d.ts +0 -1
  32. package/dist/src/metadata/article.js +3 -2
  33. package/dist/src/pdf/capture.d.ts +7 -16
  34. package/dist/src/pdf/capture.js +44 -198
  35. package/dist/src/pdf/handler.d.ts +1 -2
  36. package/dist/src/pdf/handler.js +0 -1
  37. package/dist/src/pdf/normalize.d.ts +2 -3
  38. package/dist/src/pdf/normalize.js +34 -38
  39. package/dist/src/pdf/tools.d.ts +2 -0
  40. package/dist/src/pdf/tools.js +21 -0
  41. package/native/runtime-manifest.json +5 -5
  42. package/package.json +11 -2
  43. package/dist/src/acquisition/clip-helper.js +0 -14
  44. package/dist/src/acquisition/lock.d.ts +0 -1
  45. package/dist/src/acquisition/lock.js +0 -2
  46. package/dist/src/acquisition/pins.d.ts +0 -3
  47. package/dist/src/acquisition/pins.js +0 -21
  48. package/dist/src/acquisition/runtime.d.ts +0 -48
  49. package/dist/src/acquisition/runtime.js +0 -273
  50. package/dist/src/acquisition/verify-helper.d.ts +0 -1
  51. package/dist/src/acquisition/verify-helper.js +0 -26
  52. package/dist/src/importers/verify-helper.d.ts +0 -1
  53. package/dist/src/importers/verify-helper.js +0 -41
  54. package/dist/src/pdf/capture-helper.d.ts +0 -1
  55. package/dist/src/pdf/capture-helper.js +0 -15
  56. package/dist/src/pdf/errors.d.ts +0 -2
  57. package/dist/src/pdf/errors.js +0 -22
  58. package/dist/src/pdf/source-identity.d.ts +0 -4
  59. package/dist/src/pdf/source-identity.js +0 -13
  60. package/dist/src/pdf/verify-helper.d.ts +0 -1
  61. package/dist/src/pdf/verify-helper.js +0 -29
  62. /package/dist/src/acquisition/{clip-helper.d.ts → child-request.js} +0 -0
@@ -1,9 +1,9 @@
1
- import { join, isAbsolute } from 'node:path';
1
+ import { isAbsolute } from 'node:path';
2
2
  import { randomUUID } from 'node:crypto';
3
- import { readConnections } from './config.js';
3
+ import { connectionFor, readConnections } from './config.js';
4
4
  import { pdfSource } from '../pdf/index.js';
5
5
  import { articleSource, inferKind, mediaSource } from '../app/source.js';
6
- import { defaultCapturePins, inspectCaptureRuntime, provisionCapture, captureSetupPlan } from '../acquisition/index.js';
6
+ import { pdfToolsAvailable } from '../pdf/tools.js';
7
7
  import { queueAction } from './queue.js';
8
8
  import { portabilityAction } from './portability.js';
9
9
  import { restoreLibrary } from '../portability/backup.js';
@@ -28,15 +28,6 @@ import { SpongeError, codeOf, publicCode, exitFor, isSpongeCode } from '../error
28
28
  const abs = (v) => typeof v === 'string' && isAbsolute(v);
29
29
  const media = { language: 'value', 'force-local': 'boolean', 'allow-private-network': 'boolean' };
30
30
  const lines = (items) => items.map((item) => `${item.itemId}\t${item.title}`);
31
- /** A required capture runtime, as the categorical code of its inspection failure. */
32
- async function captureRuntime(c) {
33
- try {
34
- return await inspectCaptureRuntime(c.captureRoot, defaultCapturePins);
35
- }
36
- catch (error) {
37
- throw new SpongeError(publicCode(error, 'runtime-missing'), { cause: error });
38
- }
39
- }
40
31
  /** The failure of a job that did not run here: its state, or its recorded reason. */
41
32
  function jobFailure(store, jobId) {
42
33
  const job = store.queue.list().find((j) => j.id === jobId), reason = job?.reason;
@@ -50,16 +41,7 @@ function jobFailure(store, jobId) {
50
41
  ? reason
51
42
  : 'unclassified');
52
43
  }
53
- const resolveConnection = (connections) => (realm) => {
54
- if (realm === 'public')
55
- return undefined;
56
- const connection = connections.find((c) => c.realm === realm);
57
- if (!connection)
58
- throw new SpongeError('repair-auth');
59
- if (!connection.capture)
60
- throw new SpongeError('connection-retired');
61
- return connection.capture;
62
- };
44
+ const resolveConnection = (connections) => (realm) => connectionFor(connections, realm);
63
45
  /** add/refresh with --wait and run --drain: drain acquisition (one job when given), then queued indexes for run. */
64
46
  async function drain(c, jobId, connections) {
65
47
  const { store, args, signal } = c;
@@ -69,13 +51,11 @@ async function drain(c, jobId, connections) {
69
51
  if (job && job.state !== 'queued')
70
52
  throw jobFailure(store, jobId);
71
53
  }
72
- const runtime = await captureRuntime(c);
73
54
  const results = await drainLibrary(store, {
74
55
  asrRuntime: await optionalNative('asr', c.nativeRoot, c.version),
75
56
  mediaRuntime: await optionalNative('media', c.nativeRoot, c.version),
76
57
  language: args.flags.language,
77
58
  forceLocal: args.flags['force-local'] === true,
78
- runtime,
79
59
  jobId,
80
60
  signal,
81
61
  allowPrivateNetwork: args.flags['allow-private-network'] === true,
@@ -175,14 +155,7 @@ export const commands = [
175
155
  await installNative('media').catch((error) => {
176
156
  media = codeOf(error);
177
157
  });
178
- const plan = captureSetupPlan(defaultCapturePins);
179
- process.stderr.write(`Capture setup: ${plan.downloadBytes} pinned artifact bytes, plus frozen dependency downloads. Temporary installation limit: ${plan.stagingBudgetBytes} bytes; retained runtime limit: ${plan.installationBudgetBytes} bytes; dependency installation deadline: ${plan.installationDeadlineMs / 60000} minutes. ${plan.requiredFreeBytes} free bytes required.\n`);
180
- await provisionCapture({
181
- runtimeRoot: join(c.store.root, 'runtime', 'capture'),
182
- pins: defaultCapturePins,
183
- signal: c.signal,
184
- });
185
- // Articles and PDFs run anywhere; the media helper is a native release asset and is best-effort.
158
+ // Articles and PDFs are built in; the media helper is a native release asset and is best-effort.
186
159
  const gc = await setupRuntimeGc(c.store, c.version), ready = media === 'ready'
187
160
  ? 'Capture ready'
188
161
  : media === 'unsupported'
@@ -438,13 +411,8 @@ export const commands = [
438
411
  library: 'open',
439
412
  handler: async (c) => {
440
413
  const { store } = c;
441
- let capture = 'ready';
442
- try {
443
- await inspectCaptureRuntime(c.captureRoot, defaultCapturePins);
444
- }
445
- catch (error) {
446
- capture = publicCode(error, 'runtime-missing');
447
- }
414
+ // Article capture is built in; PDFs need the local Poppler tools.
415
+ const capture = 'ready', pdf = pdfToolsAvailable() ? 'ready' : 'runtime-missing';
448
416
  const states = {};
449
417
  for (const kind of ['asr', 'embedding', 'media']) {
450
418
  try {
@@ -456,8 +424,8 @@ export const commands = [
456
424
  }
457
425
  }
458
426
  const cleanup = store.queue.pendingCleanup(), worker = await workerDoctor(store, c.version), workerBroken = !['ready', 'not-installed', 'unsupported'].includes(worker.worker);
459
- c.emit({ library: 'ready', capture, ...states, pendingCleanup: cleanup, worker }, `Library: ready\nCapture: ${capture}\nASR: ${states.asr}\nEmbedding: ${states.embedding}\nPending cleanup: ${cleanup}\nWorker: ${worker.worker}${Array.isArray(worker.drift) ? ` (${worker.drift.join(', ')})` : ''}${worker.repair ? `\n ${worker.repair}` : ''}`);
460
- return cleanup || capture !== 'ready' || workerBroken ? 4 : 0;
427
+ c.emit({ library: 'ready', capture, pdf, ...states, pendingCleanup: cleanup, worker }, `Library: ready\nCapture: ${capture}\nPDF: ${pdf === 'ready' ? 'ready' : 'Poppler not found (brew install poppler)'}\nASR: ${states.asr}\nEmbedding: ${states.embedding}\nPending cleanup: ${cleanup}\nWorker: ${worker.worker}${Array.isArray(worker.drift) ? ` (${worker.drift.join(', ')})` : ''}${worker.repair ? `\n ${worker.repair}` : ''}`);
428
+ return cleanup || workerBroken ? 4 : 0;
461
429
  },
462
430
  },
463
431
  {
@@ -554,7 +522,6 @@ export const commands = [
554
522
  signal: c.signal,
555
523
  language: args.flags.language,
556
524
  forceLocal: args.flags['force-local'] === true,
557
- resolveCaptureRuntime: () => inspectCaptureRuntime(c.captureRoot, defaultCapturePins),
558
525
  resolveConnection: resolveConnection(connections),
559
526
  }, c.version));
560
527
  return 0;
@@ -577,10 +544,8 @@ export const commands = [
577
544
  const { store, args, signal } = c, dryRun = args.flags.apply !== true, offset = Number(args.flags.offset ?? 0);
578
545
  if (args.positional[0] === 'jungle' && args.positional.length === 2) {
579
546
  // A dry run reads Jungle and the library only; applying reverifies every capture with the released verifier.
580
- const runtime = dryRun ? undefined : await captureRuntime(c);
581
547
  const report = await jungleAction(store, args.positional[1], {
582
548
  dryRun,
583
- runtime,
584
549
  limit: Number(args.flags.limit ?? 1000),
585
550
  offset,
586
551
  signal,
@@ -592,8 +557,7 @@ export const commands = [
592
557
  ? 3
593
558
  : 0;
594
559
  }
595
- const runtime = await captureRuntime(c), report = await importAction(store, args.positional[0], {
596
- runtime,
560
+ const report = await importAction(store, args.positional[0], {
597
561
  dryRun,
598
562
  limit: Number(args.flags.limit ?? 20),
599
563
  offset,
@@ -5,5 +5,10 @@ export interface Connection {
5
5
  /** Absent for a retired GhostGet account connection: it still names its items' realm, but cannot capture. */
6
6
  capture?: CaptureConnection;
7
7
  }
8
+ /**
9
+ * The capture connection for a source realm. Public and imported realms capture without one: an import realm
10
+ * records where an item came from, not an account, so refreshing an imported web page fetches it publicly.
11
+ */
12
+ export declare function connectionFor(connections: Connection[], realm: string): CaptureConnection | undefined;
8
13
  /** Local references only. Tokens, cookies and passwords never belong in this file. */
9
14
  export declare function readConnections(root: string): Promise<Connection[]>;
@@ -3,6 +3,20 @@ import { readConfined } from '../acquisition/files.js';
3
3
  import { join } from 'node:path';
4
4
  import { sha256 } from '../util/hash.js';
5
5
  import { SpongeError } from '../errors.js';
6
+ /**
7
+ * The capture connection for a source realm. Public and imported realms capture without one: an import realm
8
+ * records where an item came from, not an account, so refreshing an imported web page fetches it publicly.
9
+ */
10
+ export function connectionFor(connections, realm) {
11
+ if (realm === 'public' || realm.startsWith('import:'))
12
+ return undefined;
13
+ const connection = connections.find((c) => c.realm === realm);
14
+ if (!connection)
15
+ throw new SpongeError('repair-auth');
16
+ if (!connection.capture)
17
+ throw new SpongeError('connection-retired');
18
+ return connection.capture;
19
+ }
6
20
  /** Local references only. Tokens, cookies and passwords never belong in this file. */
7
21
  export async function readConnections(root) {
8
22
  const path = join(root, 'connections.json');
@@ -1,13 +1,11 @@
1
1
  import type { LibraryStore } from '../store/index.js';
2
- import type { CaptureRuntime } from '../acquisition/index.js';
3
2
  import { type ImportOptions, type ImportReport } from '../importers/apply.js';
4
3
  import { type JungleImportReport } from '../importers/jungle.js';
5
4
  /** A source manifest is explicit; default execution produces a private dry-run report. */
6
5
  export declare function importAction(store: LibraryStore, path: string, options: ImportOptions): Promise<ImportReport>;
7
- /** Jungle is read in place; only an applied import needs the capture runtime, to reverify each bundle. */
6
+ /** Jungle is read in place; only an applied import reverifies each bundle. */
8
7
  export declare function jungleAction(store: LibraryStore, path: string, options: {
9
8
  dryRun: boolean;
10
- runtime?: CaptureRuntime;
11
9
  limit: number;
12
10
  offset: number;
13
11
  signal?: AbortSignal;
@@ -1,18 +1,18 @@
1
1
  import { importManifest } from '../importers/apply.js';
2
- import { releasedBundleVerifier } from '../importers/inspect.js';
2
+ import { ownedBundleVerifier } from '../importers/inspect.js';
3
3
  import { importJungle } from '../importers/jungle.js';
4
4
  /** A source manifest is explicit; default execution produces a private dry-run report. */
5
5
  export function importAction(store, path, options) {
6
6
  return importManifest(store, path, options);
7
7
  }
8
- /** Jungle is read in place; only an applied import needs the capture runtime, to reverify each bundle. */
8
+ /** Jungle is read in place; only an applied import reverifies each bundle. */
9
9
  export function jungleAction(store, path, options) {
10
10
  return importJungle(store, path, {
11
11
  dryRun: options.dryRun,
12
12
  limit: options.limit,
13
13
  offset: options.offset,
14
14
  ...(options.signal ? { signal: options.signal } : {}),
15
- ...(options.runtime ? { verify: releasedBundleVerifier(options.runtime, store.root, options.signal) } : {}),
15
+ ...(options.dryRun ? {} : { verify: ownedBundleVerifier(store.root, options.signal) }),
16
16
  });
17
17
  }
18
18
  const tally = (m) => Object.entries(m)
@@ -6,7 +6,6 @@ import { terminalSafe } from './terminal.js';
6
6
  import { parseArguments } from './arguments.js';
7
7
  import { command, usageOf } from './commands.js';
8
8
  import { openLibrary } from '../store/index.js';
9
- import { defaultCapturePins } from '../acquisition/index.js';
10
9
  import { failure } from '../acquisition/errors.js';
11
10
  import { SpongeError, codeOf, publicCode, exitFor, hints, detailHints, debugChain, } from '../errors.js';
12
11
  /** Failures print one categorical JSON line: the public code, its specific `reason` when narrower, and a fixed hint.
@@ -65,7 +64,6 @@ export async function main(argv, version) {
65
64
  return await spec.handler({
66
65
  ...context,
67
66
  store,
68
- captureRoot: join(store.root, 'runtime', 'capture', defaultCapturePins.id),
69
67
  nativeRoot: join(store.root, 'runtime', 'models'),
70
68
  });
71
69
  }
@@ -1,10 +1,9 @@
1
- import { defaultCapturePins } from '../acquisition/index.js';
2
1
  import { loadNativeCatalog, nativeModelPackage } from '../models/catalog.js';
3
2
  import { modelSetupPlan } from '../models/artifacts.js';
4
3
  import { digestFingerprints } from '../models/digest.js';
5
4
  import { collectRuntimes } from '../worker/runtime-gc.js';
6
5
  import { codeOf } from '../errors.js';
7
- /** References of this installed Sponge: its capture pin, its catalog's model packages and every digest pin. */
6
+ /** References of this installed Sponge: its catalog's model packages and every digest pin. Capture is built in, so every capture runtime is superseded. */
8
7
  async function installedReferences(version) {
9
8
  let models = null;
10
9
  try {
@@ -19,7 +18,7 @@ async function installedReferences(version) {
19
18
  catch {
20
19
  /* Without the catalog no model directory is provably superseded. */
21
20
  }
22
- return { captureIds: [defaultCapturePins.id], models };
21
+ return { captureIds: [], models };
23
22
  }
24
23
  export async function runtimeGcAction(store, version, dryRun) {
25
24
  return collectRuntimes(store, { ...(await installedReferences(version)), dryRun });
@@ -12,10 +12,7 @@ export declare function workerDoctor(store: LibraryStore, version: string, envir
12
12
  lastExit?: unknown;
13
13
  }>;
14
14
  type DrainOptions = Parameters<typeof drainLibrary>[1];
15
- export type ServiceDrainOptions = Omit<DrainOptions, 'runtime'> & {
16
- runtime?: DrainOptions['runtime'];
17
- resolveCaptureRuntime?: () => Promise<DrainOptions['runtime']>;
18
- };
15
+ export type ServiceDrainOptions = DrainOptions;
19
16
  /** Dispatch only work that claimNext can admit; delayed retries retain their due time. */
20
17
  export declare function hasDueAcquisition(store: LibraryStore): boolean;
21
18
  export declare function drainServiceQueues(store: LibraryStore, options: ServiceDrainOptions, version: string, signal: AbortSignal): Promise<readonly unknown[]>;
@@ -60,16 +60,12 @@ export async function drainServiceQueues(store, options, version, signal) {
60
60
  let acquisitionError;
61
61
  if (hasDueAcquisition(store)) {
62
62
  try {
63
- const runtime = options.runtime ?? (await options.resolveCaptureRuntime?.());
64
- if (!runtime)
65
- throw new SpongeError('runtime-missing');
66
63
  const models = join(store.root, 'runtime', 'models');
67
64
  // Media jobs report a missing helper themselves; articles and PDFs still drain.
68
65
  const asrRuntime = options.asrRuntime ?? (await optionalNative('asr', models, version));
69
66
  const mediaRuntime = options.mediaRuntime ?? (await optionalNative('media', models, version));
70
67
  results.push(...(await drainLibrary(store, {
71
68
  ...options,
72
- runtime,
73
69
  asrRuntime,
74
70
  mediaRuntime,
75
71
  signal,
@@ -1,9 +1,7 @@
1
1
  import type { LibraryStore } from '../store/index.js';
2
2
  import { type ImportEntry } from './manifest.js';
3
3
  import { type BundleVerifier } from './inspect.js';
4
- import type { CaptureRuntime } from '../acquisition/runtime.js';
5
4
  export interface ImportOptions {
6
- runtime: CaptureRuntime;
7
5
  dryRun?: boolean;
8
6
  limit?: number;
9
7
  offset?: number;
@@ -5,7 +5,7 @@ import { localizeMarkdown } from '../app/markdown.js';
5
5
  import { selectedMetadata } from '../metadata/article.js';
6
6
  import { drainQueue } from '../worker/engine.js';
7
7
  import { readImportManifest, importBundleRoot } from './manifest.js';
8
- import { inspectImportBundle, releasedBundleVerifier } from './inspect.js';
8
+ import { inspectImportBundle, ownedBundleVerifier } from './inspect.js';
9
9
  import { SpongeError, publicCode } from '../errors.js';
10
10
  const errorCode = (error) => {
11
11
  const code = publicCode(error);
@@ -125,7 +125,7 @@ export async function importManifest(store, path, options) {
125
125
  entries: [],
126
126
  nextOffset: offset + limit < manifest.entries.length ? offset + limit : null,
127
127
  };
128
- const verify = options.verify ?? releasedBundleVerifier(options.runtime, store.root, options.signal);
128
+ const verify = options.verify ?? ownedBundleVerifier(store.root, options.signal);
129
129
  for (const entry of manifest.entries.slice(offset, offset + limit)) {
130
130
  if (options.signal?.aborted)
131
131
  throw new SpongeError('cancelled');
@@ -1,5 +1,4 @@
1
1
  import type { Asset, MetadataClaim } from '../../contracts/index.js';
2
- import { type CaptureRuntime } from '../acquisition/runtime.js';
3
2
  export interface ImportedBundle {
4
3
  root: string;
5
4
  kind: 'article' | 'pdf';
@@ -16,6 +15,7 @@ export interface ImportedBundle {
16
15
  importHash: string;
17
16
  }
18
17
  export type BundleVerifier = (root: string, kind: 'article' | 'pdf') => Promise<unknown>;
19
- export declare function releasedBundleVerifier(runtime: CaptureRuntime, libraryRoot: string, signal?: AbortSignal): BundleVerifier;
18
+ /** Bundles are read by the owned child with networking denied; its report carries no document text. */
19
+ export declare function ownedBundleVerifier(libraryRoot: string, signal?: AbortSignal): BundleVerifier;
20
20
  /** An import-time digest never upgrades a missing historical document digest. */
21
21
  export declare function inspectImportBundle(root: string, verify: BundleVerifier): Promise<ImportedBundle>;
@@ -1,4 +1,5 @@
1
- import { fileURLToPath, pathToFileURL } from 'node:url';
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { pathToFileURL } from 'node:url';
2
3
  import { join } from 'node:path';
3
4
  import { limits } from '../../contracts/index.js';
4
5
  import { validateAsset } from '../../contracts/validate.js';
@@ -6,32 +7,32 @@ import { object, metadataFromMarkdown } from '../acquisition/normalize.js';
6
7
  import { readConfined } from '../acquisition/files.js';
7
8
  import { sha256 as hash } from '../util/hash.js';
8
9
  import { withImportVerification } from './custody.js';
9
- import { inspectCaptureRuntime } from '../acquisition/runtime.js';
10
+ import { workerArguments } from '../acquisition/child.js';
10
11
  import { articleMetadata } from '../metadata/article.js';
11
12
  import { bundleBytes } from '../app/bundle-files.js';
12
13
  import { SpongeError } from '../errors.js';
13
- export function releasedBundleVerifier(runtime, libraryRoot, signal) {
14
- return async (root, kind) => {
15
- const checked = await inspectCaptureRuntime(runtime.root, runtime.pins), helper = fileURLToPath(new URL(import.meta.url.endsWith('.ts') ? './verify-helper.ts' : './verify-helper.js', import.meta.url));
16
- return withImportVerification(libraryRoot, async ({ scratchRoot, runProcess }) => {
17
- const args = [helper, checked.root, root, kind];
18
- const result = await runProcess(process.platform === 'darwin' ? '/usr/bin/sandbox-exec' : checked.bun, process.platform === 'darwin'
19
- ? ['-p', '(version 1) (allow default) (deny network*)', checked.bun, ...args]
20
- : args, {
21
- env: { PATH: '/usr/bin:/bin', HOME: checked.root, TMPDIR: scratchRoot, LANG: 'en_US.UTF-8' },
22
- deadlineMs: 45000,
23
- maxOutputBytes: 65536,
24
- signal,
25
- });
26
- if (result.cleanup !== 'settled')
27
- throw new SpongeError('cleanup-required');
28
- if (result.stopped)
29
- throw new SpongeError(result.stopped);
30
- if (result.code !== 0)
31
- throw new SpongeError('unsupported');
32
- return JSON.parse(result.stdout);
14
+ /** Bundles are read by the owned child with networking denied; its report carries no document text. */
15
+ export function ownedBundleVerifier(libraryRoot, signal) {
16
+ return (bundle, format) => withImportVerification(libraryRoot, async ({ scratchRoot, runProcess }) => {
17
+ const requestFile = join(scratchRoot, 'request.json'), request = { schemaVersion: 1, kind: 'verify', format, bundle };
18
+ await writeFile(requestFile, JSON.stringify(request), { mode: 0o600 });
19
+ const args = [...workerArguments(), requestFile];
20
+ const result = await runProcess(process.platform === 'darwin' ? '/usr/bin/sandbox-exec' : process.execPath, process.platform === 'darwin'
21
+ ? ['-p', '(version 1) (allow default) (deny network*)', process.execPath, ...args]
22
+ : args, {
23
+ env: { PATH: '/usr/bin:/bin', HOME: scratchRoot, TMPDIR: scratchRoot, LANG: 'en_US.UTF-8' },
24
+ deadlineMs: 45000,
25
+ maxOutputBytes: 65536,
26
+ signal,
33
27
  });
34
- };
28
+ if (result.cleanup !== 'settled')
29
+ throw new SpongeError('cleanup-required');
30
+ if (result.stopped)
31
+ throw new SpongeError(result.stopped);
32
+ if (result.code !== 0)
33
+ throw new SpongeError('unsupported');
34
+ return JSON.parse(result.stdout);
35
+ });
35
36
  }
36
37
  function imageType(bytes) {
37
38
  if (bytes[0] === 255 && bytes[1] === 216)
@@ -1,5 +1,4 @@
1
1
  import type { MetadataClaim, MetadataWarning } from '../../contracts/index.js';
2
- /** Consume bounded observed Wordcell evidence without fetching linked JSON-LD contexts. */
3
2
  export declare function articleMetadata(input: unknown, observedAt: string): MetadataClaim[];
4
3
  export declare function selectedMetadata(claims: readonly MetadataClaim[], field: string): MetadataClaim | undefined;
5
4
  export declare function articleMetadataWarnings(input: unknown): MetadataWarning[];
@@ -6,11 +6,12 @@ function object(v) {
6
6
  throw new SpongeError('integrity');
7
7
  return v;
8
8
  }
9
- /** Consume bounded observed Wordcell evidence without fetching linked JSON-LD contexts. */
9
+ /** Consume bounded observed page evidence without fetching linked JSON-LD contexts. Imported Wordcell bundles keep their own version. */
10
+ const extractorVersions = new Set(['sponge-metadata-v1', 'wordcell-metadata-v1']);
10
11
  export function articleMetadata(input, observedAt) {
11
12
  const envelope = object(input);
12
13
  articleMetadataWarnings(input);
13
- if (envelope.schemaVersion !== 1 || envelope.extractorVersion !== 'wordcell-metadata-v1')
14
+ if (envelope.schemaVersion !== 1 || !extractorVersions.has(String(envelope.extractorVersion)))
14
15
  throw new SpongeError('schema-newer');
15
16
  if (!Array.isArray(envelope.claims) ||
16
17
  envelope.claims.length > limits.metadataClaims ||
@@ -1,26 +1,17 @@
1
- import type { CaptureResult, ErrorEnvelope } from '../../contracts/index.js';
2
- import { runOwned } from '../acquisition/process.js';
3
- import { type CaptureRuntime } from '../acquisition/runtime.js';
4
- type CaptureConnection = {
5
- kind: 'cdp';
6
- endpoint: string;
7
- };
1
+ import type { runOwned } from '../acquisition/process.js';
2
+ import { type CaptureOutcome } from '../acquisition/child.js';
3
+ export type { CaptureOutcome } from '../acquisition/child.js';
8
4
  export interface PdfCaptureRequest {
9
- runtime: CaptureRuntime;
10
5
  stagingRoot: string;
11
6
  sourceUrl: string;
12
7
  requestId: string;
13
- connection?: CaptureConnection;
8
+ connection?: {
9
+ kind: 'cdp';
10
+ endpoint: string;
11
+ };
14
12
  signal?: AbortSignal;
15
13
  deadlineMs?: number;
16
14
  allowPrivateNetwork?: boolean;
17
15
  runProcess?: typeof runOwned;
18
16
  }
19
- export type CaptureOutcome = {
20
- ok: true;
21
- capture: CaptureResult;
22
- bundleRoot: string;
23
- dispose: () => Promise<void>;
24
- } | ErrorEnvelope;
25
17
  export declare function capturePdf(request: PdfCaptureRequest): Promise<CaptureOutcome>;
26
- export {};