@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.
- package/README.md +21 -9
- 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 +38 -16
- 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 +9 -46
- package/dist/src/digest/editorial.js +57 -49
- package/dist/src/digest/endpoint.js +63 -27
- package/dist/src/digest/pipeline.d.ts +49 -28
- package/dist/src/digest/pipeline.js +343 -100
- package/dist/src/digest/schema.d.ts +11 -25
- package/dist/src/digest/schema.js +86 -18
- 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 +3 -6
- package/dist/src/importers/jungle-digest.js +196 -69
- package/dist/src/importers/jungle-store.d.ts +15 -6
- package/dist/src/importers/jungle-store.js +122 -28
- package/dist/src/importers/jungle.d.ts +14 -14
- package/dist/src/importers/jungle.js +136 -31
- 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 +106 -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 -79
- package/dist/src/store/foundation.d.ts +0 -7
- package/dist/src/store/foundation.js +0 -7
|
@@ -1,82 +1,100 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
2
|
+
import { runAttempt, classifyErrors } from '../ingest/attempt.js';
|
|
3
|
+
import { sha256 } from '../util/hash.js';
|
|
4
4
|
import { bundleBytes } from '../app/bundle-files.js';
|
|
5
5
|
import { runOwned } from '../acquisition/process.js';
|
|
6
6
|
import { captureMedia } from './capture.js';
|
|
7
7
|
import { transcribeMedia } from './transcribe.js';
|
|
8
|
+
import { SpongeError } from '../errors.js';
|
|
9
|
+
const mediaCode = classifyErrors([
|
|
10
|
+
'cleanup-required',
|
|
11
|
+
'integrity',
|
|
12
|
+
'invalid-input',
|
|
13
|
+
'limit-exceeded',
|
|
14
|
+
'cancelled',
|
|
15
|
+
'owner-busy',
|
|
16
|
+
]);
|
|
8
17
|
/** Only a transcript and thumbnail enter durable storage; acquisition owns all source media. */
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
export function ingestMedia(store, token, options) {
|
|
19
|
+
// An uncertain native process may still be reading source bytes: runAttempt then preserves ownership.
|
|
20
|
+
return runAttempt(store, token, (error, attempt) => {
|
|
21
|
+
const code = mediaCode(error);
|
|
22
|
+
if (code === 'cleanup-required')
|
|
23
|
+
attempt.cleanup = 'pending';
|
|
24
|
+
return code;
|
|
25
|
+
}, async (attempt) => {
|
|
26
|
+
const source = store.queue.source(token.jobId);
|
|
14
27
|
if (!source || source.kind !== 'media')
|
|
15
|
-
throw
|
|
16
|
-
const result = await (options.capture ?? captureMedia)({
|
|
28
|
+
throw new SpongeError('invalid-input');
|
|
29
|
+
const result = await (options.capture ?? captureMedia)({
|
|
30
|
+
...options,
|
|
31
|
+
stagingRoot: join(store.root, 'staging'),
|
|
32
|
+
requestId: token.attemptId,
|
|
33
|
+
sourceUrl: source.locator,
|
|
34
|
+
});
|
|
17
35
|
if (!result.ok) {
|
|
18
|
-
|
|
19
|
-
|
|
36
|
+
if (result.code === 'cleanup-required')
|
|
37
|
+
attempt.cleanup = 'pending';
|
|
38
|
+
return result;
|
|
20
39
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const revision = store.commitRevision({ ...token, itemId: source.itemId, bundleHash: createHash('sha256').update(JSON.stringify(['media-transcript-v1', result.manifestSha256, transcriptHash])).digest('hex'), markdown: transcription.markdown, title: result.title, extractor: { name: 'sponge-media', version: '1' }, status, metadata, assets, warnings: result.warnings.length ? ['partial'] : [] });
|
|
47
|
-
committed = true;
|
|
48
|
-
outcome = { ok: true, itemId: source.itemId, revisionId: revision.revisionId, status };
|
|
49
|
-
}
|
|
40
|
+
attempt.hold(result);
|
|
41
|
+
await options.checkpoint?.('extracting');
|
|
42
|
+
const transcription = await (options.transcribe ?? transcribeMedia)({
|
|
43
|
+
requestId: token.attemptId,
|
|
44
|
+
audio: result.audio,
|
|
45
|
+
captions: result.captions,
|
|
46
|
+
language: options.language,
|
|
47
|
+
forceLocal: options.forceLocal,
|
|
48
|
+
}, {
|
|
49
|
+
scratchRoot: result.root,
|
|
50
|
+
runtime: options.asrRuntime,
|
|
51
|
+
signal: options.signal,
|
|
52
|
+
runProcess: options.runProcess ?? runOwned,
|
|
53
|
+
});
|
|
54
|
+
attempt.cleanup = transcription.cleanup;
|
|
55
|
+
if (!transcription.ok)
|
|
56
|
+
return transcription;
|
|
57
|
+
const transcriptBytes = Buffer.from(JSON.stringify(transcription.transcript)), transcriptHash = sha256(transcriptBytes);
|
|
58
|
+
const transcriptBlob = await store.blobs.put(token, transcriptHash, transcriptBytes.length, (async function* () {
|
|
59
|
+
yield transcriptBytes;
|
|
60
|
+
})());
|
|
61
|
+
const assets = [{ ...transcriptBlob, mediaType: 'application/json', relationship: 'transcript' }];
|
|
62
|
+
if (result.thumbnail) {
|
|
63
|
+
const blob = await store.blobs.put(token, result.thumbnail.sha256, result.thumbnail.bytes, bundleBytes(result.root, result.thumbnail));
|
|
64
|
+
assets.push({ ...blob, mediaType: result.thumbnail.mediaType, relationship: 'thumbnail' });
|
|
50
65
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (raw === 'cleanup-required')
|
|
55
|
-
cleanup = 'pending';
|
|
56
|
-
const code = ['cleanup-required', 'integrity', 'invalid-input', 'limit-exceeded', 'cancelled', 'owner-busy'].includes(raw) ? raw : 'unclassified';
|
|
57
|
-
outcome = failure(token.attemptId, code, 'storage');
|
|
58
|
-
}
|
|
59
|
-
// An uncertain native process may still be reading source bytes: preserve ownership.
|
|
60
|
-
if (captured && cleanup === 'settled')
|
|
61
|
-
try {
|
|
62
|
-
await captured.dispose();
|
|
66
|
+
if (options.signal?.aborted) {
|
|
67
|
+
store.queue.cancel(token.jobId);
|
|
68
|
+
throw new SpongeError('cancelled');
|
|
63
69
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
70
|
+
const observedAt = new Date().toISOString(), metadata = [
|
|
71
|
+
{ field: 'transcriptSource', value: transcription.transcript.source, source: 'media', observedAt },
|
|
72
|
+
{ field: 'language', value: transcription.transcript.language, source: 'media', observedAt },
|
|
73
|
+
{ field: 'durationMs', value: String(transcription.transcript.durationMs), source: 'media', observedAt },
|
|
74
|
+
];
|
|
75
|
+
for (const field of ['title', 'author'])
|
|
76
|
+
if (result[field])
|
|
77
|
+
metadata.push({
|
|
78
|
+
field: field === 'author' ? 'authors' : field,
|
|
79
|
+
value: field === 'author' ? [result[field]] : result[field],
|
|
80
|
+
source: 'media',
|
|
81
|
+
observedAt,
|
|
82
|
+
selected: true,
|
|
83
|
+
});
|
|
84
|
+
const status = result.warnings.length ? 'partial' : 'complete';
|
|
85
|
+
await options.checkpoint?.('promoting');
|
|
86
|
+
const revision = store.revisions.commit({
|
|
87
|
+
...token,
|
|
88
|
+
itemId: source.itemId,
|
|
89
|
+
bundleHash: sha256(JSON.stringify(['media-transcript-v1', result.manifestSha256, transcriptHash])),
|
|
90
|
+
markdown: transcription.markdown,
|
|
91
|
+
title: result.title,
|
|
92
|
+
extractor: { name: 'sponge-media', version: '1' },
|
|
93
|
+
status,
|
|
94
|
+
metadata,
|
|
95
|
+
assets,
|
|
96
|
+
warnings: result.warnings.length ? ['partial'] : [],
|
|
97
|
+
});
|
|
98
|
+
return { ok: true, itemId: source.itemId, revisionId: revision.revisionId, status };
|
|
99
|
+
});
|
|
82
100
|
}
|
|
@@ -1,17 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { sha256 } from '../util/hash.js';
|
|
2
2
|
import { failure } from '../acquisition/errors.js';
|
|
3
3
|
import { transcribeAudio } from '../inference/asr/runtime.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { SpongeError, codeOf } from '../errors.js';
|
|
5
|
+
const timestamp = (s) => {
|
|
6
|
+
const m = /^(?:(\d{2,}):)?(\d{2}):(\d{2})\.(\d{3})$/.exec(s);
|
|
7
|
+
if (!m || Number(m[2]) > 59 || Number(m[3]) > 59)
|
|
8
|
+
throw new SpongeError('integrity');
|
|
9
|
+
const n = ((Number(m[1] ?? 0) * 60 + Number(m[2])) * 60 + Number(m[3])) * 1000 + Number(m[4]);
|
|
10
|
+
if (n > 86400000)
|
|
11
|
+
throw new SpongeError('limit-exceeded');
|
|
12
|
+
return n;
|
|
13
|
+
};
|
|
14
|
+
function plain(text) {
|
|
15
|
+
return text
|
|
16
|
+
.replace(/<[^>]*>/g, '')
|
|
17
|
+
.replace(/&(?:amp|lt|gt|quot|apos|nbsp);/g, (s) => ({ '&': '&', '<': '<', '>': '>', '"': '"', ''': "'", ' ': ' ' })[s])
|
|
18
|
+
.replace(/\s+/g, ' ')
|
|
19
|
+
.trim();
|
|
20
|
+
}
|
|
8
21
|
/** Strict, bounded timed WebVTT. Rolling captions only deduplicate already-overlapping text. */
|
|
9
22
|
export function parseWebVtt(captions, requestId) {
|
|
10
|
-
if (Buffer.byteLength(captions.text) > 4 * 1024 ** 2 ||
|
|
11
|
-
|
|
23
|
+
if (Buffer.byteLength(captions.text) > 4 * 1024 ** 2 ||
|
|
24
|
+
sha256(captions.text) !== captions.sha256 ||
|
|
25
|
+
!['manual', 'automatic'].includes(captions.source) ||
|
|
26
|
+
!/^[a-zA-Z]{2,8}(?:-[a-zA-Z0-9]{1,8})*$/.test(captions.language) ||
|
|
27
|
+
!Number.isFinite(captions.durationMs) ||
|
|
28
|
+
captions.durationMs <= 0 ||
|
|
29
|
+
captions.durationMs > 86400000)
|
|
30
|
+
throw new SpongeError('integrity');
|
|
12
31
|
const text = captions.text.replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
|
|
13
32
|
if (!/^WEBVTT(?:[ \t][^\n]*)?\n/.test(text) || text.includes('\0'))
|
|
14
|
-
throw
|
|
33
|
+
throw new SpongeError('integrity');
|
|
15
34
|
const segments = [];
|
|
16
35
|
let lastStart = 0;
|
|
17
36
|
let previousCue;
|
|
@@ -21,17 +40,17 @@ export function parseWebVtt(captions, requestId) {
|
|
|
21
40
|
continue;
|
|
22
41
|
const index = lines[0].includes('-->') ? 0 : 1, line = lines[index];
|
|
23
42
|
if (!line)
|
|
24
|
-
throw
|
|
43
|
+
throw new SpongeError('integrity');
|
|
25
44
|
const match = /^(\S+)\s+-->\s+(\S+)(?:\s+.*)?$/.exec(line);
|
|
26
45
|
if (!match)
|
|
27
|
-
throw
|
|
46
|
+
throw new SpongeError('integrity');
|
|
28
47
|
const startMs = timestamp(match[1]), endMs = timestamp(match[2]);
|
|
29
48
|
if (startMs < lastStart || endMs <= startMs || endMs > captions.durationMs + 1000)
|
|
30
|
-
throw
|
|
49
|
+
throw new SpongeError('integrity');
|
|
31
50
|
lastStart = startMs;
|
|
32
51
|
let cue = plain(lines.slice(index + 1).join(' '));
|
|
33
52
|
if (Buffer.byteLength(cue) > 16384)
|
|
34
|
-
throw
|
|
53
|
+
throw new SpongeError('limit-exceeded');
|
|
35
54
|
if (!cue)
|
|
36
55
|
continue;
|
|
37
56
|
const previous = segments.at(-1), rawCue = cue;
|
|
@@ -51,29 +70,52 @@ export function parseWebVtt(captions, requestId) {
|
|
|
51
70
|
}
|
|
52
71
|
segments.push({ startMs, endMs, text: cue });
|
|
53
72
|
if (segments.length > 30000)
|
|
54
|
-
throw
|
|
73
|
+
throw new SpongeError('limit-exceeded');
|
|
55
74
|
}
|
|
56
75
|
if (!segments.length)
|
|
57
|
-
throw
|
|
58
|
-
return {
|
|
76
|
+
throw new SpongeError('unsupported');
|
|
77
|
+
return {
|
|
78
|
+
schemaVersion: 1,
|
|
79
|
+
requestId,
|
|
80
|
+
source: captions.source === 'manual' ? 'human-captions' : 'automatic-captions',
|
|
81
|
+
language: captions.language,
|
|
82
|
+
segments,
|
|
83
|
+
cleanup: 'settled',
|
|
84
|
+
sourceSha256: captions.sha256,
|
|
85
|
+
timing: 'source-captions',
|
|
86
|
+
durationMs: captions.durationMs,
|
|
87
|
+
};
|
|
59
88
|
}
|
|
60
|
-
const stamp = (ms) => {
|
|
89
|
+
const stamp = (ms) => {
|
|
90
|
+
const seconds = Math.floor(ms / 1000);
|
|
91
|
+
return [Math.floor(seconds / 3600), Math.floor(seconds / 60) % 60, seconds % 60]
|
|
92
|
+
.map((n) => String(n).padStart(2, '0'))
|
|
93
|
+
.join(':');
|
|
94
|
+
};
|
|
61
95
|
export function transcriptMarkdown(transcript) {
|
|
62
96
|
const paragraphs = [];
|
|
63
97
|
for (const segment of transcript.segments) {
|
|
64
98
|
const prior = paragraphs.at(-1);
|
|
65
|
-
if (transcript.source === 'local-asr' &&
|
|
99
|
+
if (transcript.source === 'local-asr' &&
|
|
100
|
+
prior &&
|
|
101
|
+
segment.startMs - prior.endMs <= 2000 &&
|
|
102
|
+
segment.endMs - prior.startMs <= 15000 &&
|
|
103
|
+
!/[.!?]["')]*$/.test(prior.text)) {
|
|
66
104
|
prior.text += ' ' + segment.text;
|
|
67
105
|
prior.endMs = segment.endMs;
|
|
68
106
|
}
|
|
69
107
|
else
|
|
70
108
|
paragraphs.push({ ...segment });
|
|
71
109
|
}
|
|
72
|
-
return paragraphs.map(s => `[${stamp(s.startMs)}] ${s.text.replace(/[\\`*_{}\[\]()<>!#|]/g, '\\$&')}`).join('\n\n') +
|
|
110
|
+
return (paragraphs.map((s) => `[${stamp(s.startMs)}] ${s.text.replace(/[\\`*_{}\[\]()<>!#|]/g, '\\$&')}`).join('\n\n') +
|
|
111
|
+
'\n');
|
|
73
112
|
}
|
|
74
113
|
/** Acquisition owns media lifetime; transcription never deletes or mutates caller source files. */
|
|
75
114
|
export async function transcribeMedia(input, options) {
|
|
76
|
-
const failed = (code) => ({
|
|
115
|
+
const failed = (code) => ({
|
|
116
|
+
...failure(input.requestId, code, 'inference'),
|
|
117
|
+
cleanup: 'settled',
|
|
118
|
+
});
|
|
77
119
|
if (options.signal?.aborted)
|
|
78
120
|
return failed('cancelled');
|
|
79
121
|
if (input.captions && !input.forceLocal) {
|
|
@@ -82,8 +124,8 @@ export async function transcribeMedia(input, options) {
|
|
|
82
124
|
return { ok: true, transcript, markdown: transcriptMarkdown(transcript), cleanup: 'settled' };
|
|
83
125
|
}
|
|
84
126
|
catch (e) {
|
|
85
|
-
if (
|
|
86
|
-
return failed(e
|
|
127
|
+
if (codeOf(e) !== 'unsupported' || !input.audio)
|
|
128
|
+
return failed(codeOf(e) === 'limit-exceeded' ? 'limit-exceeded' : 'integrity');
|
|
87
129
|
}
|
|
88
130
|
}
|
|
89
131
|
if (!input.audio)
|
|
@@ -1,26 +1,58 @@
|
|
|
1
1
|
import { limits } from '../../contracts/index.js';
|
|
2
2
|
import { validateMetadataClaim, validateMetadataWarnings } from '../../contracts/validate.js';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { SpongeError } from '../errors.js';
|
|
4
|
+
function object(v) {
|
|
5
|
+
if (!v || typeof v !== 'object' || Array.isArray(v))
|
|
6
|
+
throw new SpongeError('integrity');
|
|
7
|
+
return v;
|
|
8
|
+
}
|
|
5
9
|
/** Consume bounded observed Wordcell evidence without fetching linked JSON-LD contexts. */
|
|
6
10
|
export function articleMetadata(input, observedAt) {
|
|
7
11
|
const envelope = object(input);
|
|
8
12
|
articleMetadataWarnings(input);
|
|
9
13
|
if (envelope.schemaVersion !== 1 || envelope.extractorVersion !== 'wordcell-metadata-v1')
|
|
10
|
-
throw
|
|
11
|
-
if (!Array.isArray(envelope.claims) ||
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
throw new SpongeError('schema-newer');
|
|
15
|
+
if (!Array.isArray(envelope.claims) ||
|
|
16
|
+
envelope.claims.length > limits.metadataClaims ||
|
|
17
|
+
Buffer.byteLength(JSON.stringify(input)) > limits.metadataBytes)
|
|
18
|
+
throw new SpongeError('limit-exceeded');
|
|
19
|
+
const claims = envelope.claims.map((raw) => {
|
|
14
20
|
const claim = object(raw), locator = claim.locator;
|
|
15
21
|
if (typeof locator !== 'string' || typeof claim.selected !== 'boolean')
|
|
16
|
-
throw
|
|
17
|
-
const sources = {
|
|
22
|
+
throw new SpongeError('integrity');
|
|
23
|
+
const sources = {
|
|
24
|
+
'html-title': 'html',
|
|
25
|
+
'html-meta': 'html',
|
|
26
|
+
'html-link': 'html',
|
|
27
|
+
'html-language': 'html',
|
|
28
|
+
'json-ld': 'jsonld',
|
|
29
|
+
extractor: 'html',
|
|
30
|
+
};
|
|
18
31
|
if (typeof claim.source !== 'string' || !Object.hasOwn(sources, claim.source))
|
|
19
|
-
throw
|
|
20
|
-
const source = /^meta\[(?:property|name)=og:/.test(locator)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
32
|
+
throw new SpongeError('integrity');
|
|
33
|
+
const source = /^meta\[(?:property|name)=og:/.test(locator)
|
|
34
|
+
? 'opengraph'
|
|
35
|
+
: /^meta\[(?:property|name)=twitter:/.test(locator)
|
|
36
|
+
? 'twitter'
|
|
37
|
+
: sources[claim.source];
|
|
38
|
+
if (claim.timezone !== undefined &&
|
|
39
|
+
(typeof claim.timezone !== 'string' || !['explicit', 'unknown', 'not-applicable'].includes(claim.timezone)))
|
|
40
|
+
throw new SpongeError('integrity');
|
|
41
|
+
const result = {
|
|
42
|
+
field: claim.field,
|
|
43
|
+
value: claim.value,
|
|
44
|
+
observed: claim.observed,
|
|
45
|
+
source,
|
|
46
|
+
locator,
|
|
47
|
+
selected: claim.selected,
|
|
48
|
+
observedAt,
|
|
49
|
+
rule: claim.rule,
|
|
50
|
+
extractionVersion: envelope.extractorVersion,
|
|
51
|
+
...(claim.datePrecision !== undefined ? { datePrecision: claim.datePrecision } : {}),
|
|
52
|
+
...(claim.timezone === 'explicit' || claim.timezone === 'unknown'
|
|
53
|
+
? { timezoneKnown: claim.timezone === 'explicit' }
|
|
54
|
+
: {}),
|
|
55
|
+
};
|
|
24
56
|
validateMetadataClaim(result);
|
|
25
57
|
return result;
|
|
26
58
|
});
|
|
@@ -30,14 +62,14 @@ export function articleMetadata(input, observedAt) {
|
|
|
30
62
|
if (!claim.selected)
|
|
31
63
|
continue;
|
|
32
64
|
if (selected.has(claim.field) && !['authors', 'tags', 'alternate'].includes(claim.field))
|
|
33
|
-
throw
|
|
65
|
+
throw new SpongeError('integrity');
|
|
34
66
|
selected.add(claim.field);
|
|
35
67
|
}
|
|
36
68
|
return claims;
|
|
37
69
|
}
|
|
38
70
|
export function selectedMetadata(claims, field) {
|
|
39
|
-
const matches = claims.filter(claim => claim.field === field);
|
|
40
|
-
return matches.find(claim => claim.selected === true) ?? matches.find(claim => claim.selected === undefined);
|
|
71
|
+
const matches = claims.filter((claim) => claim.field === field);
|
|
72
|
+
return matches.find((claim) => claim.selected === true) ?? matches.find((claim) => claim.selected === undefined);
|
|
41
73
|
}
|
|
42
74
|
export function articleMetadataWarnings(input) {
|
|
43
75
|
const envelope = object(input);
|
|
@@ -45,7 +77,7 @@ export function articleMetadataWarnings(input) {
|
|
|
45
77
|
validateMetadataWarnings(envelope.warnings);
|
|
46
78
|
}
|
|
47
79
|
catch {
|
|
48
|
-
throw
|
|
80
|
+
throw new SpongeError('integrity');
|
|
49
81
|
}
|
|
50
82
|
return [...envelope.warnings];
|
|
51
83
|
}
|
|
@@ -10,10 +10,6 @@ export interface ModelPackage {
|
|
|
10
10
|
license: string;
|
|
11
11
|
artifacts: readonly ModelArtifact[];
|
|
12
12
|
}
|
|
13
|
-
export declare class ModelSetupError extends Error {
|
|
14
|
-
readonly code: 'invalid-input' | 'integrity' | 'unavailable' | 'cancelled' | 'owner-busy' | 'limit-exceeded' | 'cleanup-required' | 'repair-auth';
|
|
15
|
-
constructor(code: 'invalid-input' | 'integrity' | 'unavailable' | 'cancelled' | 'owner-busy' | 'limit-exceeded' | 'cleanup-required' | 'repair-auth');
|
|
16
|
-
}
|
|
17
13
|
export declare function modelSetupPlan(manifest: ModelPackage): {
|
|
18
14
|
id: string;
|
|
19
15
|
license: string;
|
|
@@ -23,14 +19,20 @@ export declare function modelSetupPlan(manifest: ModelPackage): {
|
|
|
23
19
|
artifacts: readonly ModelArtifact[];
|
|
24
20
|
};
|
|
25
21
|
export declare function inspectModelPackage(cacheRoot: string, manifest: ModelPackage): Promise<Record<string, string>>;
|
|
26
|
-
/** Explicit recovery only: an absent or different process birth identity proves the downloader is gone.
|
|
27
|
-
* Downloading is in-process fetch; no native child can outlive this owner. A missing or
|
|
28
|
-
* foreign stage receipt remains pending, as does an interrupted recovery guard. */
|
|
29
22
|
export declare function recoverModelPackage(cacheRoot: string, manifest: ModelPackage): Promise<{
|
|
30
23
|
recovered: boolean;
|
|
31
24
|
}>;
|
|
32
|
-
/** Explicit setup only. Never called by install hooks or by inference fallback
|
|
25
|
+
/** Explicit setup only. Never called by install hooks or by inference fallback, except `reuseOnly`: that adopts an
|
|
26
|
+
* installed package from verified local artifacts under the same setup lock and never reaches the network. */
|
|
33
27
|
export declare function provisionModelPackage(cacheRoot: string, manifest: ModelPackage, options?: {
|
|
34
28
|
signal?: AbortSignal;
|
|
35
29
|
fetch?: typeof fetch;
|
|
30
|
+
reuseOnly?: boolean;
|
|
36
31
|
}): Promise<Record<string, string>>;
|
|
32
|
+
/** The installed package for this manifest, by content, or null when none is. A package installed under an earlier
|
|
33
|
+
* URL-bound fingerprint is adopted once: its verified artifacts are hard-linked into the content-named directory under
|
|
34
|
+
* the setup lock (no network; the old directory stays intact for any process holding its paths until runtime GC
|
|
35
|
+
* supersedes it). Anything short of a fully verified adoption is reported as not installed, as before. */
|
|
36
|
+
export declare function inspectInstalledModelPackage(cacheRoot: string, manifest: ModelPackage, options?: {
|
|
37
|
+
pause?: (ms: number) => Promise<void>;
|
|
38
|
+
}): Promise<Record<string, string> | null>;
|