@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
package/README.md
CHANGED
|
@@ -79,6 +79,9 @@ to that repository's contents. Sponge never saves the token and sends it only to
|
|
|
79
79
|
GitHub's API; public model downloads do not receive it. With an authorized GitHub
|
|
80
80
|
CLI session, for example: `GH_TOKEN="$(gh auth token)" sponge setup --asr`.
|
|
81
81
|
Repeat with `--embedding` for search. Already verified caches work without a token.
|
|
82
|
+
Installed helpers and models are identified by content, so an upgrade that ships
|
|
83
|
+
byte-identical native artifacts keeps working without rerunning setup; a release
|
|
84
|
+
that changes them reports `runtime-missing` until setup runs again.
|
|
82
85
|
|
|
83
86
|
```sh
|
|
84
87
|
sponge setup --asr
|
|
@@ -87,7 +90,7 @@ sponge refresh ITEM --wait --language en --force-local
|
|
|
87
90
|
sponge setup --embedding
|
|
88
91
|
sponge index create
|
|
89
92
|
sponge index run
|
|
90
|
-
sponge index status
|
|
93
|
+
sponge index status
|
|
91
94
|
sponge search 'your query'
|
|
92
95
|
```
|
|
93
96
|
|
|
@@ -98,11 +101,16 @@ saves the English ASR choice for background jobs. Search combines local
|
|
|
98
101
|
full-text and semantic results when a compatible index and model are available,
|
|
99
102
|
and reports when semantic search is unavailable. `--mode lexical` uses only
|
|
100
103
|
full-text search. Use `index create --profile NAME` and `search QUERY --profile NAME`
|
|
101
|
-
to keep separate search profiles.
|
|
102
|
-
|
|
104
|
+
to keep separate search profiles. The active index follows the library: each new capture,
|
|
105
|
+
refresh or delete queues only its new chunks, which the worker (or `index run`) embeds, and
|
|
106
|
+
identical chunks are embedded once. `index status` shows the active generation's coverage
|
|
107
|
+
and what is pending; `index status GENERATION` shows one generation. Model changes create
|
|
108
|
+
separate index generations; a new one becomes active only after it covers the current
|
|
109
|
+
library. `index rollback
|
|
103
110
|
GENERATION` requires a complete generation compatible with the current revisions.
|
|
104
111
|
`index prune --dry-run` lists what pruning would delete; `index prune` deletes the
|
|
105
|
-
|
|
112
|
+
coverage of cancelled, failed and retired generations, then the vectors no kept generation
|
|
113
|
+
covers (including those of deleted items), keeping the active and any
|
|
106
114
|
building generation plus the most recent rollback-ready one per profile (`--keep N`,
|
|
107
115
|
0-16). It refuses while index work, cleanup or a backup is running. Freed pages are
|
|
108
116
|
reused; `--vacuum` also shrinks the file after checking free space. Rollback to a
|
|
@@ -146,9 +154,11 @@ sponge worker status
|
|
|
146
154
|
sponge worker uninstall
|
|
147
155
|
```
|
|
148
156
|
|
|
149
|
-
Exports contain Markdown, images and saved notes. Backups
|
|
150
|
-
assets, reading state
|
|
151
|
-
|
|
157
|
+
Exports contain Markdown, images and saved notes, never the publishing key. Backups
|
|
158
|
+
preserve revisions, assets, reading state, notes and the publishing key, so a
|
|
159
|
+
restored library publishes under the same identity; keep backups private. Restore
|
|
160
|
+
requires a new directory, verifies every file and leaves connections unresolved;
|
|
161
|
+
runtimes and credentials are excluded.
|
|
152
162
|
Garbage collection removes only unreferenced blobs after rechecking the report.
|
|
153
163
|
`gc --runtimes` removes capture runtimes and model packages this Sponge no longer
|
|
154
164
|
pins, plus settled worker journals; `--dry-run` only lists them. It holds worker
|
|
@@ -156,8 +166,10 @@ custody, keeps models an active or building index generation needs, and refuses
|
|
|
156
166
|
symlinks and anything in use. Successful `setup` runs the same collection.
|
|
157
167
|
|
|
158
168
|
The background worker is opt-in and processes work sequentially. Its LaunchAgent
|
|
159
|
-
pins the installed command; after
|
|
160
|
-
|
|
169
|
+
pins the installed command and Node; after a Sponge or Node upgrade, `worker status`
|
|
170
|
+
and `doctor` report the drift and `sponge worker install` reinstalls it in place.
|
|
171
|
+
Its output goes to `runtime/service/launchd.log` in the library, rotated at 1 MiB.
|
|
172
|
+
Uninstall stops the worker and preserves the library. In-session startup
|
|
161
173
|
and cleanup are checked; fresh-login startup remains unqualified. See
|
|
162
174
|
[importing existing captures](docs/importing.md) for a preview-first import flow.
|
|
163
175
|
|
package/bin/sponge.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
const [major,minor]=process.versions.node.split('.').map(Number);
|
|
4
|
-
if(major<24||(major===24&&minor<20)){
|
|
5
|
-
process.stderr.write('Sponge requires Node.js 24.20 or newer.\n');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const {
|
|
9
|
-
|
|
3
|
+
const [major, minor] = process.versions.node.split('.').map(Number);
|
|
4
|
+
if (major < 24 || (major === 24 && minor < 20)) {
|
|
5
|
+
process.stderr.write('Sponge requires Node.js 24.20 or newer.\n');
|
|
6
|
+
process.exitCode = 2;
|
|
7
|
+
} else {
|
|
8
|
+
const { version } = createRequire(import.meta.url)('../package.json');
|
|
9
|
+
const { main } = await import('../dist/src/cli/main.js');
|
|
10
|
+
process.exitCode = await main(process.argv.slice(2), version);
|
|
10
11
|
}
|
package/dist/contracts/index.js
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
|
-
export const exitCodes = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
export const exitCodes = {
|
|
2
|
+
success: 0,
|
|
3
|
+
failure: 1,
|
|
4
|
+
'invalid-input': 2,
|
|
5
|
+
partial: 3,
|
|
6
|
+
'action-required': 4,
|
|
7
|
+
cancelled: 130,
|
|
8
|
+
};
|
|
9
|
+
export const limits = Object.freeze({
|
|
10
|
+
pdfBytes: 100 * 1024 ** 2,
|
|
11
|
+
imageBytes: 20 * 1024 ** 2,
|
|
12
|
+
imagePixels: 40_000_000,
|
|
13
|
+
imagesPerItem: 200,
|
|
14
|
+
mediaDurationMs: 3_600_000,
|
|
15
|
+
temporaryMediaBytes: 1024 ** 3,
|
|
16
|
+
stagingBytes: 2 * 1024 ** 3,
|
|
17
|
+
freeReserveBytes: 2 * 1024 ** 3,
|
|
18
|
+
metadataBytes: 1024 ** 2,
|
|
19
|
+
metadataClaims: 512,
|
|
20
|
+
markdownBytes: 16 * 1024 ** 2,
|
|
21
|
+
captureDeadlineMs: 120_000,
|
|
22
|
+
helperFrameBytes: 64 * 1024,
|
|
23
|
+
helperOutputBytes: 16 * 1024 ** 2,
|
|
24
|
+
});
|
|
@@ -2,18 +2,32 @@ import { isAbsolute } from 'node:path';
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { limits } from './index.js';
|
|
4
4
|
const digest = /^[a-f0-9]{64}$/;
|
|
5
|
-
function requireValue(ok) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function
|
|
10
|
-
|
|
5
|
+
function requireValue(ok) {
|
|
6
|
+
if (!ok)
|
|
7
|
+
throw new Error('invalid-contract');
|
|
8
|
+
}
|
|
9
|
+
function record(value) {
|
|
10
|
+
requireValue(value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
11
|
+
}
|
|
12
|
+
function boundedString(value, max = 4096) {
|
|
13
|
+
requireValue(typeof value === 'string' && value.length > 0 && value.length <= max && !value.includes('\0'));
|
|
14
|
+
}
|
|
15
|
+
function onlyKeys(value, keys) {
|
|
16
|
+
requireValue(Object.keys(value).every((key) => keys.includes(key)));
|
|
17
|
+
}
|
|
18
|
+
function envelope(value) {
|
|
19
|
+
requireValue(value.schemaVersion === 1);
|
|
20
|
+
boundedString(value.requestId, 128);
|
|
21
|
+
}
|
|
11
22
|
/** Lexical confinement only. Consumers MUST additionally no-follow open/hash the bytes. */
|
|
12
23
|
export function validateAsset(value) {
|
|
13
24
|
record(value);
|
|
14
25
|
requireValue(typeof value.sha256 === 'string' && digest.test(value.sha256));
|
|
15
26
|
boundedString(value.relativePath);
|
|
16
|
-
requireValue(!value.relativePath.startsWith('/') &&
|
|
27
|
+
requireValue(!value.relativePath.startsWith('/') &&
|
|
28
|
+
!value.relativePath.includes('\\') &&
|
|
29
|
+
!value.relativePath.includes(':') &&
|
|
30
|
+
value.relativePath.split('/').every((p) => p !== '' && p !== '.' && p !== '..'));
|
|
17
31
|
boundedString(value.mediaType, 128);
|
|
18
32
|
requireValue(Number.isSafeInteger(value.bytes) && Number(value.bytes) >= 0);
|
|
19
33
|
}
|
|
@@ -27,7 +41,10 @@ export function validateCapture(value) {
|
|
|
27
41
|
validateAsset(value.markdown);
|
|
28
42
|
requireValue(value.markdown.bytes <= limits.markdownBytes && value.markdown.mediaType === 'text/markdown');
|
|
29
43
|
requireValue(Array.isArray(value.assets) && value.assets.length <= limits.imagesPerItem);
|
|
30
|
-
value.assets.forEach(asset => {
|
|
44
|
+
value.assets.forEach((asset) => {
|
|
45
|
+
validateAsset(asset);
|
|
46
|
+
requireValue(asset.mediaType.startsWith('image/') && asset.bytes <= limits.imageBytes);
|
|
47
|
+
});
|
|
31
48
|
if (value.sourcePdf !== undefined) {
|
|
32
49
|
validateAsset(value.sourcePdf);
|
|
33
50
|
requireValue(value.sourcePdf.mediaType === 'application/pdf' && value.sourcePdf.bytes <= limits.pdfBytes);
|
|
@@ -35,30 +52,38 @@ export function validateCapture(value) {
|
|
|
35
52
|
requireValue(value.kind !== 'pdf' || value.sourcePdf !== undefined);
|
|
36
53
|
record(value.completeness);
|
|
37
54
|
requireValue(typeof value.completeness.text === 'string' && ['complete', 'partial'].includes(String(value.completeness.text)));
|
|
38
|
-
requireValue(typeof value.completeness.images === 'string' &&
|
|
55
|
+
requireValue(typeof value.completeness.images === 'string' &&
|
|
56
|
+
['complete', 'partial', 'not-requested'].includes(String(value.completeness.images)));
|
|
39
57
|
requireValue(value.status !== 'complete' || (value.completeness.text === 'complete' && value.completeness.images !== 'partial'));
|
|
40
58
|
record(value.extractor);
|
|
41
59
|
boundedString(value.extractor.name, 128);
|
|
42
60
|
boundedString(value.extractor.version, 128);
|
|
43
61
|
requireValue(Array.isArray(value.warnings) && value.warnings.length <= 128);
|
|
44
|
-
value.warnings.forEach(v => requireValue(errorCodes.has(v)));
|
|
45
|
-
requireValue(Array.isArray(value.metadata) &&
|
|
62
|
+
value.warnings.forEach((v) => requireValue(errorCodes.has(v)));
|
|
63
|
+
requireValue(Array.isArray(value.metadata) &&
|
|
64
|
+
value.metadata.length <= limits.metadataClaims &&
|
|
65
|
+
Buffer.byteLength(JSON.stringify(value.metadata)) <= limits.metadataBytes);
|
|
46
66
|
value.metadata.forEach(validateMetadataClaim);
|
|
47
67
|
if (value.metadataWarnings !== undefined)
|
|
48
68
|
validateMetadataWarnings(value.metadataWarnings);
|
|
49
69
|
}
|
|
50
70
|
export function validateMetadataWarnings(value) {
|
|
51
|
-
requireValue(Array.isArray(value) &&
|
|
71
|
+
requireValue(Array.isArray(value) &&
|
|
72
|
+
value.length <= 5 &&
|
|
73
|
+
new Set(value).size === value.length &&
|
|
74
|
+
value.every((v) => typeof v === 'string' &&
|
|
75
|
+
['input-limit', 'claim-limit', 'value-limit', 'json-limit', 'json-invalid'].includes(v)));
|
|
52
76
|
}
|
|
53
77
|
export function validateMetadataClaim(claim) {
|
|
54
78
|
record(claim);
|
|
55
79
|
boundedString(claim.field, 128);
|
|
56
80
|
boundedString(claim.observedAt, 64);
|
|
57
81
|
requireValue(Number.isFinite(Date.parse(claim.observedAt)));
|
|
58
|
-
requireValue(typeof claim.source === 'string' &&
|
|
82
|
+
requireValue(typeof claim.source === 'string' &&
|
|
83
|
+
['html', 'opengraph', 'twitter', 'jsonld', 'pdf', 'media'].includes(String(claim.source)));
|
|
59
84
|
if (Array.isArray(claim.value)) {
|
|
60
85
|
requireValue(claim.value.length <= 128);
|
|
61
|
-
claim.value.forEach(v => boundedString(v, 16384));
|
|
86
|
+
claim.value.forEach((v) => boundedString(v, 16384));
|
|
62
87
|
}
|
|
63
88
|
else
|
|
64
89
|
boundedString(claim.value, 16384);
|
|
@@ -69,7 +94,7 @@ export function validateMetadataClaim(claim) {
|
|
|
69
94
|
if (claim.observed !== undefined) {
|
|
70
95
|
if (Array.isArray(claim.observed)) {
|
|
71
96
|
requireValue(claim.observed.length <= 128);
|
|
72
|
-
claim.observed.forEach(v => boundedString(v, 16384));
|
|
97
|
+
claim.observed.forEach((v) => boundedString(v, 16384));
|
|
73
98
|
}
|
|
74
99
|
else
|
|
75
100
|
boundedString(claim.observed, 16384);
|
|
@@ -83,27 +108,71 @@ export function validateMetadataClaim(claim) {
|
|
|
83
108
|
if (claim.datePrecision !== undefined)
|
|
84
109
|
requireValue(typeof claim.datePrecision === 'string' && ['date', 'datetime', 'invalid'].includes(claim.datePrecision));
|
|
85
110
|
}
|
|
86
|
-
const errorCodes = new Set([
|
|
111
|
+
const errorCodes = new Set([
|
|
112
|
+
'grant-permission',
|
|
113
|
+
'repair-auth',
|
|
114
|
+
'unsupported',
|
|
115
|
+
'unavailable',
|
|
116
|
+
'rate-limited',
|
|
117
|
+
'partial',
|
|
118
|
+
'cleanup-required',
|
|
119
|
+
'cancelled',
|
|
120
|
+
'invalid-input',
|
|
121
|
+
'unclassified',
|
|
122
|
+
'integrity',
|
|
123
|
+
'limit-exceeded',
|
|
124
|
+
'runtime-missing',
|
|
125
|
+
'schema-newer',
|
|
126
|
+
'owner-busy',
|
|
127
|
+
]);
|
|
87
128
|
export function validateError(value) {
|
|
88
129
|
record(value);
|
|
89
130
|
envelope(value);
|
|
90
131
|
onlyKeys(value, ['schemaVersion', 'requestId', 'ok', 'code', 'retryable', 'retryAfterMs', 'phase']);
|
|
91
|
-
requireValue(value.ok === false &&
|
|
132
|
+
requireValue(value.ok === false &&
|
|
133
|
+
typeof value.code === 'string' &&
|
|
134
|
+
errorCodes.has(value.code) &&
|
|
135
|
+
typeof value.retryable === 'boolean');
|
|
92
136
|
requireValue(!value.retryable || value.code === 'unavailable' || value.code === 'rate-limited');
|
|
93
137
|
if (value.phase !== undefined)
|
|
94
|
-
requireValue(typeof value.phase === 'string' &&
|
|
138
|
+
requireValue(typeof value.phase === 'string' &&
|
|
139
|
+
['setup', 'acquisition', 'verification', 'storage', 'inference', 'cleanup'].includes(String(value.phase)));
|
|
95
140
|
if (value.retryAfterMs !== undefined)
|
|
96
|
-
requireValue(Number.isSafeInteger(value.retryAfterMs) &&
|
|
141
|
+
requireValue(Number.isSafeInteger(value.retryAfterMs) &&
|
|
142
|
+
Number(value.retryAfterMs) >= 0 &&
|
|
143
|
+
Number(value.retryAfterMs) <= 86_400_000);
|
|
97
144
|
}
|
|
98
145
|
/** Fixed ordered tuple avoids key-order or irrelevant envelope fields changing a space. */
|
|
99
146
|
export function embeddingFingerprint(identity) {
|
|
100
|
-
const hashes = [
|
|
101
|
-
|
|
147
|
+
const hashes = [
|
|
148
|
+
identity.modelSha256,
|
|
149
|
+
identity.runtimeSha256,
|
|
150
|
+
identity.tokenizerSha256,
|
|
151
|
+
identity.queryTemplateSha256,
|
|
152
|
+
identity.documentTemplateSha256,
|
|
153
|
+
identity.preprocessingSha256,
|
|
154
|
+
];
|
|
155
|
+
requireValue(hashes.every((h) => typeof h === 'string' && digest.test(h)));
|
|
102
156
|
requireValue(Number.isSafeInteger(identity.dimensions) && identity.dimensions > 0 && identity.dimensions <= 16384);
|
|
103
157
|
requireValue(identity.metric === 'cosine' && typeof identity.normalized === 'boolean');
|
|
104
|
-
requireValue(Array.isArray(identity.modalities) &&
|
|
105
|
-
|
|
106
|
-
|
|
158
|
+
requireValue(Array.isArray(identity.modalities) &&
|
|
159
|
+
identity.modalities.length > 0 &&
|
|
160
|
+
identity.modalities.every((m) => m === 'text' || m === 'image') &&
|
|
161
|
+
new Set(identity.modalities).size === identity.modalities.length);
|
|
162
|
+
[identity.pooling, identity.quantization, identity.backend].forEach((v) => boundedString(v, 256));
|
|
163
|
+
return createHash('sha256')
|
|
164
|
+
.update(JSON.stringify([
|
|
165
|
+
'sponge-space-v1',
|
|
166
|
+
...hashes,
|
|
167
|
+
identity.dimensions,
|
|
168
|
+
[...identity.modalities].sort(),
|
|
169
|
+
identity.normalized,
|
|
170
|
+
identity.metric,
|
|
171
|
+
identity.pooling,
|
|
172
|
+
identity.quantization,
|
|
173
|
+
identity.backend,
|
|
174
|
+
]))
|
|
175
|
+
.digest('hex');
|
|
107
176
|
}
|
|
108
177
|
/** Manifest validation is admission metadata, not proof of downloaded bytes. */
|
|
109
178
|
export function validateModelManifest(value) {
|
|
@@ -145,8 +214,10 @@ export function validateJobCheckpoint(value) {
|
|
|
145
214
|
for (const key of ['jobId', 'attemptId', 'ownerIncarnation'])
|
|
146
215
|
boundedString(value[key], 128);
|
|
147
216
|
requireValue(Number.isSafeInteger(value.fence) && Number(value.fence) > 0);
|
|
148
|
-
requireValue(typeof value.stage === 'string' &&
|
|
149
|
-
|
|
217
|
+
requireValue(typeof value.stage === 'string' &&
|
|
218
|
+
['queued', 'acquiring', 'extracting', 'indexing', 'promoting', 'cleanup', 'settled'].includes(String(value.stage)));
|
|
219
|
+
requireValue(typeof value.status === 'string' &&
|
|
220
|
+
['pending', 'running', 'action-required', 'cancelled', 'failed', 'complete'].includes(String(value.status)));
|
|
150
221
|
requireValue(value.cleanup === 'settled' || value.cleanup === 'pending');
|
|
151
222
|
requireValue(value.stage !== 'settled' || value.cleanup === 'settled');
|
|
152
223
|
requireValue(value.status !== 'complete' || value.stage === 'settled');
|
|
@@ -159,8 +230,12 @@ export function validateHelperRequest(value) {
|
|
|
159
230
|
envelope(value);
|
|
160
231
|
requireValue(Buffer.byteLength(JSON.stringify(value)) <= limits.helperFrameBytes);
|
|
161
232
|
requireValue(Number.isSafeInteger(value.deadlineMs) && Number(value.deadlineMs) > 0 && Number(value.deadlineMs) <= 86_400_000);
|
|
162
|
-
requireValue(Number.isSafeInteger(value.maxInputBytes) &&
|
|
163
|
-
|
|
233
|
+
requireValue(Number.isSafeInteger(value.maxInputBytes) &&
|
|
234
|
+
Number(value.maxInputBytes) > 0 &&
|
|
235
|
+
Number(value.maxInputBytes) <= limits.temporaryMediaBytes);
|
|
236
|
+
requireValue(Number.isSafeInteger(value.maxOutputBytes) &&
|
|
237
|
+
Number(value.maxOutputBytes) > 0 &&
|
|
238
|
+
Number(value.maxOutputBytes) <= limits.helperOutputBytes);
|
|
164
239
|
const keys = ['schemaVersion', 'requestId', 'operation', 'deadlineMs', 'maxInputBytes', 'maxOutputBytes'];
|
|
165
240
|
if (value.operation === 'cancel') {
|
|
166
241
|
onlyKeys(value, [...keys, 'targetRequestId']);
|
|
@@ -181,7 +256,9 @@ export function validateHelperRequest(value) {
|
|
|
181
256
|
boundedString(value.input.ownedPath);
|
|
182
257
|
requireValue(isAbsolute(value.input.ownedPath));
|
|
183
258
|
requireValue(typeof value.input.sha256 === 'string' && digest.test(value.input.sha256));
|
|
184
|
-
requireValue(Number.isSafeInteger(value.input.bytes) &&
|
|
259
|
+
requireValue(Number.isSafeInteger(value.input.bytes) &&
|
|
260
|
+
Number(value.input.bytes) > 0 &&
|
|
261
|
+
Number(value.input.bytes) <= Number(value.maxInputBytes));
|
|
185
262
|
if (value.operation === 'embed-image')
|
|
186
263
|
requireValue(Number(value.input.bytes) <= limits.imageBytes);
|
|
187
264
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Backup fences name their holder (a process identity), so a fence left by a crashed backup can be expired on exact death proof. Additive: older rows keep NULL and stay unknown. */
|
|
2
|
+
export declare const version = 8;
|
|
3
|
+
export declare const sql = "\nALTER TABLE backup_fences ADD COLUMN owner_json TEXT;\n";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Backup fences name their holder (a process identity), so a fence left by a crashed backup can be expired on exact death proof. Additive: older rows keep NULL and stay unknown. */
|
|
2
|
+
export const version = 8;
|
|
3
|
+
export const sql = `
|
|
4
|
+
ALTER TABLE backup_fences ADD COLUMN owner_json TEXT;
|
|
5
|
+
`;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Sync readiness: local paths move to a local-only table, and titles split into captured, importer-note and user-override fields (`items.title` stays the effective title). The code step re-keys immutable objects; see docs/decisions/content-ids.md. */
|
|
2
|
+
export declare const version = 9;
|
|
3
|
+
export declare const sql = "\nCREATE TABLE local_sources(item_id TEXT PRIMARY KEY REFERENCES items(id),locator TEXT NOT NULL CHECK(locator LIKE 'file:%')) STRICT;\nALTER TABLE items ADD COLUMN title_captured TEXT NOT NULL DEFAULT '';\nALTER TABLE items ADD COLUMN title_note TEXT;\nALTER TABLE items ADD COLUMN title_override TEXT;\n";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Sync readiness: local paths move to a local-only table, and titles split into captured, importer-note and user-override fields (`items.title` stays the effective title). The code step re-keys immutable objects; see docs/decisions/content-ids.md. */
|
|
2
|
+
export const version = 9;
|
|
3
|
+
export const sql = `
|
|
4
|
+
CREATE TABLE local_sources(item_id TEXT PRIMARY KEY REFERENCES items(id),locator TEXT NOT NULL CHECK(locator LIKE 'file:%')) STRICT;
|
|
5
|
+
ALTER TABLE items ADD COLUMN title_captured TEXT NOT NULL DEFAULT '';
|
|
6
|
+
ALTER TABLE items ADD COLUMN title_note TEXT;
|
|
7
|
+
ALTER TABLE items ADD COLUMN title_override TEXT;
|
|
8
|
+
`;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Incremental semantic index: vectors are keyed by space fingerprint and content hash, so identical chunks embed once across items, revisions and generations; coverage moves from `coverage_json` to rows a capture or delete can update. `vectors` moves to a connection-private temporary table that the code step converts and drops, so the stored schema is exactly this SQL; see docs/decisions/incremental-index.md. */
|
|
2
|
+
export declare const version = 10;
|
|
3
|
+
export declare const sql = "\nCREATE TABLE embeddings(fingerprint TEXT NOT NULL REFERENCES embedding_spaces(fingerprint),content_hash TEXT NOT NULL CHECK(length(content_hash)=64),input_hash TEXT NOT NULL CHECK(length(input_hash)=64),vector BLOB NOT NULL,PRIMARY KEY(fingerprint,content_hash)) STRICT;\nCREATE TABLE index_items(generation_id TEXT NOT NULL REFERENCES index_generations(id),item_id TEXT NOT NULL REFERENCES items(id),revision_id TEXT NOT NULL REFERENCES revisions(id),PRIMARY KEY(generation_id,item_id)) STRICT,WITHOUT ROWID;\nCREATE TABLE index_inputs(generation_id TEXT NOT NULL REFERENCES index_generations(id),source_id TEXT NOT NULL,revision_id TEXT NOT NULL REFERENCES revisions(id),content_hash TEXT NOT NULL CHECK(length(content_hash)=64),PRIMARY KEY(generation_id,source_id)) STRICT;\nCREATE INDEX index_inputs_revision ON index_inputs(generation_id,revision_id);\nCREATE INDEX index_inputs_content ON index_inputs(content_hash);\nCREATE TEMP TABLE vectors_v9 AS SELECT * FROM main.vectors;\nDROP TABLE main.vectors;\n";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Incremental semantic index: vectors are keyed by space fingerprint and content hash, so identical chunks embed once across items, revisions and generations; coverage moves from `coverage_json` to rows a capture or delete can update. `vectors` moves to a connection-private temporary table that the code step converts and drops, so the stored schema is exactly this SQL; see docs/decisions/incremental-index.md. */
|
|
2
|
+
export const version = 10;
|
|
3
|
+
export const sql = `
|
|
4
|
+
CREATE TABLE embeddings(fingerprint TEXT NOT NULL REFERENCES embedding_spaces(fingerprint),content_hash TEXT NOT NULL CHECK(length(content_hash)=64),input_hash TEXT NOT NULL CHECK(length(input_hash)=64),vector BLOB NOT NULL,PRIMARY KEY(fingerprint,content_hash)) STRICT;
|
|
5
|
+
CREATE TABLE index_items(generation_id TEXT NOT NULL REFERENCES index_generations(id),item_id TEXT NOT NULL REFERENCES items(id),revision_id TEXT NOT NULL REFERENCES revisions(id),PRIMARY KEY(generation_id,item_id)) STRICT,WITHOUT ROWID;
|
|
6
|
+
CREATE TABLE index_inputs(generation_id TEXT NOT NULL REFERENCES index_generations(id),source_id TEXT NOT NULL,revision_id TEXT NOT NULL REFERENCES revisions(id),content_hash TEXT NOT NULL CHECK(length(content_hash)=64),PRIMARY KEY(generation_id,source_id)) STRICT;
|
|
7
|
+
CREATE INDEX index_inputs_revision ON index_inputs(generation_id,revision_id);
|
|
8
|
+
CREATE INDEX index_inputs_content ON index_inputs(content_hash);
|
|
9
|
+
CREATE TEMP TABLE vectors_v9 AS SELECT * FROM main.vectors;
|
|
10
|
+
DROP TABLE main.vectors;
|
|
11
|
+
`;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
/**
|
|
3
|
+
* One schema step. `sql` is immutable once released: `schema_migrations` stores its digest and every open checks it.
|
|
4
|
+
* `step` runs after `sql` in the same `BEGIN IMMEDIATE`; code steps are total per row, so one hostile row never makes a
|
|
5
|
+
* library unopenable.
|
|
6
|
+
*/
|
|
7
|
+
export interface Migration {
|
|
8
|
+
readonly version: number;
|
|
9
|
+
readonly sql: string;
|
|
10
|
+
readonly step?: (db: DatabaseSync) => void;
|
|
11
|
+
}
|
|
12
|
+
/** Every migration, in order. Add a new numbered file and append it here; never edit or reorder a released entry. */
|
|
13
|
+
export declare const migrations: readonly Migration[];
|
|
14
|
+
/** The schema version this build writes. */
|
|
15
|
+
export declare const schemaVersion: number;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { backfillUris } from '../src/identity/visibility.js';
|
|
2
|
+
import { seedChanges } from '../src/store/changes.js';
|
|
3
|
+
import * as m1 from './001.js';
|
|
4
|
+
import * as m2 from './002.js';
|
|
5
|
+
import * as m3 from './003.js';
|
|
6
|
+
import * as m4 from './004.js';
|
|
7
|
+
import * as m5 from './005.js';
|
|
8
|
+
import * as m6 from './006.js';
|
|
9
|
+
import * as m7 from './007.js';
|
|
10
|
+
import * as m8 from './008.js';
|
|
11
|
+
import * as m9 from './009.js';
|
|
12
|
+
import * as m10 from './010.js';
|
|
13
|
+
import { syncReady } from '../src/store/sync-ready.js';
|
|
14
|
+
import { incrementalIndex } from '../src/indexing/upgrade.js';
|
|
15
|
+
/** Every migration, in order. Add a new numbered file and append it here; never edit or reorder a released entry. */
|
|
16
|
+
export const migrations = Object.freeze([
|
|
17
|
+
m1,
|
|
18
|
+
m2,
|
|
19
|
+
m3,
|
|
20
|
+
m4,
|
|
21
|
+
{
|
|
22
|
+
...m5,
|
|
23
|
+
step: (db) => {
|
|
24
|
+
backfillUris(db);
|
|
25
|
+
seedChanges(db);
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
m6,
|
|
29
|
+
m7,
|
|
30
|
+
m8,
|
|
31
|
+
{ ...m9, step: syncReady },
|
|
32
|
+
{ ...m10, step: incrementalIndex },
|
|
33
|
+
].map((m) => Object.freeze({ ...m })));
|
|
34
|
+
migrations.forEach((m, i) => {
|
|
35
|
+
if (m.version !== i + 1)
|
|
36
|
+
throw new Error(`migration ${i + 1} declares version ${m.version}`);
|
|
37
|
+
});
|
|
38
|
+
/** The schema version this build writes. */
|
|
39
|
+
export const schemaVersion = migrations.length;
|