@hraness/sponge 0.2.5 → 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.
- package/README.md +23 -10
- package/bin/sponge.js +8 -7
- package/dist/contracts/index.js +24 -6
- package/dist/contracts/validate.js +106 -29
- package/dist/migrations/008.d.ts +3 -0
- package/dist/migrations/008.js +5 -0
- package/dist/migrations/009.d.ts +3 -0
- package/dist/migrations/009.js +8 -0
- package/dist/migrations/010.d.ts +3 -0
- package/dist/migrations/010.js +11 -0
- package/dist/migrations/index.d.ts +15 -0
- package/dist/migrations/index.js +39 -0
- package/dist/src/acquisition/capture.js +87 -28
- package/dist/src/acquisition/cleanup.d.ts +0 -4
- package/dist/src/acquisition/cleanup.js +3 -5
- package/dist/src/acquisition/errors.js +17 -2
- package/dist/src/acquisition/files.d.ts +0 -1
- package/dist/src/acquisition/files.js +14 -11
- package/dist/src/acquisition/index.d.ts +2 -3
- package/dist/src/acquisition/index.js +1 -2
- package/dist/src/acquisition/lock.js +1 -1
- package/dist/src/acquisition/normalize.js +57 -18
- package/dist/src/acquisition/pins.js +22 -4
- package/dist/src/acquisition/process.js +67 -29
- package/dist/src/acquisition/runtime.d.ts +5 -7
- package/dist/src/acquisition/runtime.js +129 -68
- package/dist/src/acquisition/verify-helper.js +10 -3
- package/dist/src/annotations/quote.js +40 -12
- package/dist/src/annotations/store.d.ts +0 -1
- package/dist/src/annotations/store.js +66 -14
- package/dist/src/app/article.d.ts +2 -1
- package/dist/src/app/article.js +66 -70
- package/dist/src/app/bundle-files.d.ts +0 -4
- package/dist/src/app/bundle-files.js +11 -14
- package/dist/src/app/markdown.js +18 -6
- package/dist/src/app/source.d.ts +0 -4
- package/dist/src/app/source.js +18 -14
- package/dist/src/cli/arguments.d.ts +2 -8
- package/dist/src/cli/arguments.js +67 -80
- package/dist/src/cli/commands.d.ts +42 -0
- package/dist/src/cli/commands.js +795 -0
- package/dist/src/cli/config.js +12 -9
- package/dist/src/cli/digest.d.ts +1 -1
- package/dist/src/cli/digest.js +39 -17
- package/dist/src/cli/import.js +25 -4
- package/dist/src/cli/indexing.d.ts +2 -0
- package/dist/src/cli/indexing.js +26 -9
- package/dist/src/cli/main.js +47 -386
- package/dist/src/cli/portability.d.ts +0 -2
- package/dist/src/cli/portability.js +13 -8
- package/dist/src/cli/queue.js +12 -8
- package/dist/src/cli/runtimes.d.ts +1 -3
- package/dist/src/cli/runtimes.js +13 -6
- package/dist/src/cli/terminal.js +1 -1
- package/dist/src/cli/worker.d.ts +7 -0
- package/dist/src/cli/worker.js +54 -15
- package/dist/src/derivations/kinds.d.ts +34 -0
- package/dist/src/derivations/kinds.js +14 -0
- package/dist/src/derivations/types.d.ts +48 -0
- package/dist/src/derivations/types.js +1 -0
- package/dist/src/digest/derivation.d.ts +25 -0
- package/dist/src/digest/derivation.js +124 -0
- package/dist/src/digest/editorial.d.ts +10 -46
- package/dist/src/digest/editorial.js +73 -59
- package/dist/src/digest/endpoint.js +63 -27
- package/dist/src/digest/pipeline.d.ts +53 -32
- package/dist/src/digest/pipeline.js +357 -125
- package/dist/src/digest/schema.d.ts +44 -17
- package/dist/src/digest/schema.js +132 -44
- package/dist/src/errors.d.ts +46 -0
- package/dist/src/errors.js +117 -0
- package/dist/src/identity/query-policy.d.ts +0 -5
- package/dist/src/identity/query-policy.js +61 -9
- package/dist/src/identity/uri.d.ts +6 -5
- package/dist/src/identity/uri.js +37 -18
- package/dist/src/identity/visibility.d.ts +11 -1
- package/dist/src/identity/visibility.js +81 -17
- package/dist/src/importers/apply.js +104 -36
- package/dist/src/importers/custody.js +27 -9
- package/dist/src/importers/inspect.js +125 -38
- package/dist/src/importers/jungle-digest.d.ts +31 -24
- package/dist/src/importers/jungle-digest.js +217 -115
- package/dist/src/importers/jungle-store.d.ts +20 -11
- package/dist/src/importers/jungle-store.js +136 -41
- package/dist/src/importers/jungle.d.ts +32 -16
- package/dist/src/importers/jungle.js +145 -32
- package/dist/src/importers/manifest.js +57 -21
- package/dist/src/importers/verify-helper.js +18 -4
- package/dist/src/indexing/image.js +29 -10
- package/dist/src/indexing/index.d.ts +37 -6
- package/dist/src/indexing/index.js +360 -124
- package/dist/src/indexing/jobs.js +156 -47
- package/dist/src/indexing/prune-run.d.ts +2 -0
- package/dist/src/indexing/prune-run.js +69 -26
- package/dist/src/indexing/prune.d.ts +12 -3
- package/dist/src/indexing/prune.js +85 -35
- package/dist/src/indexing/runner.d.ts +2 -1
- package/dist/src/indexing/runner.js +83 -40
- package/dist/src/indexing/upgrade.d.ts +9 -0
- package/dist/src/indexing/upgrade.js +79 -0
- package/dist/src/inference/asr/bridge.js +6 -2
- package/dist/src/inference/asr/protocol.js +109 -19
- package/dist/src/inference/asr/runtime.js +58 -21
- package/dist/src/inference/asr/windows.js +9 -5
- package/dist/src/inference/embedding/index.d.ts +2 -6
- package/dist/src/inference/embedding/index.js +89 -29
- package/dist/src/ingest/attempt.d.ts +29 -0
- package/dist/src/ingest/attempt.js +55 -0
- package/dist/src/media/capture-helper.js +7 -2
- package/dist/src/media/capture.d.ts +2 -1
- package/dist/src/media/capture.js +124 -43
- package/dist/src/media/handler.d.ts +4 -2
- package/dist/src/media/handler.js +88 -70
- package/dist/src/media/transcribe.js +63 -21
- package/dist/src/metadata/article.js +49 -17
- package/dist/src/models/artifacts.d.ts +10 -8
- package/dist/src/models/artifacts.js +237 -106
- package/dist/src/models/catalog.d.ts +2 -5
- package/dist/src/models/catalog.js +94 -25
- package/dist/src/models/download.js +55 -15
- package/dist/src/models/runtime.d.ts +0 -2
- package/dist/src/models/runtime.js +69 -33
- package/dist/src/pdf/capture-helper.js +3 -1
- package/dist/src/pdf/capture.d.ts +2 -1
- package/dist/src/pdf/capture.js +95 -27
- package/dist/src/pdf/errors.js +12 -1
- package/dist/src/pdf/handler.d.ts +2 -1
- package/dist/src/pdf/handler.js +70 -71
- package/dist/src/pdf/index.d.ts +1 -3
- package/dist/src/pdf/index.js +0 -2
- package/dist/src/pdf/input.js +12 -7
- package/dist/src/pdf/normalize.js +80 -21
- package/dist/src/pdf/source-identity.js +3 -2
- package/dist/src/pdf/source.js +12 -8
- package/dist/src/pdf/verify-helper.js +15 -3
- package/dist/src/portability/backup.d.ts +2 -9
- package/dist/src/portability/backup.js +108 -22
- package/dist/src/portability/database.js +51 -17
- package/dist/src/portability/export.js +35 -9
- package/dist/src/portability/files.d.ts +1 -4
- package/dist/src/portability/files.js +127 -67
- package/dist/src/portability/gc.js +65 -22
- package/dist/src/publish/component.js +1 -1
- package/dist/src/publish/feed.d.ts +2 -2
- package/dist/src/publish/feed.js +75 -29
- package/dist/src/publish/identity.d.ts +5 -1
- package/dist/src/publish/identity.js +31 -11
- package/dist/src/publish/index.d.ts +2 -3
- package/dist/src/publish/index.js +34 -21
- package/dist/src/publish/projection.d.ts +2 -9
- package/dist/src/publish/projection.js +21 -14
- package/dist/src/search/extension.js +3 -1
- package/dist/src/search/hybrid.d.ts +3 -8
- package/dist/src/search/hybrid.js +45 -11
- package/dist/src/search/lexical.js +18 -4
- package/dist/src/search/query.js +56 -25
- package/dist/src/search/vector.js +37 -10
- package/dist/src/service/launch-agent.d.ts +5 -3
- package/dist/src/service/launch-agent.js +272 -83
- package/dist/src/service/lifetime.js +47 -14
- package/dist/src/service/loop.d.ts +3 -3
- package/dist/src/service/loop.js +47 -19
- package/dist/src/store/annotations.d.ts +32 -0
- package/dist/src/store/annotations.js +44 -0
- package/dist/src/store/backups.d.ts +18 -0
- package/dist/src/store/backups.js +110 -0
- package/dist/src/store/blobs.d.ts +24 -0
- package/dist/src/store/blobs.js +139 -0
- package/dist/src/store/changes.d.ts +5 -12
- package/dist/src/store/changes.js +108 -56
- package/dist/src/store/context.d.ts +15 -0
- package/dist/src/store/context.js +19 -0
- package/dist/src/store/derivations.d.ts +42 -0
- package/dist/src/store/derivations.js +104 -0
- package/dist/src/store/files.d.ts +0 -2
- package/dist/src/store/files.js +19 -24
- package/dist/src/store/ids.d.ts +30 -0
- package/dist/src/store/ids.js +34 -0
- package/dist/src/store/imports.d.ts +26 -0
- package/dist/src/store/imports.js +57 -0
- package/dist/src/store/index.d.ts +3 -186
- package/dist/src/store/index.js +2 -809
- package/dist/src/store/lease.d.ts +20 -0
- package/dist/src/store/lease.js +62 -0
- package/dist/src/store/library.d.ts +75 -0
- package/dist/src/store/library.js +174 -0
- package/dist/src/store/open.d.ts +7 -0
- package/dist/src/store/open.js +183 -0
- package/dist/src/store/queue.d.ts +65 -0
- package/dist/src/store/queue.js +327 -0
- package/dist/src/store/revisions.d.ts +12 -0
- package/dist/src/store/revisions.js +189 -0
- package/dist/src/store/search-index.d.ts +63 -0
- package/dist/src/store/search-index.js +190 -0
- package/dist/src/store/sync-ready.d.ts +6 -0
- package/dist/src/store/sync-ready.js +121 -0
- package/dist/src/store/types.d.ts +1 -5
- package/dist/src/store/types.js +1 -8
- package/dist/src/util/canonical.d.ts +2 -0
- package/dist/src/util/canonical.js +17 -0
- package/dist/src/util/exact.d.ts +2 -0
- package/dist/src/util/exact.js +9 -0
- package/dist/src/util/fsync.d.ts +4 -0
- package/dist/src/util/fsync.js +22 -0
- package/dist/src/util/hash.d.ts +4 -0
- package/dist/src/util/hash.js +8 -0
- package/dist/src/worker/basic.d.ts +0 -1
- package/dist/src/worker/basic.js +23 -7
- package/dist/src/worker/custody.d.ts +4 -1
- package/dist/src/worker/custody.js +111 -38
- package/dist/src/worker/engine.d.ts +2 -1
- package/dist/src/worker/engine.js +42 -20
- package/dist/src/worker/identity.js +13 -5
- package/dist/src/worker/journal.d.ts +1 -1
- package/dist/src/worker/journal.js +54 -24
- package/dist/src/worker/lock.d.ts +18 -0
- package/dist/src/worker/lock.js +196 -0
- package/dist/src/worker/runtime-gc.d.ts +4 -3
- package/dist/src/worker/runtime-gc.js +68 -41
- package/dist/src/worker/supervisor-child.js +19 -6
- package/dist/src/worker/supervisor.js +41 -8
- package/native/runtime-manifest.json +4 -4
- package/package.json +10 -2
- package/dist/src/digest/store.d.ts +0 -57
- package/dist/src/digest/store.js +0 -77
- package/dist/src/store/foundation.d.ts +0 -7
- package/dist/src/store/foundation.js +0 -7
|
@@ -2,33 +2,51 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { rm } from 'node:fs/promises';
|
|
4
4
|
import { acquireWorkerCustody, proveAttemptCleanup } from '../worker/custody.js';
|
|
5
|
-
import { directory, writeJournal, readJournal
|
|
5
|
+
import { directory, writeJournal, readJournal } from '../worker/journal.js';
|
|
6
|
+
import { syncDirectoryAsync } from '../util/fsync.js';
|
|
6
7
|
import { supervisedRunner } from '../worker/supervisor.js';
|
|
7
|
-
|
|
8
|
+
import { SpongeError, codeOf } from '../errors.js';
|
|
9
|
+
const pending = () => new SpongeError('cleanup-required');
|
|
8
10
|
/** Read-only verification still owns subprocesses, including during an import preview. */
|
|
9
11
|
export async function withImportVerification(libraryRoot, verify) {
|
|
10
12
|
const custody = await acquireWorkerCustody(libraryRoot), attemptId = randomUUID();
|
|
11
13
|
let scratchRoot, unknown = false;
|
|
12
14
|
try {
|
|
13
15
|
const attempt = await directory(join(custody.directory, 'attempts', attemptId));
|
|
14
|
-
await writeJournal(join(attempt, 'import-verification.json'), {
|
|
16
|
+
await writeJournal(join(attempt, 'import-verification.json'), {
|
|
17
|
+
schemaVersion: 1,
|
|
18
|
+
kind: 'import-verification',
|
|
19
|
+
attemptId,
|
|
20
|
+
ownerIncarnation: custody.incarnation,
|
|
21
|
+
});
|
|
15
22
|
scratchRoot = await directory(join(libraryRoot, 'staging', 'import-verification-' + attemptId));
|
|
16
|
-
await writeJournal(join(scratchRoot, 'ownership.json'), {
|
|
17
|
-
|
|
23
|
+
await writeJournal(join(scratchRoot, 'ownership.json'), {
|
|
24
|
+
schemaVersion: 1,
|
|
25
|
+
kind: 'import-verification',
|
|
26
|
+
requestId: attemptId,
|
|
27
|
+
ownerIncarnation: custody.incarnation,
|
|
28
|
+
});
|
|
29
|
+
return await verify({
|
|
30
|
+
scratchRoot,
|
|
31
|
+
runProcess: supervisedRunner(custody.directory, custody.incarnation, attemptId),
|
|
32
|
+
});
|
|
18
33
|
}
|
|
19
34
|
catch (error) {
|
|
20
|
-
unknown =
|
|
35
|
+
unknown = codeOf(error) === 'cleanup-required';
|
|
21
36
|
throw error;
|
|
22
37
|
}
|
|
23
38
|
finally {
|
|
24
|
-
if (unknown || !await proveAttemptCleanup(custody.directory, custody.incarnation, attemptId))
|
|
39
|
+
if (unknown || !(await proveAttemptCleanup(custody.directory, custody.incarnation, attemptId)))
|
|
25
40
|
throw pending();
|
|
26
41
|
if (scratchRoot) {
|
|
27
42
|
const marker = await readJournal(join(scratchRoot, 'ownership.json'));
|
|
28
|
-
if (marker.schemaVersion !== 1 ||
|
|
43
|
+
if (marker.schemaVersion !== 1 ||
|
|
44
|
+
marker.kind !== 'import-verification' ||
|
|
45
|
+
marker.requestId !== attemptId ||
|
|
46
|
+
marker.ownerIncarnation !== custody.incarnation)
|
|
29
47
|
throw pending();
|
|
30
48
|
await rm(scratchRoot, { recursive: true });
|
|
31
|
-
await
|
|
49
|
+
await syncDirectoryAsync(join(libraryRoot, 'staging'));
|
|
32
50
|
}
|
|
33
51
|
await custody.release();
|
|
34
52
|
}
|
|
@@ -3,84 +3,153 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { limits } from '../../contracts/index.js';
|
|
4
4
|
import { validateAsset } from '../../contracts/validate.js';
|
|
5
5
|
import { object, metadataFromMarkdown } from '../acquisition/normalize.js';
|
|
6
|
-
import { readConfined
|
|
6
|
+
import { readConfined } from '../acquisition/files.js';
|
|
7
|
+
import { sha256 as hash } from '../util/hash.js';
|
|
7
8
|
import { withImportVerification } from './custody.js';
|
|
8
9
|
import { inspectCaptureRuntime } from '../acquisition/runtime.js';
|
|
9
10
|
import { articleMetadata } from '../metadata/article.js';
|
|
10
11
|
import { bundleBytes } from '../app/bundle-files.js';
|
|
12
|
+
import { SpongeError } from '../errors.js';
|
|
11
13
|
export function releasedBundleVerifier(runtime, libraryRoot, signal) {
|
|
12
14
|
return async (root, kind) => {
|
|
13
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));
|
|
14
16
|
return withImportVerification(libraryRoot, async ({ scratchRoot, runProcess }) => {
|
|
15
17
|
const args = [helper, checked.root, root, kind];
|
|
16
|
-
const result = await runProcess(process.platform === 'darwin' ? '/usr/bin/sandbox-exec' : checked.bun, process.platform === 'darwin'
|
|
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
|
+
});
|
|
17
26
|
if (result.cleanup !== 'settled')
|
|
18
|
-
throw
|
|
27
|
+
throw new SpongeError('cleanup-required');
|
|
19
28
|
if (result.stopped)
|
|
20
|
-
throw
|
|
29
|
+
throw new SpongeError(result.stopped);
|
|
21
30
|
if (result.code !== 0)
|
|
22
|
-
throw
|
|
31
|
+
throw new SpongeError('unsupported');
|
|
23
32
|
return JSON.parse(result.stdout);
|
|
24
33
|
});
|
|
25
34
|
};
|
|
26
35
|
}
|
|
27
|
-
function imageType(bytes) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
function imageType(bytes) {
|
|
37
|
+
if (bytes[0] === 255 && bytes[1] === 216)
|
|
38
|
+
return 'image/jpeg';
|
|
39
|
+
if (bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])))
|
|
40
|
+
return 'image/png';
|
|
41
|
+
if (bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WEBP')
|
|
42
|
+
return 'image/webp';
|
|
43
|
+
if (/^GIF8[79]a/.test(bytes.toString('ascii', 0, 6)))
|
|
44
|
+
return 'image/gif';
|
|
45
|
+
if (bytes.toString('ascii', 4, 12) === 'ftypavif')
|
|
46
|
+
return 'image/avif';
|
|
47
|
+
throw new SpongeError('unsupported');
|
|
48
|
+
}
|
|
33
49
|
/** An import-time digest never upgrades a missing historical document digest. */
|
|
34
50
|
export async function inspectImportBundle(root, verify) {
|
|
51
|
+
// A bundle and its verifier report are untrusted: malformed JSON, a listed file that is missing, a bad URL or asset record is an integrity failure.
|
|
52
|
+
try {
|
|
53
|
+
return await inspectBundle(root, verify);
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
if (error instanceof SpongeError)
|
|
57
|
+
throw error;
|
|
58
|
+
throw new SpongeError('integrity', { cause: error });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function inspectBundle(root, verify) {
|
|
35
62
|
const manifestBytes = await readConfined(root, 'capture.json', limits.metadataBytes), manifest = object(JSON.parse(manifestBytes.toString('utf8'))), kind = manifest.kind === 'pdf' ? 'pdf' : 'article';
|
|
36
63
|
if (kind === 'pdf' ? manifest.schemaVersion !== 1 : ![1, 2, 3, 4].includes(Number(manifest.schemaVersion)))
|
|
37
|
-
throw
|
|
64
|
+
throw new SpongeError('unsupported');
|
|
38
65
|
const result = object(await verify(root, kind)), inspection = object(result.inspection), document = object(kind === 'pdf' ? inspection.markdown : inspection.document);
|
|
39
|
-
if (inspection.schemaVersion !== manifest.schemaVersion ||
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
if (inspection.schemaVersion !== manifest.schemaVersion ||
|
|
67
|
+
!['verified', 'unavailable'].includes(String(document.integrity)) ||
|
|
68
|
+
!Array.isArray(inspection.assets) ||
|
|
69
|
+
inspection.assets.length > limits.imagesPerItem)
|
|
70
|
+
throw new SpongeError('integrity');
|
|
71
|
+
if (result.ok !== true &&
|
|
72
|
+
(!Array.isArray(result.issues) ||
|
|
73
|
+
!result.issues.length ||
|
|
74
|
+
result.issues.some((raw) => {
|
|
75
|
+
const issue = object(raw);
|
|
76
|
+
return !(issue.kind === 'document-integrity' &&
|
|
77
|
+
issue.path === document.path &&
|
|
78
|
+
document.integrity === 'unavailable' &&
|
|
79
|
+
(kind === 'article' || issue.integrity === 'unavailable'));
|
|
80
|
+
})))
|
|
81
|
+
throw new SpongeError('integrity');
|
|
82
|
+
if (inspection.status !== undefined &&
|
|
83
|
+
inspection.status !== null &&
|
|
84
|
+
!['complete', 'partial'].includes(String(inspection.status)))
|
|
85
|
+
throw new SpongeError('unsupported');
|
|
86
|
+
const markdownBytes = await readConfined(root, String(document.path), limits.markdownBytes), markdown = {
|
|
87
|
+
relativePath: String(document.path),
|
|
88
|
+
bytes: markdownBytes.length,
|
|
89
|
+
sha256: hash(markdownBytes),
|
|
90
|
+
mediaType: 'text/markdown',
|
|
91
|
+
};
|
|
46
92
|
validateAsset(markdown);
|
|
47
93
|
if (markdown.sha256 !== document.sha256 || markdown.bytes !== document.bytes)
|
|
48
|
-
throw
|
|
94
|
+
throw new SpongeError('integrity');
|
|
49
95
|
let unavailable = document.integrity === 'unavailable';
|
|
50
96
|
const assets = [];
|
|
51
97
|
let total = 0;
|
|
52
98
|
for (const raw of inspection.assets) {
|
|
53
99
|
const asset = object(raw);
|
|
54
100
|
if (!['verified', 'unavailable'].includes(String(asset.integrity)))
|
|
55
|
-
throw
|
|
101
|
+
throw new SpongeError('integrity');
|
|
56
102
|
unavailable ||= asset.integrity === 'unavailable';
|
|
57
103
|
const bytes = await readConfined(root, String(asset.path), limits.imageBytes);
|
|
58
104
|
total += bytes.length;
|
|
59
105
|
if (total > 500 * 1024 ** 2)
|
|
60
|
-
throw
|
|
61
|
-
const value = {
|
|
106
|
+
throw new SpongeError('limit-exceeded');
|
|
107
|
+
const value = {
|
|
108
|
+
relativePath: String(asset.path),
|
|
109
|
+
bytes: bytes.length,
|
|
110
|
+
sha256: hash(bytes),
|
|
111
|
+
mediaType: imageType(bytes),
|
|
112
|
+
relationship: 'image',
|
|
113
|
+
};
|
|
62
114
|
validateAsset(value);
|
|
63
115
|
const expectedHash = kind === 'pdf' ? asset.sha256 : asset.expectedSha256, expectedBytes = kind === 'pdf' ? asset.bytes : asset.expectedBytes;
|
|
64
116
|
if (asset.integrity === 'verified' && (expectedHash !== value.sha256 || expectedBytes !== value.bytes))
|
|
65
|
-
throw
|
|
117
|
+
throw new SpongeError('integrity');
|
|
66
118
|
assets.push(value);
|
|
67
119
|
}
|
|
68
120
|
if (kind === 'pdf') {
|
|
69
121
|
const source = object(inspection.source);
|
|
70
122
|
if (source.integrity !== 'verified')
|
|
71
|
-
throw
|
|
72
|
-
const asset = {
|
|
123
|
+
throw new SpongeError('integrity');
|
|
124
|
+
const asset = {
|
|
125
|
+
relativePath: String(source.path),
|
|
126
|
+
sha256: String(source.sha256),
|
|
127
|
+
bytes: Number(source.bytes),
|
|
128
|
+
mediaType: 'application/pdf',
|
|
129
|
+
relationship: 'source-pdf',
|
|
130
|
+
};
|
|
73
131
|
validateAsset(asset);
|
|
74
|
-
for await (const _ of bundleBytes(root, asset)) {
|
|
132
|
+
for await (const _ of bundleBytes(root, asset)) {
|
|
133
|
+
}
|
|
75
134
|
assets.push(asset);
|
|
76
135
|
}
|
|
77
|
-
if (assets.length !== new Set(assets.map(a => a.relativePath)).size)
|
|
78
|
-
throw
|
|
79
|
-
const sourceUrl = kind === 'pdf'
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
136
|
+
if (assets.length !== new Set(assets.map((a) => a.relativePath)).size)
|
|
137
|
+
throw new SpongeError('integrity');
|
|
138
|
+
const sourceUrl = kind === 'pdf'
|
|
139
|
+
? (object(manifest.source).requestedUrl ?? pathToFileURL(join(root, String(object(inspection.source).path))).href)
|
|
140
|
+
: inspection.sourceUrl;
|
|
141
|
+
if (typeof sourceUrl !== 'string' ||
|
|
142
|
+
sourceUrl.length > 16384 ||
|
|
143
|
+
!['http:', 'https:', 'file:'].includes(new URL(sourceUrl).protocol))
|
|
144
|
+
throw new SpongeError('integrity');
|
|
145
|
+
const observedAt = typeof manifest.capturedAt === 'string' && Number.isFinite(Date.parse(manifest.capturedAt))
|
|
146
|
+
? manifest.capturedAt
|
|
147
|
+
: '1970-01-01T00:00:00.000Z';
|
|
148
|
+
const metadata = kind === 'article'
|
|
149
|
+
? manifest.metadata === undefined
|
|
150
|
+
? metadataFromMarkdown(markdownBytes.toString('utf8'), observedAt)
|
|
151
|
+
: articleMetadata(manifest.metadata, observedAt)
|
|
152
|
+
: [];
|
|
84
153
|
if (kind === 'pdf') {
|
|
85
154
|
const document = object(manifest.document ?? {});
|
|
86
155
|
for (const field of ['title', 'author', 'subject', 'creator', 'producer', 'createdAt', 'modifiedAt']) {
|
|
@@ -89,9 +158,27 @@ export async function inspectImportBundle(root, verify) {
|
|
|
89
158
|
metadata.push({ field, value, source: 'pdf', observedAt, rule: 'wordcell-pdf-v1', selected: true });
|
|
90
159
|
}
|
|
91
160
|
}
|
|
92
|
-
metadata.push({
|
|
161
|
+
metadata.push({
|
|
162
|
+
field: 'historicalIntegrity',
|
|
163
|
+
value: unavailable ? 'unavailable' : 'verified',
|
|
164
|
+
source: kind === 'pdf' ? 'pdf' : 'html',
|
|
165
|
+
observedAt,
|
|
166
|
+
rule: 'sponge-import-v1',
|
|
167
|
+
});
|
|
93
168
|
if (hash(await readConfined(root, 'capture.json', limits.metadataBytes)) !== hash(manifestBytes))
|
|
94
|
-
throw
|
|
95
|
-
const manifestSha256 = hash(manifestBytes), importHash = hash(JSON.stringify([manifestSha256, markdown.sha256, ...assets.map(a => [a.relativePath, a.sha256, a.bytes])]));
|
|
96
|
-
return {
|
|
169
|
+
throw new SpongeError('integrity');
|
|
170
|
+
const manifestSha256 = hash(manifestBytes), importHash = hash(JSON.stringify([manifestSha256, markdown.sha256, ...assets.map((a) => [a.relativePath, a.sha256, a.bytes])]));
|
|
171
|
+
return {
|
|
172
|
+
root,
|
|
173
|
+
kind,
|
|
174
|
+
sourceUrl,
|
|
175
|
+
schemaVersion: Number(manifest.schemaVersion),
|
|
176
|
+
status: unavailable || inspection.status !== 'complete' ? 'partial' : 'complete',
|
|
177
|
+
markdown,
|
|
178
|
+
assets,
|
|
179
|
+
metadata,
|
|
180
|
+
manifestSha256,
|
|
181
|
+
historicalIntegrity: unavailable ? 'unavailable' : 'verified',
|
|
182
|
+
importHash,
|
|
183
|
+
};
|
|
97
184
|
}
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import { type DigestIssue, type
|
|
1
|
+
import { type DigestIssue, type DigestV2 } from '../digest/schema.js';
|
|
2
2
|
import { type Form, type QuoteMatch } from '../digest/pipeline.js';
|
|
3
3
|
/**
|
|
4
|
-
* Pure conversion of one reviewed Jungle Reading digest note (`kb/notes/reading/<slug>.md`) into Digest
|
|
5
|
-
* quotes in their own section, so each quote is assigned to the idea it shares the most content words with.
|
|
4
|
+
* Pure conversion of one reviewed Jungle Reading digest note (`kb/notes/reading/<slug>.md`) into Digest v2. Jungle keeps
|
|
5
|
+
* quotes in their own section, so each verbatim quote is assigned to the idea it shares the most content words with.
|
|
6
|
+
* Owner text is never cut or rewritten: a note that does not fit v2 as written is reported, not converted.
|
|
6
7
|
* Nothing here reads files; callers pass the note and, when available, its capture Markdown.
|
|
7
8
|
*/
|
|
8
9
|
export declare const jungleTags: readonly ["agent-eng", "ai", "work", "business", "creative-coding", "finance", "music", "internet", "security", "history", "art", "hardware", "climate", "article", "interactive", "code", "x", "video"];
|
|
9
|
-
/** Tags that name how a source is consumed;
|
|
10
|
+
/** Tags that name how a source is consumed; a digest carries that as `form`. */
|
|
10
11
|
export declare const jungleFormatTags: readonly ["article", "interactive", "x", "video"];
|
|
11
|
-
|
|
12
|
-
readonly code: 'not-digest' | 'malformed' | 'no-capture';
|
|
13
|
-
constructor(code: 'not-digest' | 'malformed' | 'no-capture');
|
|
14
|
-
}
|
|
15
|
-
export interface JungleQuote {
|
|
12
|
+
interface JungleQuote {
|
|
16
13
|
text: string;
|
|
17
14
|
attribution: string;
|
|
18
15
|
speaker: string;
|
|
@@ -32,25 +29,27 @@ export interface JungleNote {
|
|
|
32
29
|
}[];
|
|
33
30
|
quotes: JungleQuote[];
|
|
34
31
|
}
|
|
35
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Why a Jungle quote is left out of the digest: not found as written in the capture (`snapped` matches differ in markup),
|
|
34
|
+
* under four words, over the quote budget (count, words or source share), or every idea already holds two.
|
|
35
|
+
*/
|
|
36
|
+
export type QuoteDrop = 'not-verbatim' | 'too-short' | 'budget' | 'overflow';
|
|
36
37
|
export interface JungleConversion {
|
|
37
38
|
note: JungleNote;
|
|
38
|
-
/**
|
|
39
|
-
gold:
|
|
40
|
-
/** Per Jungle quote: the
|
|
39
|
+
/** The note as Digest v2: the Gist's first sentence as lead and the rest as context, every idea, and the quotes that survive. */
|
|
40
|
+
gold: DigestV2;
|
|
41
|
+
/** Per Jungle quote, in note order: the idea it went to (null when dropped), how it located in the capture, and why it was dropped. */
|
|
41
42
|
quotes: {
|
|
42
43
|
idea: number | null;
|
|
43
44
|
match?: QuoteMatch;
|
|
45
|
+
dropped?: QuoteDrop;
|
|
44
46
|
}[];
|
|
45
|
-
|
|
46
|
-
/** checkDigest issues of the gold against the capture; empty without a capture. */
|
|
47
|
+
/** Why the gold is not a valid Digest v2: shape issues (an empty detail), then checkDigest issues against the capture when one is given. */
|
|
47
48
|
issues: DigestIssue[];
|
|
48
|
-
/** The gold
|
|
49
|
-
|
|
50
|
-
/** Gold passes every v1 rule against the capture with nothing dropped or trimmed. `kind` is never gold: Jungle has no kinds. */
|
|
51
|
-
clean: boolean;
|
|
49
|
+
/** The gold, when a capture was given and the gold passes every v2 rule against it. */
|
|
50
|
+
digest?: DigestV2;
|
|
52
51
|
}
|
|
53
|
-
|
|
52
|
+
type Yaml = string | boolean | Yaml[] | {
|
|
54
53
|
[k: string]: Yaml;
|
|
55
54
|
};
|
|
56
55
|
/** Capture Markdown without its YAML frontmatter: what a Sponge revision of the same page would hold. */
|
|
@@ -58,7 +57,7 @@ export declare function captureBody(capture: string): string;
|
|
|
58
57
|
export declare function parseJungleNote(markdown: string): JungleNote;
|
|
59
58
|
/**
|
|
60
59
|
* One Jungle Reading note of any form (digest, social, note, conversation), kept whole: the exact Markdown and its hash,
|
|
61
|
-
* the parsed frontmatter, and the gist, ideas and quotes where the form has them. Nothing is trimmed to
|
|
60
|
+
* the parsed frontmatter, and the gist, ideas and quotes where the form has them. Nothing is trimmed to digest limits.
|
|
62
61
|
* `capture` is the evidence the note is about: the first `synthesizes` relation, else the first `evidenced-by`.
|
|
63
62
|
*/
|
|
64
63
|
export interface JungleEditorial {
|
|
@@ -84,14 +83,22 @@ export declare function parseJungleEditorial(markdown: string): JungleEditorial;
|
|
|
84
83
|
/** Content-word stems: lowercase, stopwords out, a crude plural/tense strip. Shared by idea assignment and lexical coverage. */
|
|
85
84
|
export declare function contentTerms(text: string): Set<string>;
|
|
86
85
|
export declare function termSimilarity(a: Set<string>, b: Set<string>): number;
|
|
87
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Each quote in order goes to the idea sharing the most content words with its claim and detail (ties: the earliest idea),
|
|
88
|
+
* else to the next best idea with room; null when every idea already holds `cap` quotes.
|
|
89
|
+
*/
|
|
88
90
|
export declare function assignQuotes(ideas: {
|
|
89
91
|
claim: string;
|
|
90
92
|
detail: string;
|
|
91
93
|
}[], quotes: {
|
|
92
94
|
text: string;
|
|
93
|
-
}[]): (number | null)[];
|
|
95
|
+
}[], cap?: number): (number | null)[];
|
|
94
96
|
export declare const firstSentence: (text: string) => readonly [string, string];
|
|
95
97
|
export declare function formOf(note: JungleNote): Form;
|
|
96
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Convert one Jungle digest note. With `capture` (the capture Markdown, frontmatter allowed), each quote is kept only when it
|
|
100
|
+
* occurs as written (folded typography only) and has at least four words; whole quotes then leave, longest and then latest in
|
|
101
|
+
* the note first, until the rest fit the quote budget (never the last one); survivors are assigned to ideas, and the gold is checked.
|
|
102
|
+
*/
|
|
97
103
|
export declare function convertJungleDigest(noteMarkdown: string, capture?: string): JungleConversion;
|
|
104
|
+
export {};
|