@hraness/sponge 0.2.6 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/README.md +21 -9
  2. package/bin/sponge.js +8 -7
  3. package/dist/contracts/index.js +24 -6
  4. package/dist/contracts/validate.js +106 -29
  5. package/dist/migrations/008.d.ts +3 -0
  6. package/dist/migrations/008.js +5 -0
  7. package/dist/migrations/009.d.ts +3 -0
  8. package/dist/migrations/009.js +8 -0
  9. package/dist/migrations/010.d.ts +3 -0
  10. package/dist/migrations/010.js +11 -0
  11. package/dist/migrations/index.d.ts +15 -0
  12. package/dist/migrations/index.js +39 -0
  13. package/dist/src/acquisition/capture.js +87 -28
  14. package/dist/src/acquisition/cleanup.d.ts +0 -4
  15. package/dist/src/acquisition/cleanup.js +3 -5
  16. package/dist/src/acquisition/errors.js +17 -2
  17. package/dist/src/acquisition/files.d.ts +0 -1
  18. package/dist/src/acquisition/files.js +14 -11
  19. package/dist/src/acquisition/index.d.ts +2 -3
  20. package/dist/src/acquisition/index.js +1 -2
  21. package/dist/src/acquisition/lock.js +1 -1
  22. package/dist/src/acquisition/normalize.js +57 -18
  23. package/dist/src/acquisition/pins.js +22 -4
  24. package/dist/src/acquisition/process.js +67 -29
  25. package/dist/src/acquisition/runtime.d.ts +5 -7
  26. package/dist/src/acquisition/runtime.js +129 -68
  27. package/dist/src/acquisition/verify-helper.js +10 -3
  28. package/dist/src/annotations/quote.js +40 -12
  29. package/dist/src/annotations/store.d.ts +0 -1
  30. package/dist/src/annotations/store.js +66 -14
  31. package/dist/src/app/article.d.ts +2 -1
  32. package/dist/src/app/article.js +66 -70
  33. package/dist/src/app/bundle-files.d.ts +0 -4
  34. package/dist/src/app/bundle-files.js +11 -14
  35. package/dist/src/app/markdown.js +18 -6
  36. package/dist/src/app/source.d.ts +0 -4
  37. package/dist/src/app/source.js +18 -14
  38. package/dist/src/cli/arguments.d.ts +2 -8
  39. package/dist/src/cli/arguments.js +67 -80
  40. package/dist/src/cli/commands.d.ts +42 -0
  41. package/dist/src/cli/commands.js +795 -0
  42. package/dist/src/cli/config.js +12 -9
  43. package/dist/src/cli/digest.d.ts +1 -1
  44. package/dist/src/cli/digest.js +38 -16
  45. package/dist/src/cli/import.js +25 -4
  46. package/dist/src/cli/indexing.d.ts +2 -0
  47. package/dist/src/cli/indexing.js +26 -9
  48. package/dist/src/cli/main.js +47 -386
  49. package/dist/src/cli/portability.d.ts +0 -2
  50. package/dist/src/cli/portability.js +13 -8
  51. package/dist/src/cli/queue.js +12 -8
  52. package/dist/src/cli/runtimes.d.ts +1 -3
  53. package/dist/src/cli/runtimes.js +13 -6
  54. package/dist/src/cli/terminal.js +1 -1
  55. package/dist/src/cli/worker.d.ts +7 -0
  56. package/dist/src/cli/worker.js +54 -15
  57. package/dist/src/derivations/kinds.d.ts +34 -0
  58. package/dist/src/derivations/kinds.js +14 -0
  59. package/dist/src/derivations/types.d.ts +48 -0
  60. package/dist/src/derivations/types.js +1 -0
  61. package/dist/src/digest/derivation.d.ts +25 -0
  62. package/dist/src/digest/derivation.js +124 -0
  63. package/dist/src/digest/editorial.d.ts +9 -46
  64. package/dist/src/digest/editorial.js +57 -49
  65. package/dist/src/digest/endpoint.js +63 -27
  66. package/dist/src/digest/pipeline.d.ts +49 -28
  67. package/dist/src/digest/pipeline.js +369 -102
  68. package/dist/src/digest/schema.d.ts +11 -25
  69. package/dist/src/digest/schema.js +86 -18
  70. package/dist/src/errors.d.ts +46 -0
  71. package/dist/src/errors.js +117 -0
  72. package/dist/src/identity/query-policy.d.ts +0 -5
  73. package/dist/src/identity/query-policy.js +61 -9
  74. package/dist/src/identity/uri.d.ts +6 -5
  75. package/dist/src/identity/uri.js +37 -18
  76. package/dist/src/identity/visibility.d.ts +11 -1
  77. package/dist/src/identity/visibility.js +81 -17
  78. package/dist/src/importers/apply.js +104 -36
  79. package/dist/src/importers/custody.js +27 -9
  80. package/dist/src/importers/inspect.js +125 -38
  81. package/dist/src/importers/jungle-digest.d.ts +3 -6
  82. package/dist/src/importers/jungle-digest.js +196 -69
  83. package/dist/src/importers/jungle-store.d.ts +19 -6
  84. package/dist/src/importers/jungle-store.js +124 -28
  85. package/dist/src/importers/jungle.d.ts +14 -14
  86. package/dist/src/importers/jungle.js +142 -31
  87. package/dist/src/importers/manifest.js +57 -21
  88. package/dist/src/importers/verify-helper.js +18 -4
  89. package/dist/src/indexing/image.js +29 -10
  90. package/dist/src/indexing/index.d.ts +37 -6
  91. package/dist/src/indexing/index.js +360 -124
  92. package/dist/src/indexing/jobs.js +156 -47
  93. package/dist/src/indexing/prune-run.d.ts +2 -0
  94. package/dist/src/indexing/prune-run.js +69 -26
  95. package/dist/src/indexing/prune.d.ts +12 -3
  96. package/dist/src/indexing/prune.js +85 -35
  97. package/dist/src/indexing/runner.d.ts +2 -1
  98. package/dist/src/indexing/runner.js +83 -40
  99. package/dist/src/indexing/upgrade.d.ts +9 -0
  100. package/dist/src/indexing/upgrade.js +79 -0
  101. package/dist/src/inference/asr/bridge.js +6 -2
  102. package/dist/src/inference/asr/protocol.js +109 -19
  103. package/dist/src/inference/asr/runtime.js +58 -21
  104. package/dist/src/inference/asr/windows.js +9 -5
  105. package/dist/src/inference/embedding/index.d.ts +2 -6
  106. package/dist/src/inference/embedding/index.js +89 -29
  107. package/dist/src/ingest/attempt.d.ts +29 -0
  108. package/dist/src/ingest/attempt.js +55 -0
  109. package/dist/src/media/capture-helper.js +7 -2
  110. package/dist/src/media/capture.d.ts +2 -1
  111. package/dist/src/media/capture.js +124 -43
  112. package/dist/src/media/handler.d.ts +4 -2
  113. package/dist/src/media/handler.js +88 -70
  114. package/dist/src/media/transcribe.js +63 -21
  115. package/dist/src/metadata/article.js +49 -17
  116. package/dist/src/models/artifacts.d.ts +10 -8
  117. package/dist/src/models/artifacts.js +237 -106
  118. package/dist/src/models/catalog.d.ts +2 -5
  119. package/dist/src/models/catalog.js +94 -25
  120. package/dist/src/models/download.js +55 -15
  121. package/dist/src/models/runtime.d.ts +0 -2
  122. package/dist/src/models/runtime.js +69 -33
  123. package/dist/src/pdf/capture-helper.js +3 -1
  124. package/dist/src/pdf/capture.d.ts +2 -1
  125. package/dist/src/pdf/capture.js +95 -27
  126. package/dist/src/pdf/errors.js +12 -1
  127. package/dist/src/pdf/handler.d.ts +2 -1
  128. package/dist/src/pdf/handler.js +70 -71
  129. package/dist/src/pdf/index.d.ts +1 -3
  130. package/dist/src/pdf/index.js +0 -2
  131. package/dist/src/pdf/input.js +12 -7
  132. package/dist/src/pdf/normalize.js +80 -21
  133. package/dist/src/pdf/source-identity.js +3 -2
  134. package/dist/src/pdf/source.js +12 -8
  135. package/dist/src/pdf/verify-helper.js +15 -3
  136. package/dist/src/portability/backup.d.ts +2 -9
  137. package/dist/src/portability/backup.js +106 -22
  138. package/dist/src/portability/database.js +51 -17
  139. package/dist/src/portability/export.js +35 -9
  140. package/dist/src/portability/files.d.ts +1 -4
  141. package/dist/src/portability/files.js +127 -67
  142. package/dist/src/portability/gc.js +65 -22
  143. package/dist/src/publish/component.js +1 -1
  144. package/dist/src/publish/feed.d.ts +2 -2
  145. package/dist/src/publish/feed.js +75 -29
  146. package/dist/src/publish/identity.d.ts +5 -1
  147. package/dist/src/publish/identity.js +31 -11
  148. package/dist/src/publish/index.d.ts +2 -3
  149. package/dist/src/publish/index.js +34 -21
  150. package/dist/src/publish/projection.d.ts +2 -9
  151. package/dist/src/publish/projection.js +21 -14
  152. package/dist/src/search/extension.js +3 -1
  153. package/dist/src/search/hybrid.d.ts +3 -8
  154. package/dist/src/search/hybrid.js +45 -11
  155. package/dist/src/search/lexical.js +18 -4
  156. package/dist/src/search/query.js +56 -25
  157. package/dist/src/search/vector.js +37 -10
  158. package/dist/src/service/launch-agent.d.ts +5 -3
  159. package/dist/src/service/launch-agent.js +272 -83
  160. package/dist/src/service/lifetime.js +47 -14
  161. package/dist/src/service/loop.d.ts +3 -3
  162. package/dist/src/service/loop.js +47 -19
  163. package/dist/src/store/annotations.d.ts +32 -0
  164. package/dist/src/store/annotations.js +44 -0
  165. package/dist/src/store/backups.d.ts +18 -0
  166. package/dist/src/store/backups.js +110 -0
  167. package/dist/src/store/blobs.d.ts +24 -0
  168. package/dist/src/store/blobs.js +139 -0
  169. package/dist/src/store/changes.d.ts +5 -12
  170. package/dist/src/store/changes.js +108 -56
  171. package/dist/src/store/context.d.ts +15 -0
  172. package/dist/src/store/context.js +19 -0
  173. package/dist/src/store/derivations.d.ts +42 -0
  174. package/dist/src/store/derivations.js +104 -0
  175. package/dist/src/store/files.d.ts +0 -2
  176. package/dist/src/store/files.js +19 -24
  177. package/dist/src/store/ids.d.ts +30 -0
  178. package/dist/src/store/ids.js +34 -0
  179. package/dist/src/store/imports.d.ts +26 -0
  180. package/dist/src/store/imports.js +57 -0
  181. package/dist/src/store/index.d.ts +3 -186
  182. package/dist/src/store/index.js +2 -809
  183. package/dist/src/store/lease.d.ts +20 -0
  184. package/dist/src/store/lease.js +62 -0
  185. package/dist/src/store/library.d.ts +75 -0
  186. package/dist/src/store/library.js +174 -0
  187. package/dist/src/store/open.d.ts +7 -0
  188. package/dist/src/store/open.js +183 -0
  189. package/dist/src/store/queue.d.ts +65 -0
  190. package/dist/src/store/queue.js +327 -0
  191. package/dist/src/store/revisions.d.ts +12 -0
  192. package/dist/src/store/revisions.js +189 -0
  193. package/dist/src/store/search-index.d.ts +63 -0
  194. package/dist/src/store/search-index.js +190 -0
  195. package/dist/src/store/sync-ready.d.ts +6 -0
  196. package/dist/src/store/sync-ready.js +121 -0
  197. package/dist/src/store/types.d.ts +1 -5
  198. package/dist/src/store/types.js +1 -8
  199. package/dist/src/util/canonical.d.ts +2 -0
  200. package/dist/src/util/canonical.js +17 -0
  201. package/dist/src/util/exact.d.ts +2 -0
  202. package/dist/src/util/exact.js +9 -0
  203. package/dist/src/util/fsync.d.ts +4 -0
  204. package/dist/src/util/fsync.js +22 -0
  205. package/dist/src/util/hash.d.ts +4 -0
  206. package/dist/src/util/hash.js +8 -0
  207. package/dist/src/worker/basic.d.ts +0 -1
  208. package/dist/src/worker/basic.js +23 -7
  209. package/dist/src/worker/custody.d.ts +4 -1
  210. package/dist/src/worker/custody.js +111 -38
  211. package/dist/src/worker/engine.d.ts +2 -1
  212. package/dist/src/worker/engine.js +42 -20
  213. package/dist/src/worker/identity.js +13 -5
  214. package/dist/src/worker/journal.d.ts +1 -1
  215. package/dist/src/worker/journal.js +54 -24
  216. package/dist/src/worker/lock.d.ts +18 -0
  217. package/dist/src/worker/lock.js +196 -0
  218. package/dist/src/worker/runtime-gc.d.ts +4 -3
  219. package/dist/src/worker/runtime-gc.js +68 -41
  220. package/dist/src/worker/supervisor-child.js +19 -6
  221. package/dist/src/worker/supervisor.js +41 -8
  222. package/native/runtime-manifest.json +4 -4
  223. package/package.json +10 -2
  224. package/dist/src/digest/store.d.ts +0 -57
  225. package/dist/src/digest/store.js +0 -79
  226. package/dist/src/store/foundation.d.ts +0 -7
  227. 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 GENERATION
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. Model changes create separate index generations; a new one
102
- becomes active only after it covers the current library. `index rollback
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
- vectors of cancelled, failed and retired generations, keeping the active and any
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 preserve revisions,
150
- assets, reading state and notes. Restore requires a new directory, verifies every
151
- file and leaves connections unresolved; runtimes and credentials are excluded.
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 an upgrade, reinstall the worker to update that
160
- binding. Uninstall stops the worker and preserves the library. In-session startup
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');process.exitCode=2;
6
- }else{
7
- const {version}=createRequire(import.meta.url)('../package.json');
8
- const {main}=await import('../dist/src/cli/main.js');
9
- process.exitCode=await main(process.argv.slice(2),version);
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
  }
@@ -1,6 +1,24 @@
1
- export const exitCodes = { success: 0, failure: 1, 'invalid-input': 2, partial: 3, 'action-required': 4, cancelled: 130 };
2
- export const limits = Object.freeze({ pdfBytes: 100 * 1024 ** 2, imageBytes: 20 * 1024 ** 2,
3
- imagePixels: 40_000_000, imagesPerItem: 200, mediaDurationMs: 3_600_000,
4
- temporaryMediaBytes: 1024 ** 3, stagingBytes: 2 * 1024 ** 3, freeReserveBytes: 2 * 1024 ** 3,
5
- metadataBytes: 1024 ** 2, metadataClaims: 512, markdownBytes: 16 * 1024 ** 2,
6
- captureDeadlineMs: 120_000, helperFrameBytes: 64 * 1024, helperOutputBytes: 16 * 1024 ** 2 });
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) { if (!ok)
6
- throw new Error('invalid-contract'); }
7
- function record(value) { requireValue(value !== null && typeof value === 'object' && !Array.isArray(value)); }
8
- function boundedString(value, max = 4096) { requireValue(typeof value === 'string' && value.length > 0 && value.length <= max && !value.includes('\0')); }
9
- function onlyKeys(value, keys) { requireValue(Object.keys(value).every(key => keys.includes(key))); }
10
- function envelope(value) { requireValue(value.schemaVersion === 1); boundedString(value.requestId, 128); }
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('/') && !value.relativePath.includes('\\') && !value.relativePath.includes(':') && value.relativePath.split('/').every(p => p !== '' && p !== '.' && p !== '..'));
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 => { validateAsset(asset); requireValue(asset.mediaType.startsWith('image/') && asset.bytes <= limits.imageBytes); });
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' && ['complete', 'partial', 'not-requested'].includes(String(value.completeness.images)));
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) && value.metadata.length <= limits.metadataClaims && Buffer.byteLength(JSON.stringify(value.metadata)) <= limits.metadataBytes);
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) && value.length <= 5 && new Set(value).size === value.length && value.every(v => typeof v === 'string' && ['input-limit', 'claim-limit', 'value-limit', 'json-limit', 'json-invalid'].includes(v)));
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' && ['html', 'opengraph', 'twitter', 'jsonld', 'pdf', 'media'].includes(String(claim.source)));
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(['grant-permission', 'repair-auth', 'unsupported', 'unavailable', 'rate-limited', 'partial', 'cleanup-required', 'cancelled', 'invalid-input', 'unclassified', 'integrity', 'limit-exceeded', 'runtime-missing', 'schema-newer', 'owner-busy']);
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 && typeof value.code === 'string' && errorCodes.has(value.code) && typeof value.retryable === 'boolean');
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' && ['setup', 'acquisition', 'verification', 'storage', 'inference', 'cleanup'].includes(String(value.phase)));
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) && Number(value.retryAfterMs) >= 0 && Number(value.retryAfterMs) <= 86_400_000);
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 = [identity.modelSha256, identity.runtimeSha256, identity.tokenizerSha256, identity.queryTemplateSha256, identity.documentTemplateSha256, identity.preprocessingSha256];
101
- requireValue(hashes.every(h => typeof h === 'string' && digest.test(h)));
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) && identity.modalities.length > 0 && identity.modalities.every(m => m === 'text' || m === 'image') && new Set(identity.modalities).size === identity.modalities.length);
105
- [identity.pooling, identity.quantization, identity.backend].forEach(v => boundedString(v, 256));
106
- return createHash('sha256').update(JSON.stringify(['sponge-space-v1', ...hashes, identity.dimensions, [...identity.modalities].sort(), identity.normalized, identity.metric, identity.pooling, identity.quantization, identity.backend])).digest('hex');
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' && ['queued', 'acquiring', 'extracting', 'indexing', 'promoting', 'cleanup', 'settled'].includes(String(value.stage)));
149
- requireValue(typeof value.status === 'string' && ['pending', 'running', 'action-required', 'cancelled', 'failed', 'complete'].includes(String(value.status)));
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) && Number(value.maxInputBytes) > 0 && Number(value.maxInputBytes) <= limits.temporaryMediaBytes);
163
- requireValue(Number.isSafeInteger(value.maxOutputBytes) && Number(value.maxOutputBytes) > 0 && Number(value.maxOutputBytes) <= limits.helperOutputBytes);
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) && Number(value.input.bytes) > 0 && Number(value.input.bytes) <= Number(value.maxInputBytes));
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;