sealkeep 0.9.0 → 0.11.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/ARCHITECTURE.md +163 -14
- package/CHANGELOG.md +223 -1
- package/CONTROL_PLANE.md +2 -2
- package/LICENSE +1 -1
- package/README.md +108 -22
- package/THIRD_PARTY.md +2 -2
- package/THREAT_MODEL.md +23 -4
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/chunk-access.d.ts +26 -4
- package/dist/packages/sealkeep-crypto/src/chunk-access.js +219 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/envelope.d.ts +11 -1
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/envelope.js +58 -21
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/format.d.ts +2 -2
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/index.d.ts +1 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/index.js +1 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/recipients.js +4 -4
- package/dist/packages/sealkeep-crypto/src/squeeze.d.ts +6 -0
- package/dist/packages/sealkeep-crypto/src/squeeze.js +39 -0
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/stream.d.ts +36 -3
- package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/stream.js +196 -37
- package/dist/site/index.html +1808 -1904
- package/dist/site/llms.txt +67 -0
- package/dist/site/trust/architecture-data-flow.html +53 -0
- package/dist/site/trust/audit-roadmap.html +37 -0
- package/dist/site/trust/deployment-responsibility.html +11 -0
- package/dist/site/trust/dpa-sample.html +30 -0
- package/dist/site/trust/release-provenance.html +21 -0
- package/dist/site/trust/subprocessors.html +15 -0
- package/dist/site/trust/threat-model.html +34 -0
- package/dist/site/trust/toms.html +41 -0
- package/dist/site/trust-document.css +32 -0
- package/dist/site/trust.html +73 -0
- package/dist/site/visual/assets/index-8Nxnkc7o.js +207 -0
- package/dist/site/visual/assets/index-oxLy1bpy.css +1 -0
- package/dist/site/visual/index.html +18 -0
- package/dist/site.zip +0 -0
- package/dist/src/activity.d.ts +9 -0
- package/dist/src/activity.js +90 -1
- package/dist/src/adapters.d.ts +175 -5
- package/dist/src/adapters.js +961 -73
- package/dist/src/agent-context.d.ts +135 -0
- package/dist/src/agent-context.js +1059 -0
- package/dist/src/archive-copies.d.ts +47 -0
- package/dist/src/archive-copies.js +179 -0
- package/dist/src/audit.d.ts +1 -1
- package/dist/src/audit.js +29 -4
- package/dist/src/autopilot.d.ts +52 -7
- package/dist/src/autopilot.js +143 -25
- package/dist/src/background-bandwidth.d.ts +46 -0
- package/dist/src/background-bandwidth.js +301 -0
- package/dist/src/background-cpu.d.ts +82 -0
- package/dist/src/background-cpu.js +212 -0
- package/dist/src/background-worker-error.d.ts +12 -0
- package/dist/src/background-worker-error.js +18 -0
- package/dist/src/branding.d.ts +24 -1
- package/dist/src/branding.js +31 -1
- package/dist/src/bridge.d.ts +233 -0
- package/dist/src/bridge.js +604 -0
- package/dist/src/byte-stream.d.ts +91 -0
- package/dist/src/byte-stream.js +385 -0
- package/dist/src/chunk-store.d.ts +41 -8
- package/dist/src/chunk-store.js +161 -65
- package/dist/src/cli.js +1698 -163
- package/dist/src/cloud.d.ts +841 -31
- package/dist/src/cloud.js +3196 -277
- package/dist/src/context-background.d.ts +37 -0
- package/dist/src/context-background.js +309 -0
- package/dist/src/context-drain-child.d.ts +1 -0
- package/dist/src/context-drain-child.js +98 -0
- package/dist/src/context-reader.d.ts +118 -0
- package/dist/src/context-reader.js +447 -0
- package/dist/src/control-plane/auth.d.ts +32 -4
- package/dist/src/control-plane/auth.js +85 -24
- package/dist/src/control-plane/server.js +19 -6
- package/dist/src/control-plane.d.ts +17 -1
- package/dist/src/control-plane.js +32 -6
- package/dist/src/crypto.d.ts +1 -1
- package/dist/src/crypto.js +5 -5
- package/dist/src/daemon-lease.d.ts +70 -0
- package/dist/src/daemon-lease.js +420 -0
- package/dist/src/daemon.d.ts +94 -1
- package/dist/src/daemon.js +1082 -105
- package/dist/src/darwin-service-policy.d.ts +41 -0
- package/dist/src/darwin-service-policy.js +60 -0
- package/dist/src/dashboard-cli.js +15 -15
- package/dist/src/device-authorization.d.ts +37 -0
- package/dist/src/device-authorization.js +199 -0
- package/dist/src/device-enrollment.d.ts +91 -0
- package/dist/src/device-enrollment.js +349 -0
- package/dist/src/disk.d.ts +17 -12
- package/dist/src/disk.js +43 -17
- package/dist/src/doctor.d.ts +35 -1
- package/dist/src/doctor.js +316 -41
- package/dist/src/durable-ticket-lock.d.ts +24 -0
- package/dist/src/durable-ticket-lock.js +232 -0
- package/dist/src/enroll.d.ts +1 -1
- package/dist/src/enroll.js +13 -7
- package/dist/src/env.d.ts +10 -1
- package/dist/src/env.js +11 -3
- package/dist/src/errors.d.ts +8 -8
- package/dist/src/errors.js +6 -6
- package/dist/src/flush.d.ts +12 -0
- package/dist/src/flush.js +37 -0
- package/dist/src/heartbeat.d.ts +86 -12
- package/dist/src/heartbeat.js +415 -29
- package/dist/src/index-background-watchdog.d.ts +1 -0
- package/dist/src/index-background-watchdog.js +94 -0
- package/dist/src/index-background-work.d.ts +21 -0
- package/dist/src/index-background-work.js +25 -0
- package/dist/src/index-background.d.ts +64 -0
- package/dist/src/index-background.js +394 -0
- package/dist/src/index-build-child.d.ts +1 -0
- package/dist/src/index-build-child.js +109 -0
- package/dist/src/index-manifest.d.ts +52 -0
- package/dist/src/index-manifest.js +444 -0
- package/dist/src/index-publication-proof.d.ts +84 -0
- package/dist/src/index-publication-proof.js +380 -0
- package/dist/src/index-publication-state.d.ts +149 -0
- package/dist/src/index-publication-state.js +696 -0
- package/dist/src/index-publication-verifier.d.ts +89 -0
- package/dist/src/index-publication-verifier.js +341 -0
- package/dist/src/index-publish.d.ts +62 -0
- package/dist/src/index-publish.js +540 -0
- package/dist/src/index-scratch-cleanup.d.ts +19 -0
- package/dist/src/index-scratch-cleanup.js +166 -0
- package/dist/src/index-segment-types.d.ts +132 -0
- package/dist/src/index-segment-types.js +21 -0
- package/dist/src/index-segments.d.ts +9 -0
- package/dist/src/index-segments.js +516 -0
- package/dist/src/index-store.d.ts +123 -0
- package/dist/src/index-store.js +495 -0
- package/dist/src/index-sync.d.ts +91 -15
- package/dist/src/index-sync.js +286 -53
- package/dist/src/index-upgrade-publication.d.ts +30 -0
- package/dist/src/index-upgrade-publication.js +179 -0
- package/dist/src/integration-manager.d.ts +32 -0
- package/dist/src/integration-manager.js +394 -0
- package/dist/src/leakscan.js +1 -1
- package/dist/src/local-api.d.ts +56 -15
- package/dist/src/local-api.js +4979 -541
- package/dist/src/machine-settings.d.ts +51 -0
- package/dist/src/machine-settings.js +166 -0
- package/dist/src/managed-chunks.d.ts +5 -2
- package/dist/src/managed-chunks.js +14 -14
- package/dist/src/mcp-install.d.ts +11 -9
- package/dist/src/mcp-install.js +73 -28
- package/dist/src/mcp-workspace.d.ts +18 -0
- package/dist/src/mcp-workspace.js +50 -0
- package/dist/src/mcp.js +294 -25
- package/dist/src/migrate.js +27 -21
- package/dist/src/notify.d.ts +1 -1
- package/dist/src/notify.js +5 -5
- package/dist/src/offload.d.ts +201 -14
- package/dist/src/offload.js +1848 -140
- package/dist/src/onboarding.d.ts +8 -1
- package/dist/src/onboarding.js +4 -4
- package/dist/src/packages.d.ts +2 -2
- package/dist/src/packages.js +10 -2
- package/dist/src/passkey.d.ts +0 -1
- package/dist/src/passkey.js +2 -7
- package/dist/src/password-lock.d.ts +2 -2
- package/dist/src/password-lock.js +6 -6
- package/dist/src/paths.d.ts +2 -0
- package/dist/src/paths.js +2 -0
- package/dist/src/presence.d.ts +86 -0
- package/dist/src/presence.js +240 -0
- package/dist/src/progress-deadline.d.ts +21 -0
- package/dist/src/progress-deadline.js +91 -0
- package/dist/src/project-repair.d.ts +55 -0
- package/dist/src/project-repair.js +131 -0
- package/dist/src/providers/gcs.d.ts +28 -7
- package/dist/src/providers/gcs.js +35 -24
- package/dist/src/providers/gdrive.d.ts +71 -8
- package/dist/src/providers/gdrive.js +223 -50
- package/dist/src/providers/index.d.ts +11 -3
- package/dist/src/providers/index.js +30 -10
- package/dist/src/providers/s3.d.ts +30 -8
- package/dist/src/providers/s3.js +41 -30
- package/dist/src/providers/safe-storage-fetch.d.ts +12 -0
- package/dist/src/providers/safe-storage-fetch.js +72 -0
- package/dist/src/queue.d.ts +136 -19
- package/dist/src/queue.js +862 -96
- package/dist/src/reclaim-transaction.d.ts +156 -0
- package/dist/src/reclaim-transaction.js +1027 -0
- package/dist/src/recovery-codes.d.ts +32 -0
- package/dist/src/recovery-codes.js +338 -0
- package/dist/src/recovery.js +12 -9
- package/dist/src/rehydrate.d.ts +25 -22
- package/dist/src/rehydrate.js +319 -23
- package/dist/src/restore.d.ts +57 -4
- package/dist/src/restore.js +272 -36
- package/dist/src/resume-stub.d.ts +92 -0
- package/dist/src/resume-stub.js +417 -0
- package/dist/src/retention.d.ts +98 -7
- package/dist/src/retention.js +1066 -62
- package/dist/src/rotate.js +3 -3
- package/dist/src/search.d.ts +566 -8
- package/dist/src/search.js +5897 -290
- package/dist/src/secrets.d.ts +51 -7
- package/dist/src/secrets.js +316 -24
- package/dist/src/service.d.ts +49 -11
- package/dist/src/service.js +776 -35
- package/dist/src/share.js +3 -3
- package/dist/src/shared-spaces.d.ts +98 -0
- package/dist/src/shared-spaces.js +214 -0
- package/dist/src/source-reader.d.ts +73 -0
- package/dist/src/source-reader.js +715 -0
- package/dist/src/spool.d.ts +1 -1
- package/dist/src/spool.js +1 -1
- package/dist/src/start-tui.js +2 -1
- package/dist/src/start.js +2 -2
- package/dist/src/storage-endpoint.d.ts +21 -0
- package/dist/src/storage-endpoint.js +122 -0
- package/dist/src/storage-setup.js +12 -12
- package/dist/src/storage-targets.d.ts +109 -6
- package/dist/src/storage-targets.js +975 -67
- package/dist/src/stream-to-cloud.d.ts +5 -1
- package/dist/src/stream-to-cloud.js +34 -14
- package/dist/src/sync-rules.d.ts +31 -6
- package/dist/src/sync-rules.js +153 -14
- package/dist/src/team-backfill-scheduling.d.ts +8 -0
- package/dist/src/team-backfill-scheduling.js +33 -0
- package/dist/src/team-backfill.d.ts +116 -0
- package/dist/src/team-backfill.js +1429 -0
- package/dist/src/team-index-cache.d.ts +16 -0
- package/dist/src/team-index-cache.js +152 -0
- package/dist/src/team-offboarding.d.ts +38 -0
- package/dist/src/team-offboarding.js +1043 -0
- package/dist/src/team-presence.d.ts +127 -0
- package/dist/src/team-presence.js +904 -0
- package/dist/src/team-publication-policy.d.ts +20 -0
- package/dist/src/team-publication-policy.js +140 -0
- package/dist/src/team-realtime.d.ts +68 -0
- package/dist/src/team-realtime.js +816 -0
- package/dist/src/team-source-facts-cache.d.ts +23 -0
- package/dist/src/team-source-facts-cache.js +255 -0
- package/dist/src/trash.d.ts +1 -1
- package/dist/src/trash.js +2 -2
- package/dist/src/tui.js +11 -12
- package/dist/src/types.d.ts +173 -7
- package/dist/src/types.js +20 -0
- package/dist/src/ui-server.d.ts +163 -35
- package/dist/src/ui-server.js +712 -72
- package/dist/src/ui.d.ts +1 -2
- package/dist/src/ui.js +1 -2
- package/dist/src/upload.d.ts +27 -0
- package/dist/src/upload.js +383 -43
- package/dist/src/vault.d.ts +226 -30
- package/dist/src/vault.js +1776 -192
- package/dist/src/watcher.d.ts +7 -1
- package/dist/src/watcher.js +198 -55
- package/dist/src/worker.d.ts +27 -3
- package/dist/src/worker.js +274 -55
- package/package.json +33 -12
- package/scripts/native-reboot-rehearsal.mjs +90 -0
- package/web/app.js +6032 -343
- package/web/bootstrap.js +17 -0
- package/web/index.html +255 -57
- package/web/rail.js +317 -40
- package/web/retention.html +2 -2
- package/web/rules-view.js +188 -16
- package/web/sessions-view.js +485 -62
- package/web/sessions.html +2 -2
- package/web/setup-api.js +152 -29
- package/web/setup-logic.js +68 -9
- package/web/setup.html +113 -44
- package/web/setup.js +604 -71
- package/web/style.css +513 -98
- package/dist/packages/vaultline-crypto/src/chunk-access.js +0 -93
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/aead.d.ts +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/aead.js +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/format.js +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/recipients.d.ts +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/sha256-stream.d.ts +0 -0
- /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/sha256-stream.js +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { lstat, mkdir, open, readdir, rmdir, unlink } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fail } from "./errors.js";
|
|
4
|
+
const UUID = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
5
|
+
const SCRATCH_GENERATION = new RegExp(`^${UUID}$`);
|
|
6
|
+
const OWNED_SCRATCH_FILE = new RegExp(`^(?:\\.content-index\\.${UUID}\\.jsonl|\\.scoped-index\\.${UUID}\\.jsonl|\\.vlbody-${UUID}`
|
|
7
|
+
+ `|content-index(?:\\.[0-9a-f]{16})?\\.skindex\\.${UUID}\\.tmp)$`);
|
|
8
|
+
function errorCode(error) {
|
|
9
|
+
return typeof error === "object" && error !== null && "code" in error
|
|
10
|
+
? String(error.code)
|
|
11
|
+
: undefined;
|
|
12
|
+
}
|
|
13
|
+
function validateGeneration(generation) {
|
|
14
|
+
if (!SCRATCH_GENERATION.test(generation)) {
|
|
15
|
+
fail("invalid_argument", "A historical-index scratch generation must be a canonical lowercase UUID");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* One daemon-owned historical child gets one private scratch namespace.
|
|
20
|
+
* Foreground/manual builders deliberately keep using the ordinary index
|
|
21
|
+
* directory, so recovery of a dead child can never unlink their live files.
|
|
22
|
+
*/
|
|
23
|
+
export function backgroundIndexScratchDirectory(dataDir, generation) {
|
|
24
|
+
validateGeneration(generation);
|
|
25
|
+
const indexDirectory = resolve(dataDir, "index");
|
|
26
|
+
const scratchDirectory = resolve(indexDirectory, `.background-index-scratch.${generation}`);
|
|
27
|
+
if (dirname(scratchDirectory) !== indexDirectory) {
|
|
28
|
+
fail("internal", "Historical-index scratch escaped its owned index directory");
|
|
29
|
+
}
|
|
30
|
+
return scratchDirectory;
|
|
31
|
+
}
|
|
32
|
+
function ownedByThisUser(info) {
|
|
33
|
+
return typeof process.getuid !== "function" || info.uid === process.getuid();
|
|
34
|
+
}
|
|
35
|
+
function privateDirectory(info) {
|
|
36
|
+
return process.platform === "win32" || (Number(info.mode) & 0o077) === 0;
|
|
37
|
+
}
|
|
38
|
+
function ownedRegularFile(info) {
|
|
39
|
+
return info.isFile() && !info.isSymbolicLink() && ownedByThisUser(info) && Number(info.nlink) === 1;
|
|
40
|
+
}
|
|
41
|
+
function concurrentlyUnlinkedOwnedFile(info) {
|
|
42
|
+
return info.isFile() && !info.isSymbolicLink() && ownedByThisUser(info) && Number(info.nlink) === 0;
|
|
43
|
+
}
|
|
44
|
+
async function syncDirectory(path) {
|
|
45
|
+
let handle;
|
|
46
|
+
try {
|
|
47
|
+
handle = await open(path, "r");
|
|
48
|
+
await handle.sync();
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
// Windows does not support opening directories. POSIX must make the
|
|
52
|
+
// privacy cleanup durable before its child barrier can be retired.
|
|
53
|
+
if (process.platform !== "win32" && errorCode(error) !== "ENOENT")
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
await handle?.close().catch(() => undefined);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Create or reopen only the exact namespace assigned to this worker. */
|
|
61
|
+
export async function ensureBackgroundIndexScratch(dataDir, generation) {
|
|
62
|
+
const indexDirectory = resolve(dataDir, "index");
|
|
63
|
+
const scratchDirectory = backgroundIndexScratchDirectory(dataDir, generation);
|
|
64
|
+
await mkdir(indexDirectory, { recursive: true, mode: 0o700 });
|
|
65
|
+
try {
|
|
66
|
+
await mkdir(scratchDirectory, { mode: 0o700 });
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (errorCode(error) !== "EEXIST")
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
const info = await lstat(scratchDirectory);
|
|
73
|
+
if (!info.isDirectory() || info.isSymbolicLink() || !ownedByThisUser(info) || !privateDirectory(info)) {
|
|
74
|
+
fail("internal", "Historical-index scratch is not an owned private directory");
|
|
75
|
+
}
|
|
76
|
+
return scratchDirectory;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Remove scratch for one process generation after its durable child barrier
|
|
80
|
+
* has proved that exact worker dead. The caller owns that proof; this helper
|
|
81
|
+
* intentionally cannot scan legacy/global names because another foreground
|
|
82
|
+
* builder may be using them.
|
|
83
|
+
*
|
|
84
|
+
* Preflight every entry before deleting any. An unknown name, symlink or
|
|
85
|
+
* directory leaves the entire namespace intact for diagnosis. Concurrent
|
|
86
|
+
* successor contenders may safely repeat the same cleanup.
|
|
87
|
+
*/
|
|
88
|
+
export async function cleanupInterruptedIndexScratch(dataDir, generation) {
|
|
89
|
+
const scratchDirectory = backgroundIndexScratchDirectory(dataDir, generation);
|
|
90
|
+
let directoryInfo;
|
|
91
|
+
try {
|
|
92
|
+
directoryInfo = await lstat(scratchDirectory);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (errorCode(error) === "ENOENT") {
|
|
96
|
+
await syncDirectory(resolve(dataDir, "index"));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
if (!directoryInfo.isDirectory() || directoryInfo.isSymbolicLink()
|
|
102
|
+
|| !ownedByThisUser(directoryInfo) || !privateDirectory(directoryInfo)) {
|
|
103
|
+
fail("internal", "Refusing to clean an unowned historical-index scratch path");
|
|
104
|
+
}
|
|
105
|
+
let entries;
|
|
106
|
+
try {
|
|
107
|
+
entries = await readdir(scratchDirectory, { withFileTypes: true });
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
// A simultaneous successor may have completed the exact cleanup after our
|
|
111
|
+
// lstat. Missing at any boundary is the intended final state.
|
|
112
|
+
if (errorCode(error) === "ENOENT") {
|
|
113
|
+
await syncDirectory(resolve(dataDir, "index"));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
for (const entry of entries) {
|
|
119
|
+
if (!OWNED_SCRATCH_FILE.test(entry.name) || !entry.isFile() || entry.isSymbolicLink()) {
|
|
120
|
+
fail("internal", "Refusing to clean a historical-index scratch namespace containing an unknown entry");
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const info = await lstat(join(scratchDirectory, entry.name));
|
|
124
|
+
if (!ownedRegularFile(info) && !concurrentlyUnlinkedOwnedFile(info)) {
|
|
125
|
+
fail("internal", "Refusing to clean an unowned historical-index scratch file");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// Another successor may have claimed the same dead generation. A file
|
|
130
|
+
// disappearing is success; no other lookup failure is downgraded.
|
|
131
|
+
if (errorCode(error) !== "ENOENT")
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const entry of entries) {
|
|
136
|
+
const path = join(scratchDirectory, entry.name);
|
|
137
|
+
try {
|
|
138
|
+
// Recheck immediately before unlink. unlink itself never follows a
|
|
139
|
+
// symlink; a replacement directory is refused by the operating system.
|
|
140
|
+
const info = await lstat(path);
|
|
141
|
+
// POSIX may return the inode we just looked up with nlink=0 when a
|
|
142
|
+
// simultaneous cleaner wins unlink between lookup and inspection. Do
|
|
143
|
+
// not unlink the pathname in that case: it may already name a
|
|
144
|
+
// replacement. The final rmdir will conservatively refuse any survivor.
|
|
145
|
+
if (concurrentlyUnlinkedOwnedFile(info)) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (!ownedRegularFile(info)) {
|
|
149
|
+
fail("internal", "Refusing to clean a replaced historical-index scratch file");
|
|
150
|
+
}
|
|
151
|
+
await unlink(path);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (errorCode(error) !== "ENOENT")
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
await rmdir(scratchDirectory);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (errorCode(error) !== "ENOENT")
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
await syncDirectory(resolve(dataDir, "index"));
|
|
166
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The segmented content index — shared contracts.
|
|
3
|
+
*
|
|
4
|
+
* Sealed memories never change, so the index that describes them need not
|
|
5
|
+
* be rewritten either. The index becomes a manifest over immutable sealed
|
|
6
|
+
* SEGMENTS: each segment carries the archive rows, token postings and
|
|
7
|
+
* session metadata for a set of archives, written once. Adding memory
|
|
8
|
+
* appends a segment; a lookup opens every segment's small directory and
|
|
9
|
+
* reads only the blocks that hold its terms; compaction occasionally
|
|
10
|
+
* streams several small segments into one bigger immutable one. Nothing is
|
|
11
|
+
* ever rewritten in place, and no build holds more than one batch in memory.
|
|
12
|
+
*
|
|
13
|
+
* Row shapes are exactly the v3 index rows the reader already understands:
|
|
14
|
+
* ["a", archiveId, ...archiveLine] sorted by archiveId
|
|
15
|
+
* ["t", token, [posting, ...]] sorted by token; a posting is
|
|
16
|
+
* "<archiveId>" or "<archiveId>@c<chunk>"
|
|
17
|
+
* ["m", archiveId, metaObject] sorted by archiveId
|
|
18
|
+
* preceded by one header row `{"version":4,"segmentId":…,"builtAt":…}`.
|
|
19
|
+
*/
|
|
20
|
+
/** One row of a segment body, in the order the body stores them. */
|
|
21
|
+
export type SegmentRow = ["a", string, ...string[]] | ["t", string, string[]] | ["m", string, Record<string, unknown>];
|
|
22
|
+
export type SegmentHeader = {
|
|
23
|
+
version: 4;
|
|
24
|
+
segmentId: string;
|
|
25
|
+
builtAt: string;
|
|
26
|
+
};
|
|
27
|
+
/** Byte ranges of the three regions in the segment's raw (decrypted) body. */
|
|
28
|
+
export type SegmentRegions = {
|
|
29
|
+
archives: [number, number];
|
|
30
|
+
tokens: [number, number];
|
|
31
|
+
meta: [number, number];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* The sealed sidecar that makes a segment addressable without reading it:
|
|
35
|
+
* the raw offset of every `stride`-th token row, and the region bounds. It is
|
|
36
|
+
* bound to the exact sealed body by the body envelope's archive id.
|
|
37
|
+
*/
|
|
38
|
+
export type SegmentDirectory = {
|
|
39
|
+
version: 1;
|
|
40
|
+
segmentId: string;
|
|
41
|
+
envelopeArchiveId: string;
|
|
42
|
+
builtAt: string;
|
|
43
|
+
stride: number;
|
|
44
|
+
regions: SegmentRegions;
|
|
45
|
+
/** [token, raw byte offset of its row], ascending by token. */
|
|
46
|
+
entries: Array<[string, number]>;
|
|
47
|
+
archives: number;
|
|
48
|
+
tokens: number;
|
|
49
|
+
};
|
|
50
|
+
/** What the manifest records about one segment; metadata only, never tokens. */
|
|
51
|
+
export type SegmentEntry = {
|
|
52
|
+
id: string;
|
|
53
|
+
builtAt: string;
|
|
54
|
+
archives: number;
|
|
55
|
+
tokens: number;
|
|
56
|
+
/** Sealed body bytes on disk. */
|
|
57
|
+
bytes: number;
|
|
58
|
+
/**
|
|
59
|
+
* "local" (default): rows this machine derived from its own archives.
|
|
60
|
+
* "remote": the account index pulled from the cloud, converted whole; it is
|
|
61
|
+
* replaced when a newer copy arrives and is never compacted with local ones.
|
|
62
|
+
*/
|
|
63
|
+
origin?: "local" | "remote";
|
|
64
|
+
/**
|
|
65
|
+
* The archive ids this machine sealed that the segment covers, so coverage
|
|
66
|
+
* is known without opening it. Only these may ever be tombstoned as "no
|
|
67
|
+
* longer here": a build forgets what this machine indexed, never what it
|
|
68
|
+
* learned from another machine.
|
|
69
|
+
*/
|
|
70
|
+
archiveIds?: string[];
|
|
71
|
+
/**
|
|
72
|
+
* Rows the segment also carries for archives another machine sealed (a
|
|
73
|
+
* migrated blob had pulled them from the account index). Searchable here,
|
|
74
|
+
* never pruned here, and replaced by segment publication (step 2).
|
|
75
|
+
*/
|
|
76
|
+
remoteArchiveIds?: string[];
|
|
77
|
+
/**
|
|
78
|
+
* The account machine this segment came from, for an `origin: "remote"`
|
|
79
|
+
* entry (step 2's `pullSegments`) — which machine's `<machineId>/` prefix
|
|
80
|
+
* to delete from when a peer's compaction or retirement replaces it.
|
|
81
|
+
* Absent on a "local" entry: it is unambiguously this machine's own.
|
|
82
|
+
*/
|
|
83
|
+
machineId?: string;
|
|
84
|
+
};
|
|
85
|
+
export type SegmentManifest = {
|
|
86
|
+
version: 1;
|
|
87
|
+
/** Changes whenever the segment set changes; readers use it as a cache key. */
|
|
88
|
+
generation: string;
|
|
89
|
+
/** Newest last. A lookup consults all of them. */
|
|
90
|
+
segments: SegmentEntry[];
|
|
91
|
+
/** Archive ids removed from the vault; their rows are ignored everywhere and dropped by the next compaction. */
|
|
92
|
+
tombstones: string[];
|
|
93
|
+
};
|
|
94
|
+
/** The result of a lookup across one or many segments: the same shape queries already consume. */
|
|
95
|
+
export type SegmentLookup = {
|
|
96
|
+
archives: Record<string, string[]>;
|
|
97
|
+
tokens: Record<string, string[]>;
|
|
98
|
+
meta: Record<string, Record<string, unknown>>;
|
|
99
|
+
};
|
|
100
|
+
/** A reader over one sealed segment; opened once, closed by the caller. */
|
|
101
|
+
export interface SegmentReader {
|
|
102
|
+
readonly id: string;
|
|
103
|
+
readonly directory: SegmentDirectory;
|
|
104
|
+
/**
|
|
105
|
+
* Rows for the given terms: every "a" and "m" row of the segment, and the
|
|
106
|
+
* "t" rows whose token equals a term or starts with one (at most
|
|
107
|
+
* `prefixKeep` prefix matches per term, exact matches always). Only the
|
|
108
|
+
* blocks holding those rows are opened.
|
|
109
|
+
*/
|
|
110
|
+
lookup(terms: readonly string[], options?: {
|
|
111
|
+
prefixKeep?: number;
|
|
112
|
+
signal?: AbortSignal;
|
|
113
|
+
}): Promise<SegmentLookup>;
|
|
114
|
+
/** Every row of one region, in stored order, streamed in bounded windows. */
|
|
115
|
+
rows(region: keyof SegmentRegions, options?: {
|
|
116
|
+
signal?: AbortSignal;
|
|
117
|
+
}): AsyncIterable<SegmentRow>;
|
|
118
|
+
close(): Promise<void>;
|
|
119
|
+
}
|
|
120
|
+
/** Writes one immutable segment from rows that are already sorted region by region. */
|
|
121
|
+
export interface SegmentWriter {
|
|
122
|
+
(dataDir: string, phrase: string, input: {
|
|
123
|
+
segmentId: string;
|
|
124
|
+
builtAt: string;
|
|
125
|
+
rows: AsyncIterable<SegmentRow> | Iterable<SegmentRow>;
|
|
126
|
+
}, options?: {
|
|
127
|
+
signal?: AbortSignal;
|
|
128
|
+
onYield?: () => void | Promise<void>;
|
|
129
|
+
}): Promise<SegmentEntry>;
|
|
130
|
+
}
|
|
131
|
+
export declare const SEGMENT_DIRECTORY_STRIDE = 64;
|
|
132
|
+
export declare const MAX_POSTINGS_PER_TOKEN = 256;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The segmented content index — shared contracts.
|
|
3
|
+
*
|
|
4
|
+
* Sealed memories never change, so the index that describes them need not
|
|
5
|
+
* be rewritten either. The index becomes a manifest over immutable sealed
|
|
6
|
+
* SEGMENTS: each segment carries the archive rows, token postings and
|
|
7
|
+
* session metadata for a set of archives, written once. Adding memory
|
|
8
|
+
* appends a segment; a lookup opens every segment's small directory and
|
|
9
|
+
* reads only the blocks that hold its terms; compaction occasionally
|
|
10
|
+
* streams several small segments into one bigger immutable one. Nothing is
|
|
11
|
+
* ever rewritten in place, and no build holds more than one batch in memory.
|
|
12
|
+
*
|
|
13
|
+
* Row shapes are exactly the v3 index rows the reader already understands:
|
|
14
|
+
* ["a", archiveId, ...archiveLine] sorted by archiveId
|
|
15
|
+
* ["t", token, [posting, ...]] sorted by token; a posting is
|
|
16
|
+
* "<archiveId>" or "<archiveId>@c<chunk>"
|
|
17
|
+
* ["m", archiveId, metaObject] sorted by archiveId
|
|
18
|
+
* preceded by one header row `{"version":4,"segmentId":…,"builtAt":…}`.
|
|
19
|
+
*/
|
|
20
|
+
export const SEGMENT_DIRECTORY_STRIDE = 64;
|
|
21
|
+
export const MAX_POSTINGS_PER_TOKEN = 256;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type SegmentReader, type SegmentWriter } from "./index-segment-types.js";
|
|
2
|
+
export declare function segmentPaths(dataDir: string, segmentId: string): Promise<{
|
|
3
|
+
body: string;
|
|
4
|
+
envelope: string;
|
|
5
|
+
directory: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function removeSegment(dataDir: string, segmentId: string): Promise<void>;
|
|
8
|
+
export declare const writeSegment: SegmentWriter;
|
|
9
|
+
export declare function openSegment(dataDir: string, phrase: string, segmentId: string): Promise<SegmentReader>;
|