@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
|
@@ -4,10 +4,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { limits } from '../../contracts/index.js';
|
|
5
5
|
import { privateRoot, physicalBytes } from './files.js';
|
|
6
6
|
import { runOwned, isolatedEnvironment } from './process.js';
|
|
7
|
-
import { inspectCaptureRuntime
|
|
8
|
-
import { settleCaptureRoot
|
|
7
|
+
import { inspectCaptureRuntime } from './runtime.js';
|
|
8
|
+
import { settleCaptureRoot } from './cleanup.js';
|
|
9
9
|
import { failure, upstreamFailure } from './errors.js';
|
|
10
10
|
import { normalizeBundle, object } from './normalize.js';
|
|
11
|
+
import { SpongeError, publicCode, codeOf } from '../errors.js';
|
|
11
12
|
export async function captureArticle(request) {
|
|
12
13
|
const { requestId, signal } = request;
|
|
13
14
|
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(requestId))
|
|
@@ -28,7 +29,7 @@ export async function captureArticle(request) {
|
|
|
28
29
|
runtime = await inspectCaptureRuntime(request.runtime.root, request.runtime.pins);
|
|
29
30
|
}
|
|
30
31
|
catch (error) {
|
|
31
|
-
return failure(requestId, error
|
|
32
|
+
return failure(requestId, publicCode(error, 'runtime-missing'), 'setup');
|
|
32
33
|
}
|
|
33
34
|
let base;
|
|
34
35
|
try {
|
|
@@ -43,7 +44,24 @@ export async function captureArticle(request) {
|
|
|
43
44
|
const deadline = request.deadlineMs ?? limits.captureDeadlineMs;
|
|
44
45
|
if (!Number.isSafeInteger(deadline) || deadline < 100 || deadline > limits.captureDeadlineMs)
|
|
45
46
|
return failure(requestId, 'invalid-input');
|
|
46
|
-
const args = [
|
|
47
|
+
const args = [
|
|
48
|
+
runtime.ghostget,
|
|
49
|
+
'clip',
|
|
50
|
+
request.sourceUrl,
|
|
51
|
+
'article',
|
|
52
|
+
'--media',
|
|
53
|
+
'images',
|
|
54
|
+
'--timeout-ms',
|
|
55
|
+
String(deadline),
|
|
56
|
+
'--max-html-bytes',
|
|
57
|
+
String(limits.markdownBytes),
|
|
58
|
+
'--max-asset-bytes',
|
|
59
|
+
String(limits.imageBytes),
|
|
60
|
+
'--max-total-asset-bytes',
|
|
61
|
+
String(500 * 1024 ** 2),
|
|
62
|
+
'--quiet',
|
|
63
|
+
'--json',
|
|
64
|
+
];
|
|
47
65
|
if (request.allowPrivateNetwork)
|
|
48
66
|
args.push('--allow-private-network');
|
|
49
67
|
let selectedState;
|
|
@@ -74,27 +92,40 @@ export async function captureArticle(request) {
|
|
|
74
92
|
if (signal?.aborted)
|
|
75
93
|
abort();
|
|
76
94
|
let cleanup = 'settled', keep = false, quota = false, checking = false;
|
|
77
|
-
const monitor = setInterval(async () => {
|
|
78
|
-
|
|
79
|
-
|
|
95
|
+
const monitor = setInterval(async () => {
|
|
96
|
+
if (checking)
|
|
97
|
+
return;
|
|
98
|
+
checking = true;
|
|
99
|
+
try {
|
|
100
|
+
if ((await physicalBytes(root)) > limits.stagingBytes) {
|
|
101
|
+
quota = true;
|
|
102
|
+
controller.abort();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
80
106
|
quota = true;
|
|
81
107
|
controller.abort();
|
|
82
108
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
finally {
|
|
89
|
-
checking = false;
|
|
90
|
-
} }, 100);
|
|
109
|
+
finally {
|
|
110
|
+
checking = false;
|
|
111
|
+
}
|
|
112
|
+
}, 100);
|
|
91
113
|
const started = Date.now();
|
|
92
114
|
try {
|
|
93
115
|
await mkdir(home, { mode: 0o700 });
|
|
94
116
|
await mkdir(output, { mode: 0o700 });
|
|
95
|
-
await writeFile(join(root, 'ownership.json'), JSON.stringify({ schemaVersion: 1, requestId, state: 'acquiring' }), {
|
|
117
|
+
await writeFile(join(root, 'ownership.json'), JSON.stringify({ schemaVersion: 1, requestId, state: 'acquiring' }), {
|
|
118
|
+
mode: 0o600,
|
|
119
|
+
});
|
|
96
120
|
cleanup = 'pending';
|
|
97
|
-
const result = await (request.runProcess ?? runOwned)(runtime.bun, args, {
|
|
121
|
+
const result = await (request.runProcess ?? runOwned)(runtime.bun, args, {
|
|
122
|
+
cwd: root,
|
|
123
|
+
env,
|
|
124
|
+
signal: controller.signal,
|
|
125
|
+
deadlineMs: deadline,
|
|
126
|
+
maxOutputBytes: limits.metadataBytes,
|
|
127
|
+
graceMs: 5000,
|
|
128
|
+
});
|
|
98
129
|
cleanup = result.cleanup;
|
|
99
130
|
// Ghostget reports escaped browser custody separately from generic descendants.
|
|
100
131
|
let summary;
|
|
@@ -109,9 +140,13 @@ export async function captureArticle(request) {
|
|
|
109
140
|
cleanup = 'pending';
|
|
110
141
|
if (cleanup === 'settled' && request.connection?.kind === 'cdp') {
|
|
111
142
|
cleanup = 'pending';
|
|
112
|
-
const census = await (request.runProcess ?? runOwned)('/bin/ps', ['-axo', 'command'], {
|
|
143
|
+
const census = await (request.runProcess ?? runOwned)('/bin/ps', ['-axo', 'command'], {
|
|
144
|
+
env,
|
|
145
|
+
deadlineMs: 3000,
|
|
146
|
+
maxOutputBytes: 4 * 1024 ** 2,
|
|
147
|
+
});
|
|
113
148
|
cleanup = census.cleanup;
|
|
114
|
-
if (census.code !== 0 || census.stopped || census.stdout.split('\n').some(line => line.includes(root)))
|
|
149
|
+
if (census.code !== 0 || census.stopped || census.stdout.split('\n').some((line) => line.includes(root)))
|
|
115
150
|
cleanup = 'pending';
|
|
116
151
|
}
|
|
117
152
|
if (cleanup !== 'settled')
|
|
@@ -123,11 +158,19 @@ export async function captureArticle(request) {
|
|
|
123
158
|
if (result.code !== 0 || object(summary).ok !== true)
|
|
124
159
|
return upstream;
|
|
125
160
|
const outputDirectory = object(summary).outputDirectory;
|
|
126
|
-
if (typeof outputDirectory !== 'string' ||
|
|
161
|
+
if (typeof outputDirectory !== 'string' ||
|
|
162
|
+
!outputDirectory.startsWith(output + '/') ||
|
|
163
|
+
(await realpath(outputDirectory)) !== outputDirectory)
|
|
127
164
|
return failure(requestId, 'integrity', 'verification');
|
|
128
165
|
const helper = fileURLToPath(new URL(import.meta.url.endsWith('.ts') ? './verify-helper.ts' : './verify-helper.js', import.meta.url));
|
|
129
166
|
cleanup = 'pending';
|
|
130
|
-
const checked = await (request.runProcess ?? runOwned)(runtime.bun, [helper, runtime.root, outputDirectory], {
|
|
167
|
+
const checked = await (request.runProcess ?? runOwned)(runtime.bun, [helper, runtime.root, outputDirectory], {
|
|
168
|
+
cwd: root,
|
|
169
|
+
env,
|
|
170
|
+
signal: controller.signal,
|
|
171
|
+
deadlineMs: Math.max(1, deadline - (Date.now() - started)),
|
|
172
|
+
maxOutputBytes: limits.helperFrameBytes,
|
|
173
|
+
});
|
|
131
174
|
cleanup = checked.cleanup;
|
|
132
175
|
if (cleanup !== 'settled')
|
|
133
176
|
return failure(requestId, 'cleanup-required', 'cleanup');
|
|
@@ -135,26 +178,42 @@ export async function captureArticle(request) {
|
|
|
135
178
|
return failure(requestId, checked.stopped);
|
|
136
179
|
if (checked.code !== 0)
|
|
137
180
|
return failure(requestId, 'integrity', 'verification');
|
|
138
|
-
const capture = await normalizeBundle({
|
|
181
|
+
const capture = await normalizeBundle({
|
|
182
|
+
bundleRoot: outputDirectory,
|
|
183
|
+
inspection: JSON.parse(checked.stdout),
|
|
184
|
+
summary,
|
|
185
|
+
requestId,
|
|
186
|
+
sourceUrl: request.sourceUrl,
|
|
187
|
+
wordcellVersion: runtime.pins.wordcell.version,
|
|
188
|
+
});
|
|
139
189
|
if (controller.signal.aborted)
|
|
140
190
|
return failure(requestId, quota ? 'limit-exceeded' : 'cancelled');
|
|
141
|
-
await writeFile(join(root, 'ownership.json'), JSON.stringify({ schemaVersion: 1, requestId, state: 'verified' }), {
|
|
191
|
+
await writeFile(join(root, 'ownership.json'), JSON.stringify({ schemaVersion: 1, requestId, state: 'verified' }), {
|
|
192
|
+
mode: 0o600,
|
|
193
|
+
});
|
|
142
194
|
keep = true;
|
|
143
|
-
return {
|
|
144
|
-
|
|
195
|
+
return {
|
|
196
|
+
ok: true,
|
|
197
|
+
capture,
|
|
198
|
+
bundleRoot: outputDirectory,
|
|
199
|
+
dispose: async () => {
|
|
200
|
+
if ((await settleCaptureRoot(root, requestId)) === 'pending')
|
|
201
|
+
throw new SpongeError('cleanup-required');
|
|
202
|
+
},
|
|
203
|
+
};
|
|
145
204
|
}
|
|
146
205
|
catch (error) {
|
|
147
206
|
if (cleanup !== 'settled')
|
|
148
207
|
return failure(requestId, 'cleanup-required', 'cleanup');
|
|
149
|
-
const
|
|
208
|
+
const raw = codeOf(error), code = raw === 'integrity' || raw === 'limit-exceeded' || raw === 'schema-newer' ? raw : 'unclassified';
|
|
150
209
|
return failure(requestId, code, 'verification');
|
|
151
210
|
}
|
|
152
211
|
finally {
|
|
153
212
|
clearInterval(monitor);
|
|
154
213
|
while (checking)
|
|
155
|
-
await new Promise(r => setTimeout(r, 5));
|
|
214
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
156
215
|
signal?.removeEventListener('abort', abort);
|
|
157
|
-
if (!keep && cleanup === 'settled' && await settleCaptureRoot(root, requestId) === 'pending')
|
|
216
|
+
if (!keep && cleanup === 'settled' && (await settleCaptureRoot(root, requestId)) === 'pending')
|
|
158
217
|
return failure(requestId, 'cleanup-required', 'cleanup');
|
|
159
218
|
}
|
|
160
219
|
}
|
|
@@ -1,6 +1,2 @@
|
|
|
1
|
-
export declare class CaptureCleanupError extends Error {
|
|
2
|
-
readonly code = "cleanup-required";
|
|
3
|
-
constructor();
|
|
4
|
-
}
|
|
5
1
|
/** Cleanup failure retains ownership evidence and must not settle the queue attempt. */
|
|
6
2
|
export declare function settleCaptureRoot(root: string, requestId: string): Promise<'settled' | 'pending'>;
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
export class CaptureCleanupError extends Error {
|
|
4
|
-
code = 'cleanup-required';
|
|
5
|
-
constructor() { super('cleanup-required'); }
|
|
6
|
-
}
|
|
7
3
|
/** Cleanup failure retains ownership evidence and must not settle the queue attempt. */
|
|
8
4
|
export async function settleCaptureRoot(root, requestId) {
|
|
9
5
|
try {
|
|
@@ -14,7 +10,9 @@ export async function settleCaptureRoot(root, requestId) {
|
|
|
14
10
|
try {
|
|
15
11
|
await writeFile(join(root, 'ownership.json'), JSON.stringify({ schemaVersion: 1, requestId, state: 'cleanup-required' }), { mode: 0o600 });
|
|
16
12
|
}
|
|
17
|
-
catch {
|
|
13
|
+
catch {
|
|
14
|
+
/* Existing ownership record remains when directory access itself failed. */
|
|
15
|
+
}
|
|
18
16
|
return 'pending';
|
|
19
17
|
}
|
|
20
18
|
}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
const mappings = {
|
|
1
|
+
const mappings = {
|
|
2
|
+
unknown: 'unclassified',
|
|
3
|
+
'permission-required': 'grant-permission',
|
|
4
|
+
'auth-required': 'repair-auth',
|
|
5
|
+
'invalid-input': 'invalid-input',
|
|
6
|
+
cancelled: 'cancelled',
|
|
7
|
+
timeout: 'unavailable',
|
|
8
|
+
transient: 'unavailable',
|
|
9
|
+
unsupported: 'unsupported',
|
|
10
|
+
'cleanup-pending': 'cleanup-required',
|
|
11
|
+
'rate-limited': 'rate-limited',
|
|
12
|
+
};
|
|
2
13
|
export const failure = (requestId, code, phase = 'acquisition', retryable = false) => ({ schemaVersion: 1, requestId, ok: false, code, phase, retryable });
|
|
3
14
|
export function upstreamFailure(value, requestId) {
|
|
4
15
|
const fallback = failure(requestId, 'unclassified');
|
|
@@ -11,7 +22,11 @@ export function upstreamFailure(value, requestId) {
|
|
|
11
22
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
12
23
|
return fallback;
|
|
13
24
|
const typed = raw;
|
|
14
|
-
if (Object.keys(typed).some(k => !['schemaVersion', 'code', 'retryable'].includes(k)) ||
|
|
25
|
+
if (Object.keys(typed).some((k) => !['schemaVersion', 'code', 'retryable'].includes(k)) ||
|
|
26
|
+
typed.schemaVersion !== 1 ||
|
|
27
|
+
typeof typed.code !== 'string' ||
|
|
28
|
+
!Object.hasOwn(mappings, typed.code) ||
|
|
29
|
+
typeof typed.retryable !== 'boolean')
|
|
15
30
|
return fallback;
|
|
16
31
|
const code = mappings[typed.code];
|
|
17
32
|
if (typed.retryable && !['timeout', 'transient', 'rate-limited'].includes(typed.code))
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export declare const hash: (bytes: Uint8Array | string) => string;
|
|
2
1
|
export declare function privateRoot(path: string): Promise<string>;
|
|
3
2
|
export declare function readConfined(root: string, name: string, maxBytes: number): Promise<Buffer>;
|
|
4
3
|
export declare function physicalBytes(root: string): Promise<number>;
|
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
import { constants } from 'node:fs';
|
|
2
2
|
import { open, lstat, realpath, readdir } from 'node:fs/promises';
|
|
3
3
|
import { resolve, relative, isAbsolute, join } from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
export const hash = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
4
|
+
import { SpongeError } from '../errors.js';
|
|
6
5
|
export async function privateRoot(path) {
|
|
7
6
|
if (!isAbsolute(path))
|
|
8
|
-
throw
|
|
7
|
+
throw new SpongeError('invalid-input');
|
|
9
8
|
const canonical = await realpath(path), stat = await lstat(path);
|
|
10
|
-
if (canonical !== path ||
|
|
11
|
-
|
|
9
|
+
if (canonical !== path ||
|
|
10
|
+
!stat.isDirectory() ||
|
|
11
|
+
stat.isSymbolicLink() ||
|
|
12
|
+
stat.uid !== process.getuid?.() ||
|
|
13
|
+
(stat.mode & 0o077) !== 0)
|
|
14
|
+
throw new SpongeError('invalid-input');
|
|
12
15
|
return canonical;
|
|
13
16
|
}
|
|
14
17
|
export async function readConfined(root, name, maxBytes) {
|
|
15
|
-
if (!name || isAbsolute(name) || name.includes('\\') || name.split('/').some(p => !p || p === '.' || p === '..'))
|
|
16
|
-
throw
|
|
18
|
+
if (!name || isAbsolute(name) || name.includes('\\') || name.split('/').some((p) => !p || p === '.' || p === '..'))
|
|
19
|
+
throw new SpongeError('integrity');
|
|
17
20
|
const path = resolve(root, name);
|
|
18
|
-
if (relative(root, path).startsWith('..') || await realpath(path) !== path)
|
|
19
|
-
throw
|
|
21
|
+
if (relative(root, path).startsWith('..') || (await realpath(path)) !== path)
|
|
22
|
+
throw new SpongeError('integrity');
|
|
20
23
|
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
21
24
|
try {
|
|
22
25
|
const stat = await file.stat();
|
|
23
26
|
if (!stat.isFile() || stat.size > maxBytes)
|
|
24
|
-
throw
|
|
27
|
+
throw new SpongeError('limit-exceeded');
|
|
25
28
|
const data = Buffer.alloc(stat.size + 1);
|
|
26
29
|
const { bytesRead } = await file.read(data, 0, data.length, 0);
|
|
27
30
|
if (bytesRead !== stat.size)
|
|
28
|
-
throw
|
|
31
|
+
throw new SpongeError('integrity');
|
|
29
32
|
return data.subarray(0, bytesRead);
|
|
30
33
|
}
|
|
31
34
|
finally {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export { captureArticle } from './capture.js';
|
|
2
2
|
export type { CaptureConnection, CaptureRequest, CaptureOutcome } from './capture.js';
|
|
3
|
-
export { provisionCapture, inspectCaptureRuntime, captureSetupPlan
|
|
4
|
-
export type { CaptureRuntime
|
|
3
|
+
export { provisionCapture, inspectCaptureRuntime, captureSetupPlan } from './runtime.js';
|
|
4
|
+
export type { CaptureRuntime } from './runtime.js';
|
|
5
5
|
export { defaultCapturePins } from './pins.js';
|
|
6
|
-
export { CaptureCleanupError } from './cleanup.js';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
export { captureArticle } from './capture.js';
|
|
2
|
-
export { provisionCapture, inspectCaptureRuntime, captureSetupPlan
|
|
2
|
+
export { provisionCapture, inspectCaptureRuntime, captureSetupPlan } from './runtime.js';
|
|
3
3
|
export { defaultCapturePins } from './pins.js';
|
|
4
|
-
export { CaptureCleanupError } from './cleanup.js';
|