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,64 @@
|
|
|
1
|
+
export type ContentIndexBuildResult = {
|
|
2
|
+
indexedNow: number;
|
|
3
|
+
skipped: number;
|
|
4
|
+
};
|
|
5
|
+
export type BackgroundIndexBuild = {
|
|
6
|
+
/** Useful for diagnostics and for proving this work is not on the daemon's PID. */
|
|
7
|
+
pid: number | undefined;
|
|
8
|
+
/** Secret-free sibling; diagnostic identity, never the build owner. */
|
|
9
|
+
watchdogPid?: number;
|
|
10
|
+
/** Resolves once the child has accepted the secret and is beginning the build. */
|
|
11
|
+
started: Promise<void>;
|
|
12
|
+
result: Promise<ContentIndexBuildResult>;
|
|
13
|
+
/** Stops this exact child with bounded escalation. Its durable barrier remains
|
|
14
|
+
* if the kernel has not confirmed exit within the join window. Idempotent. */
|
|
15
|
+
stop(): Promise<void>;
|
|
16
|
+
};
|
|
17
|
+
/** Hard V8-heap boundary for best-effort derived work. Plaintext and index
|
|
18
|
+
* serialization are streamed, so exceeding this means the retained index is
|
|
19
|
+
* itself unreasonable; the child fails without taking the interactive daemon
|
|
20
|
+
* or the rest of the desktop down with it. */
|
|
21
|
+
export declare const BACKGROUND_INDEX_MAX_OLD_SPACE_MB = 1024;
|
|
22
|
+
export declare const BACKGROUND_INDEX_MAX_SEMI_SPACE_MB = 16;
|
|
23
|
+
/** Checked cooperatively every 8 MiB of plaintext in addition to V8's heap
|
|
24
|
+
* boundary. This includes native buffers, decompression, and token storage. */
|
|
25
|
+
export declare const BACKGROUND_INDEX_MAX_RSS_BYTES: number;
|
|
26
|
+
/** Configuration and credential-store discovery only; credentials are loaded
|
|
27
|
+
* from the vault by the worker, never inherited from the daemon environment. */
|
|
28
|
+
export declare function backgroundIndexWorkerEnvironment(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
29
|
+
export declare function backgroundIndexExecArgv(parent: readonly string[]): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Runs a historical content-index build outside the daemon's event loop.
|
|
32
|
+
*
|
|
33
|
+
* Index extraction is not merely asynchronous I/O: archive authentication,
|
|
34
|
+
* decompression, token scanning, index serialisation, gzip and encryption all
|
|
35
|
+
* contain long synchronous sections. A Promise around those sections still
|
|
36
|
+
* blocks heartbeat timers. A child also gives the large derived index its own
|
|
37
|
+
* heap, and lets the OS prefer interactive Codex/Claude hook processes while a
|
|
38
|
+
* backlog is being rebuilt.
|
|
39
|
+
*
|
|
40
|
+
* The recovery phrase travels only over the already-owned IPC channel. It is
|
|
41
|
+
* deliberately absent from argv and the environment, which are routinely
|
|
42
|
+
* exposed by process inspectors and service diagnostics.
|
|
43
|
+
*/
|
|
44
|
+
export declare function startBackgroundIndexBuild(dataDir: string, phrase: string, options: {
|
|
45
|
+
sync: boolean;
|
|
46
|
+
idleMs?: number;
|
|
47
|
+
/** Child boot gets a small floor; once it says started, idleMs governs real work. */
|
|
48
|
+
startupMs?: number;
|
|
49
|
+
stopGraceMs?: number;
|
|
50
|
+
/** Historical work defaults to one lane and 30% of one core. */
|
|
51
|
+
lanes?: number;
|
|
52
|
+
cpuTarget?: number;
|
|
53
|
+
/** Deterministic process fixture; production always uses the package child. */
|
|
54
|
+
childPath?: string;
|
|
55
|
+
/** Deterministic failure fixture; production always uses the package watchdog. */
|
|
56
|
+
watchdogPath?: string;
|
|
57
|
+
/** Supervised cleanup only after the exact worker has actually exited. */
|
|
58
|
+
onWorkerExit?: (worker: {
|
|
59
|
+
pid: number;
|
|
60
|
+
processIdentity: string;
|
|
61
|
+
}) => Promise<void>;
|
|
62
|
+
/** Exact owning daemon's current-vault upload capability; never inferred globally. */
|
|
63
|
+
signerAuthorized?: boolean;
|
|
64
|
+
}): BackgroundIndexBuild;
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { fork } from "node:child_process";
|
|
2
|
+
import { constants, setPriority } from "node:os";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { SealkeepError } from "./errors.js";
|
|
5
|
+
import { backgroundDaemonCpuBudget, BACKGROUND_CPU_TOTAL_TARGET, signalOwnedBackgroundProcess, onOwnedBackgroundProcessExit, startBackgroundProcessCpuGovernor } from "./background-cpu.js";
|
|
6
|
+
import { daemonProcessIdentity, registerDaemonIndexWorker } from "./daemon-lease.js";
|
|
7
|
+
import { envVar } from "./env.js";
|
|
8
|
+
import { backgroundWorkerErrorFromWire } from "./background-worker-error.js";
|
|
9
|
+
/** Hard V8-heap boundary for best-effort derived work. Plaintext and index
|
|
10
|
+
* serialization are streamed, so exceeding this means the retained index is
|
|
11
|
+
* itself unreasonable; the child fails without taking the interactive daemon
|
|
12
|
+
* or the rest of the desktop down with it. */
|
|
13
|
+
export const BACKGROUND_INDEX_MAX_OLD_SPACE_MB = 1024;
|
|
14
|
+
export const BACKGROUND_INDEX_MAX_SEMI_SPACE_MB = 16;
|
|
15
|
+
/** Checked cooperatively every 8 MiB of plaintext in addition to V8's heap
|
|
16
|
+
* boundary. This includes native buffers, decompression, and token storage. */
|
|
17
|
+
export const BACKGROUND_INDEX_MAX_RSS_BYTES = 1536 * 1024 * 1024;
|
|
18
|
+
const PROCESS_RUNTIME_ENV = ["PATH", "SystemRoot", "WINDIR", "TEMP", "TMP", "TMPDIR"];
|
|
19
|
+
/** Configuration and credential-store discovery only; credentials are loaded
|
|
20
|
+
* from the vault by the worker, never inherited from the daemon environment. */
|
|
21
|
+
export function backgroundIndexWorkerEnvironment(env = process.env) {
|
|
22
|
+
const result = {};
|
|
23
|
+
for (const key of [...PROCESS_RUNTIME_ENV, "HOME", "USERPROFILE", "APPDATA", "LOCALAPPDATA",
|
|
24
|
+
"XDG_CONFIG_HOME", "XDG_DATA_HOME", "XDG_RUNTIME_DIR", "DBUS_SESSION_BUS_ADDRESS", "LANG", "LC_ALL", "TZ"]) {
|
|
25
|
+
if (env[key] !== undefined)
|
|
26
|
+
result[key] = env[key];
|
|
27
|
+
}
|
|
28
|
+
for (const prefix of ["SEALKEEP_", "VAULTLINE_"]) {
|
|
29
|
+
for (const name of ["SECRET_BACKEND", "CLOUD_URL", "ALLOW_PRIVATE_STORAGE_ENDPOINT", "GDRIVE_CLIENT_ID", "MAX_ARCHIVE_BYTES"]) {
|
|
30
|
+
if (env[prefix + name] !== undefined)
|
|
31
|
+
result[prefix + name] = env[prefix + name];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
export function backgroundIndexExecArgv(parent) {
|
|
37
|
+
// A parent may have --eval source, inspector authentication, or arbitrary
|
|
38
|
+
// preload arguments. Only our development TS loader must cross this boundary.
|
|
39
|
+
const loaders = [];
|
|
40
|
+
for (let index = 0; index < parent.length; index += 1) {
|
|
41
|
+
const argument = parent[index];
|
|
42
|
+
const match = /^(--import|--require|-r)(?:=(.*))?$/.exec(argument);
|
|
43
|
+
if (!match)
|
|
44
|
+
continue;
|
|
45
|
+
const value = match[2] ?? parent[++index];
|
|
46
|
+
if (value && (value === "tsx" || /[/\\]tsx[/\\]dist[/\\](?:loader\.mjs|preflight\.cjs)$/.test(value))) {
|
|
47
|
+
loaders.push(match[1], value);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [
|
|
51
|
+
...loaders,
|
|
52
|
+
`--max-old-space-size=${BACKGROUND_INDEX_MAX_OLD_SPACE_MB}`,
|
|
53
|
+
`--max-semi-space-size=${BACKGROUND_INDEX_MAX_SEMI_SPACE_MB}`,
|
|
54
|
+
// V8's default concurrent collector can add two or more CPU cores while
|
|
55
|
+
// the JavaScript lane is correctly sleeping. This child is explicitly
|
|
56
|
+
// background work; serial GC trades a little wall time for a bounded,
|
|
57
|
+
// non-interactive process footprint.
|
|
58
|
+
"--single-threaded-gc",
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Runs a historical content-index build outside the daemon's event loop.
|
|
63
|
+
*
|
|
64
|
+
* Index extraction is not merely asynchronous I/O: archive authentication,
|
|
65
|
+
* decompression, token scanning, index serialisation, gzip and encryption all
|
|
66
|
+
* contain long synchronous sections. A Promise around those sections still
|
|
67
|
+
* blocks heartbeat timers. A child also gives the large derived index its own
|
|
68
|
+
* heap, and lets the OS prefer interactive Codex/Claude hook processes while a
|
|
69
|
+
* backlog is being rebuilt.
|
|
70
|
+
*
|
|
71
|
+
* The recovery phrase travels only over the already-owned IPC channel. It is
|
|
72
|
+
* deliberately absent from argv and the environment, which are routinely
|
|
73
|
+
* exposed by process inspectors and service diagnostics.
|
|
74
|
+
*/
|
|
75
|
+
export function startBackgroundIndexBuild(dataDir, phrase, options) {
|
|
76
|
+
// Tests execute the TypeScript source through tsx; packaged installs execute
|
|
77
|
+
// dist JavaScript. Match the entry point to the module that created us.
|
|
78
|
+
const extension = import.meta.url.endsWith(".ts") ? ".ts" : ".js";
|
|
79
|
+
const childPath = options.childPath ?? fileURLToPath(new URL(`./index-build-child${extension}`, import.meta.url));
|
|
80
|
+
// Explicit per-daemon denial overrides ambient operator enablement. Direct
|
|
81
|
+
// callers retain their explicit operator setting, but never another daemon's
|
|
82
|
+
// process-local grant. The resulting capability travels only through IPC.
|
|
83
|
+
const signerAuthorized = options.signerAuthorized ?? (envVar("ENABLE_SIGNER") === "1");
|
|
84
|
+
const child = fork(childPath, [], {
|
|
85
|
+
stdio: ["ignore", "ignore", "inherit", "ipc"],
|
|
86
|
+
execArgv: backgroundIndexExecArgv(process.execArgv),
|
|
87
|
+
env: backgroundIndexWorkerEnvironment(),
|
|
88
|
+
});
|
|
89
|
+
// The daemon and lifecycle hooks are interactive control lanes; a historical
|
|
90
|
+
// rebuild is best-effort derived work. Priority adjustment is advisory and
|
|
91
|
+
// unsupported on a few hosts, where ordinary scheduler fairness still holds.
|
|
92
|
+
if (child.pid !== undefined) {
|
|
93
|
+
try {
|
|
94
|
+
setPriority(child.pid, constants.priority.PRIORITY_BELOW_NORMAL);
|
|
95
|
+
}
|
|
96
|
+
catch { /* priority is an optimisation, never a reason to lose indexing */ }
|
|
97
|
+
}
|
|
98
|
+
const cpuTarget = Math.min(1, Math.max(0.05, options.cpuTarget ?? 0.30));
|
|
99
|
+
// The in-child limiter handles ordinary parse/compression work. Its event
|
|
100
|
+
// loop cannot interrupt V8's own GC, so this owned parent also governs the
|
|
101
|
+
// whole process on POSIX. This is what makes the ceiling apply to native
|
|
102
|
+
// worker/collector CPU rather than just JavaScript checkpoints.
|
|
103
|
+
// Do not pause or send any work until the independent orphan watchdog has
|
|
104
|
+
// verified this exact worker generation and acknowledged ownership.
|
|
105
|
+
let cpuGovernor = { stop() { } };
|
|
106
|
+
const watchdogEnv = {};
|
|
107
|
+
for (const key of PROCESS_RUNTIME_ENV) {
|
|
108
|
+
if (process.env[key] !== undefined)
|
|
109
|
+
watchdogEnv[key] = process.env[key];
|
|
110
|
+
}
|
|
111
|
+
let watchdog;
|
|
112
|
+
try {
|
|
113
|
+
watchdog = fork(options.watchdogPath ?? fileURLToPath(new URL(`./index-background-watchdog${extension}`, import.meta.url)), [], {
|
|
114
|
+
stdio: ["ignore", "ignore", "inherit", "ipc"],
|
|
115
|
+
env: watchdogEnv,
|
|
116
|
+
execArgv: [...backgroundIndexExecArgv(process.execArgv).filter((arg) => !arg.startsWith("--max-old-space-size")), "--max-old-space-size=64"],
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
// A synchronous spawn/argument failure occurs before the usual listeners
|
|
121
|
+
// exist. This exact worker has received neither work nor a pause signal.
|
|
122
|
+
child.once("error", () => { });
|
|
123
|
+
signalOwnedBackgroundProcess(child, "SIGKILL");
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
let watchdogDisarmed = false;
|
|
127
|
+
let watchdogFailure;
|
|
128
|
+
let watchdogExited = false;
|
|
129
|
+
let watchdogExitTimer;
|
|
130
|
+
let watchdogClosedResolve;
|
|
131
|
+
const watchdogClosed = new Promise((resolve) => { watchdogClosedResolve = resolve; });
|
|
132
|
+
// Keep this owned child and its IPC channel referenced until it exits. A bare
|
|
133
|
+
// Promise does not keep Node alive; unref'ing here can abandon a build before
|
|
134
|
+
// daemon.close() gets the chance to terminate it and wait for the exact PID.
|
|
135
|
+
let startedResolve;
|
|
136
|
+
let startedSettled = false;
|
|
137
|
+
const started = new Promise((resolve) => { startedResolve = resolve; });
|
|
138
|
+
const markStarted = () => {
|
|
139
|
+
if (startedSettled)
|
|
140
|
+
return;
|
|
141
|
+
startedSettled = true;
|
|
142
|
+
startedResolve();
|
|
143
|
+
};
|
|
144
|
+
let resultSettled = false;
|
|
145
|
+
let resultResolve;
|
|
146
|
+
let resultReject;
|
|
147
|
+
const result = new Promise((resolve, reject) => {
|
|
148
|
+
resultResolve = resolve;
|
|
149
|
+
resultReject = reject;
|
|
150
|
+
});
|
|
151
|
+
let reportedResult;
|
|
152
|
+
let reportedError;
|
|
153
|
+
let workerIdentity;
|
|
154
|
+
let workerRegistration;
|
|
155
|
+
let closedResolve;
|
|
156
|
+
let closedSettled = false;
|
|
157
|
+
const closed = new Promise((resolve) => { closedResolve = resolve; });
|
|
158
|
+
const idleMs = Math.max(1, options.idleMs ?? 2 * 60_000);
|
|
159
|
+
// A busy machine can take longer to load Node (and the TypeScript loader in
|
|
160
|
+
// development) than a deliberately tiny test/work idle window. Treat boot
|
|
161
|
+
// and work as separate liveness phases: a child that never starts is still
|
|
162
|
+
// bounded, while a healthy child is judged by actual progress only after its
|
|
163
|
+
// first IPC acknowledgement. Production's two-minute idle window remains
|
|
164
|
+
// the startup window too; the floor matters only for shorter overrides.
|
|
165
|
+
const startupMs = Math.max(idleMs, options.startupMs ?? 10_000);
|
|
166
|
+
const stopGraceMs = Math.max(1, options.stopGraceMs ?? 2_000);
|
|
167
|
+
let timedOut = false;
|
|
168
|
+
let idleTimer;
|
|
169
|
+
let forceTimer;
|
|
170
|
+
const clearDeadlines = () => {
|
|
171
|
+
if (idleTimer)
|
|
172
|
+
clearTimeout(idleTimer);
|
|
173
|
+
if (forceTimer)
|
|
174
|
+
clearTimeout(forceTimer);
|
|
175
|
+
idleTimer = undefined;
|
|
176
|
+
forceTimer = undefined;
|
|
177
|
+
};
|
|
178
|
+
const armIdleDeadline = (windowMs = idleMs, phase = "running") => {
|
|
179
|
+
if (closedSettled || timedOut)
|
|
180
|
+
return;
|
|
181
|
+
if (idleTimer)
|
|
182
|
+
clearTimeout(idleTimer);
|
|
183
|
+
idleTimer = setTimeout(() => {
|
|
184
|
+
timedOut = true;
|
|
185
|
+
reportedError = new SealkeepError("operation_timeout", phase === "starting"
|
|
186
|
+
? `Historical index child did not start within ${windowMs} ms`
|
|
187
|
+
: `Historical index build made no progress for ${windowMs} ms`, { operation: "historical_index", phase, idleMs: windowMs, retryable: true });
|
|
188
|
+
cpuGovernor.stop();
|
|
189
|
+
signalOwnedBackgroundProcess(child, "SIGTERM");
|
|
190
|
+
forceTimer = setTimeout(() => { if (!closedSettled)
|
|
191
|
+
signalOwnedBackgroundProcess(child, "SIGKILL"); }, stopGraceMs);
|
|
192
|
+
}, windowMs);
|
|
193
|
+
};
|
|
194
|
+
onOwnedBackgroundProcessExit(child, (code, signal) => {
|
|
195
|
+
cpuGovernor.stop();
|
|
196
|
+
clearDeadlines();
|
|
197
|
+
closedSettled = true;
|
|
198
|
+
watchdogDisarmed = true;
|
|
199
|
+
if (!watchdogExited) {
|
|
200
|
+
if (watchdog.connected)
|
|
201
|
+
watchdog.send({ type: "disarm" }, () => { });
|
|
202
|
+
watchdogExitTimer = setTimeout(() => signalOwnedBackgroundProcess(watchdog, "SIGKILL"), stopGraceMs);
|
|
203
|
+
}
|
|
204
|
+
void (async () => {
|
|
205
|
+
try {
|
|
206
|
+
const registration = await workerRegistration;
|
|
207
|
+
const cleanupErrors = [];
|
|
208
|
+
try {
|
|
209
|
+
if (workerIdentity && child.pid !== undefined) {
|
|
210
|
+
await options.onWorkerExit?.({ pid: child.pid, processIdentity: workerIdentity });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
cleanupErrors.push(error);
|
|
215
|
+
}
|
|
216
|
+
// A shared-lane recovery failure must not skip cleanup of this dead
|
|
217
|
+
// generation's private scratch or leave its startup barrier stranded.
|
|
218
|
+
try {
|
|
219
|
+
await registration?.retireAfterExit();
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
cleanupErrors.push(error);
|
|
223
|
+
}
|
|
224
|
+
if (cleanupErrors.length)
|
|
225
|
+
throw new AggregateError(cleanupErrors, "Historical index worker cleanup failed");
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
reportedError = error instanceof Error ? error : new Error(String(error));
|
|
229
|
+
reportedResult = undefined;
|
|
230
|
+
}
|
|
231
|
+
closedResolve();
|
|
232
|
+
markStarted();
|
|
233
|
+
if (resultSettled)
|
|
234
|
+
return;
|
|
235
|
+
resultSettled = true;
|
|
236
|
+
if (watchdogFailure)
|
|
237
|
+
resultReject(watchdogFailure);
|
|
238
|
+
else if (governorFailure)
|
|
239
|
+
resultReject(governorFailure);
|
|
240
|
+
else if (reportedResult)
|
|
241
|
+
resultResolve(reportedResult);
|
|
242
|
+
else
|
|
243
|
+
resultReject(reportedError ?? new Error(`Historical index child exited before reporting a result (${signal ?? code ?? "unknown"})`));
|
|
244
|
+
})();
|
|
245
|
+
});
|
|
246
|
+
child.once("error", (error) => { reportedError ??= error; });
|
|
247
|
+
child.on("message", (wire) => {
|
|
248
|
+
if (!wire || typeof wire !== "object" || !("type" in wire))
|
|
249
|
+
return;
|
|
250
|
+
const message = wire;
|
|
251
|
+
if (message.type === "started" || message.type === "progress") {
|
|
252
|
+
markStarted();
|
|
253
|
+
armIdleDeadline();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (message.type === "result") {
|
|
257
|
+
markStarted();
|
|
258
|
+
if (!timedOut)
|
|
259
|
+
reportedResult = message.result;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (message.type === "error") {
|
|
263
|
+
reportedError = backgroundWorkerErrorFromWire(message.error);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
let governorFailure;
|
|
267
|
+
const beginBuild = (scratchGeneration) => {
|
|
268
|
+
if (closedSettled || timedOut || stopInFlight)
|
|
269
|
+
return;
|
|
270
|
+
cpuGovernor = startBackgroundProcessCpuGovernor({
|
|
271
|
+
target: cpuTarget,
|
|
272
|
+
budget: backgroundDaemonCpuBudget,
|
|
273
|
+
signal: (signal) => signalOwnedBackgroundProcess(child, signal),
|
|
274
|
+
verifySignals: true,
|
|
275
|
+
onFailure: (error) => {
|
|
276
|
+
governorFailure ??= error;
|
|
277
|
+
reportedError = error;
|
|
278
|
+
reportedResult = undefined;
|
|
279
|
+
if (child.connected)
|
|
280
|
+
child.disconnect();
|
|
281
|
+
void stop();
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
if (governorFailure)
|
|
285
|
+
return;
|
|
286
|
+
child.send({
|
|
287
|
+
type: "build", dataDir, phrase, sync: options.sync,
|
|
288
|
+
lanes: Math.max(1, Math.floor(options.lanes ?? 1)),
|
|
289
|
+
// POSIX has a parent-side hard process governor, which can pause even a
|
|
290
|
+
// synchronous GC. Let that be the one clock instead of compounding it with
|
|
291
|
+
// child sleeps. Windows has no SIGSTOP and keeps cooperative accounting.
|
|
292
|
+
cpuTarget: process.platform === "win32" ? Math.min(cpuTarget, BACKGROUND_CPU_TOTAL_TARGET / 3) : 1,
|
|
293
|
+
maxRssBytes: BACKGROUND_INDEX_MAX_RSS_BYTES,
|
|
294
|
+
scratchGeneration,
|
|
295
|
+
signerAuthorized,
|
|
296
|
+
}, (error) => {
|
|
297
|
+
if (error) {
|
|
298
|
+
reportedError ??= error;
|
|
299
|
+
void stop();
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
let stopInFlight;
|
|
304
|
+
const stop = () => {
|
|
305
|
+
if (stopInFlight)
|
|
306
|
+
return stopInFlight;
|
|
307
|
+
stopInFlight = (async () => {
|
|
308
|
+
if (closedSettled) {
|
|
309
|
+
await closed;
|
|
310
|
+
await watchdogClosed;
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
clearDeadlines();
|
|
314
|
+
cpuGovernor.stop();
|
|
315
|
+
signalOwnedBackgroundProcess(child, "SIGTERM");
|
|
316
|
+
let force;
|
|
317
|
+
await Promise.race([
|
|
318
|
+
closed,
|
|
319
|
+
new Promise((resolve) => {
|
|
320
|
+
force = setTimeout(() => {
|
|
321
|
+
if (!closedSettled)
|
|
322
|
+
signalOwnedBackgroundProcess(child, "SIGKILL");
|
|
323
|
+
resolve();
|
|
324
|
+
}, stopGraceMs);
|
|
325
|
+
}),
|
|
326
|
+
]);
|
|
327
|
+
if (force)
|
|
328
|
+
clearTimeout(force);
|
|
329
|
+
// SIGKILL is asynchronous too. Give the exact PID another join window;
|
|
330
|
+
// its durable barrier still excludes replacement if the kernel stalls.
|
|
331
|
+
if (!closedSettled) {
|
|
332
|
+
let final;
|
|
333
|
+
await Promise.race([
|
|
334
|
+
closed,
|
|
335
|
+
new Promise((resolve) => { final = setTimeout(resolve, stopGraceMs); }),
|
|
336
|
+
]);
|
|
337
|
+
if (final)
|
|
338
|
+
clearTimeout(final);
|
|
339
|
+
}
|
|
340
|
+
if (closedSettled) {
|
|
341
|
+
await closed;
|
|
342
|
+
await watchdogClosed;
|
|
343
|
+
}
|
|
344
|
+
})();
|
|
345
|
+
return stopInFlight;
|
|
346
|
+
};
|
|
347
|
+
const watchdogFailed = (cause) => {
|
|
348
|
+
if (closedSettled || watchdogDisarmed)
|
|
349
|
+
return;
|
|
350
|
+
watchdogFailure ??= cause;
|
|
351
|
+
reportedError = cause;
|
|
352
|
+
reportedResult = undefined;
|
|
353
|
+
void stop();
|
|
354
|
+
};
|
|
355
|
+
watchdog.once("error", (error) => { watchdogFailed(error); });
|
|
356
|
+
watchdog.once("disconnect", () => {
|
|
357
|
+
watchdogFailed(new Error("Historical index orphan watchdog exited unexpectedly (IPC disconnected)"));
|
|
358
|
+
});
|
|
359
|
+
onOwnedBackgroundProcessExit(watchdog, (code, signal) => {
|
|
360
|
+
watchdogExited = true;
|
|
361
|
+
if (watchdogExitTimer)
|
|
362
|
+
clearTimeout(watchdogExitTimer);
|
|
363
|
+
watchdogClosedResolve();
|
|
364
|
+
watchdogFailed(new Error(`Historical index orphan watchdog exited unexpectedly (${signal ?? code ?? "unknown"})`));
|
|
365
|
+
});
|
|
366
|
+
watchdog.once("message", (wire) => {
|
|
367
|
+
if (wire && typeof wire === "object" && "type" in wire && wire.type === "armed") {
|
|
368
|
+
if (closedSettled || timedOut || stopInFlight || !workerIdentity || child.pid === undefined)
|
|
369
|
+
return;
|
|
370
|
+
workerRegistration = registerDaemonIndexWorker(dataDir, child.pid, workerIdentity);
|
|
371
|
+
void workerRegistration.then((registration) => beginBuild(registration.scratchGeneration), watchdogFailed);
|
|
372
|
+
}
|
|
373
|
+
else
|
|
374
|
+
watchdogFailed(new Error("Historical index orphan watchdog did not acknowledge ownership"));
|
|
375
|
+
});
|
|
376
|
+
// Send only after all failure listeners are installed. Neither the phrase
|
|
377
|
+
// nor the vault path ever enters the watchdog's IPC, argv or environment.
|
|
378
|
+
void (async () => {
|
|
379
|
+
if (child.pid === undefined)
|
|
380
|
+
throw new Error("Historical index child has no process identity");
|
|
381
|
+
const identity = await daemonProcessIdentity(child.pid);
|
|
382
|
+
if (closedSettled || timedOut || stopInFlight)
|
|
383
|
+
return;
|
|
384
|
+
if (identity.state !== "identified")
|
|
385
|
+
throw new Error("Historical index child process generation could not be verified");
|
|
386
|
+
workerIdentity = identity.identity;
|
|
387
|
+
watchdog.send({ type: "arm", pid: child.pid, identity: identity.identity, graceMs: stopGraceMs }, (error) => {
|
|
388
|
+
if (error)
|
|
389
|
+
watchdogFailed(error);
|
|
390
|
+
});
|
|
391
|
+
})().catch((error) => watchdogFailed(error));
|
|
392
|
+
armIdleDeadline(startupMs, "starting");
|
|
393
|
+
return { pid: child.pid, watchdogPid: watchdog.pid, started, result, stop };
|
|
394
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { backgroundUploadBytesPerSecond, readLocalSettings } from "./machine-settings.js";
|
|
2
|
+
import { createBackgroundCpuYield } from "./background-cpu.js";
|
|
3
|
+
import { runBackgroundIndexWork } from "./index-background-work.js";
|
|
4
|
+
import { SealkeepError } from "./errors.js";
|
|
5
|
+
import { acquireDaemonSignerGrant } from "./control-plane.js";
|
|
6
|
+
import { backgroundWorkerErrorToWire } from "./background-worker-error.js";
|
|
7
|
+
function send(message) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
if (!process.send || !process.connected) {
|
|
10
|
+
resolve();
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
process.send(message, () => resolve());
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
let completed = false;
|
|
17
|
+
const cancellation = new AbortController();
|
|
18
|
+
const cancel = () => {
|
|
19
|
+
if (completed || cancellation.signal.aborted)
|
|
20
|
+
return;
|
|
21
|
+
const error = new Error("Historical index owner requested shutdown");
|
|
22
|
+
error.name = "AbortError";
|
|
23
|
+
cancellation.abort(error);
|
|
24
|
+
};
|
|
25
|
+
// A normal daemon stop first asks the child to abort. Chunk-independent remote
|
|
26
|
+
// archives are indexed directly from their authenticated stream, so cancellation
|
|
27
|
+
// closes that read without owning archive-sized scratch space. The parent's
|
|
28
|
+
// existing two-second SIGKILL fallback remains the hard bound for an unresponsive
|
|
29
|
+
// provider or synchronous legacy decrypt.
|
|
30
|
+
process.once("SIGTERM", cancel);
|
|
31
|
+
process.once("SIGINT", cancel);
|
|
32
|
+
// If the daemon dies without reaching close(), the IPC pipe still tells this
|
|
33
|
+
// child it has lost its sole owner. Give an active remote read one short
|
|
34
|
+
// graceful window to close. The independent, never-paused sibling watchdog
|
|
35
|
+
// supplies the hard orphan bound even when this event loop is SIGSTOP-paused
|
|
36
|
+
// or stuck in synchronous work and cannot observe the disconnect itself.
|
|
37
|
+
process.once("disconnect", () => {
|
|
38
|
+
if (completed)
|
|
39
|
+
return;
|
|
40
|
+
cancel();
|
|
41
|
+
setTimeout(() => process.exit(0), 2_000).unref();
|
|
42
|
+
});
|
|
43
|
+
// One IPC request, one build, one result. Keeping this child single-purpose is
|
|
44
|
+
// what makes daemon cancellation exact: there is never a second queued build
|
|
45
|
+
// left behind after the parent replaces or shuts down the service.
|
|
46
|
+
process.once("message", async (wire) => {
|
|
47
|
+
const request = wire && typeof wire === "object" ? wire : {};
|
|
48
|
+
if (request.type !== "build" || typeof request.dataDir !== "string" || typeof request.phrase !== "string" || typeof request.sync !== "boolean"
|
|
49
|
+
|| typeof request.scratchGeneration !== "string"
|
|
50
|
+
|| !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(request.scratchGeneration)) {
|
|
51
|
+
await send({ type: "error", error: { message: "Historical index child received an invalid build request" } });
|
|
52
|
+
completed = true;
|
|
53
|
+
if (process.connected)
|
|
54
|
+
process.disconnect();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Only an explicit capability from this exact owning daemon crosses IPC.
|
|
58
|
+
// Other live daemons in the parent's process cannot implicitly authorize us.
|
|
59
|
+
const signerGrant = request.signerAuthorized === true ? acquireDaemonSignerGrant() : undefined;
|
|
60
|
+
try {
|
|
61
|
+
await send({ type: "started" });
|
|
62
|
+
const backgroundTransferBytesPerSecond = backgroundUploadBytesPerSecond(await readLocalSettings(request.dataDir));
|
|
63
|
+
const cpuTarget = typeof request.cpuTarget === "number" ? request.cpuTarget : 0.30;
|
|
64
|
+
const maxRssBytes = typeof request.maxRssBytes === "number" ? request.maxRssBytes : 1536 * 1024 * 1024;
|
|
65
|
+
const yieldForUpgrade = createBackgroundCpuYield({
|
|
66
|
+
target: cpuTarget,
|
|
67
|
+
budget: null,
|
|
68
|
+
signal: cancellation.signal,
|
|
69
|
+
});
|
|
70
|
+
const reportProgress = () => { void send({ type: "progress" }); };
|
|
71
|
+
const result = await runBackgroundIndexWork(request.dataDir, request.phrase, {
|
|
72
|
+
sync: request.sync,
|
|
73
|
+
signal: cancellation.signal,
|
|
74
|
+
upgradeProgress: reportProgress,
|
|
75
|
+
upgradeYield: async () => {
|
|
76
|
+
if (process.memoryUsage().rss > maxRssBytes) {
|
|
77
|
+
throw new SealkeepError("operation_timeout", `Historical index publication reached its ${Math.round(maxRssBytes / (1024 * 1024))} MiB memory safety ceiling. Sealkeep will retry later.`, { operation: "historical_index_publication", reason: "memory_ceiling", maxRssBytes, retryable: true });
|
|
78
|
+
}
|
|
79
|
+
await yieldForUpgrade();
|
|
80
|
+
reportProgress();
|
|
81
|
+
},
|
|
82
|
+
scheduling: {
|
|
83
|
+
lanes: Number.isInteger(request.lanes) ? request.lanes : 1,
|
|
84
|
+
cpuTarget: typeof request.cpuTarget === "number" ? request.cpuTarget : 0.30,
|
|
85
|
+
maxRssBytes,
|
|
86
|
+
backgroundTransferBytesPerSecond,
|
|
87
|
+
scratchGeneration: request.scratchGeneration,
|
|
88
|
+
// Segments mode only: this background child cannot hold a merged
|
|
89
|
+
// index above this many tokens, so publishFromSegments abandons
|
|
90
|
+
// publication (pushed: false) rather than producing one and failing.
|
|
91
|
+
publishMaxTokens: 400_000,
|
|
92
|
+
},
|
|
93
|
+
onActivity: () => { void send({ type: "progress" }); },
|
|
94
|
+
onProgress: () => { void send({ type: "progress" }); },
|
|
95
|
+
});
|
|
96
|
+
await send({ type: "result", result });
|
|
97
|
+
}
|
|
98
|
+
catch (cause) {
|
|
99
|
+
await send({ type: "error", error: backgroundWorkerErrorToWire(cause) });
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
signerGrant?.release();
|
|
103
|
+
// Closing the IPC handle lets the otherwise listener-free child exit on
|
|
104
|
+
// its own; no secret or build worker remains resident after completion.
|
|
105
|
+
completed = true;
|
|
106
|
+
if (process.connected)
|
|
107
|
+
process.disconnect();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The segment manifest — the list of immutable sealed segments that make up
|
|
3
|
+
* the content index — and the compaction that keeps that list short.
|
|
4
|
+
*
|
|
5
|
+
* Sealed segments never change, so the index over them need not be rewritten
|
|
6
|
+
* either: appending memory means appending a segment, removing an archive
|
|
7
|
+
* means tombstoning it, and staying fast means occasionally streaming a few
|
|
8
|
+
* small segments into one bigger immutable one. The manifest itself is the
|
|
9
|
+
* only thing ever rewritten, and every rewrite is temp-write + rename under a
|
|
10
|
+
* lock, so two processes can never interleave a read-modify-write.
|
|
11
|
+
*/
|
|
12
|
+
import { type SegmentEntry, type SegmentManifest, type SegmentReader, type SegmentRow, type SegmentWriter } from "./index-segment-types.js";
|
|
13
|
+
export declare function readManifest(dataDir: string): Promise<SegmentManifest>;
|
|
14
|
+
/** Read-modify-write under the manifest lock. Every successful write gets a
|
|
15
|
+
* fresh `generation`, whether or not `mutate` actually changed anything. */
|
|
16
|
+
export declare function updateManifest(dataDir: string, mutate: (current: SegmentManifest) => SegmentManifest | Promise<SegmentManifest>): Promise<SegmentManifest>;
|
|
17
|
+
export declare function appendSegment(dataDir: string, entry: SegmentEntry): Promise<SegmentManifest>;
|
|
18
|
+
export declare function retireArchives(dataDir: string, archiveIds: readonly string[]): Promise<SegmentManifest>;
|
|
19
|
+
/**
|
|
20
|
+
* Ids to merge, or `[]`. A "remote" segment (the whole account index pulled
|
|
21
|
+
* from the cloud) is never a candidate: it never merges with a locally-built
|
|
22
|
+
* one, and it does not count toward the segment-count trigger either.
|
|
23
|
+
*/
|
|
24
|
+
export declare function planCompaction(manifest: SegmentManifest, options?: {
|
|
25
|
+
maxSegments?: number;
|
|
26
|
+
maxSmallBytes?: number;
|
|
27
|
+
}): string[];
|
|
28
|
+
/**
|
|
29
|
+
* The compaction merge on its own: the union/cap/tombstone/summary rules that
|
|
30
|
+
* turn several sealed segments' rows into one segment's worth, streamed so no
|
|
31
|
+
* more than one batch of archive or token rows is held at once.
|
|
32
|
+
*
|
|
33
|
+
* `readers` must be given oldest first (manifest order) — every "later wins"
|
|
34
|
+
* rule below is defined relative to that order. Meta rows are the one
|
|
35
|
+
* exception to streaming: a segment holds only a few hundred bytes of
|
|
36
|
+
* metadata per archive, so the whole merged map is kept in memory (also
|
|
37
|
+
* needed to rank token postings by `archivedAt`) and emitted last.
|
|
38
|
+
*/
|
|
39
|
+
export declare function mergeSegmentRows(readers: readonly SegmentReader[], tombstones: ReadonlySet<string>, options?: {
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
onYield?: () => void | Promise<void>;
|
|
42
|
+
}): AsyncGenerator<SegmentRow>;
|
|
43
|
+
export declare function compactSegments(dataDir: string, phrase: string, segmentIds: readonly string[], tools: {
|
|
44
|
+
open: (dataDir: string, phrase: string, segmentId: string) => Promise<SegmentReader>;
|
|
45
|
+
write: SegmentWriter;
|
|
46
|
+
remove: (dataDir: string, segmentId: string) => Promise<void>;
|
|
47
|
+
}, options?: {
|
|
48
|
+
signal?: AbortSignal;
|
|
49
|
+
onYield?: () => void | Promise<void>;
|
|
50
|
+
}): Promise<SegmentEntry>;
|
|
51
|
+
/** Segment ids present on disk that the manifest does not list. */
|
|
52
|
+
export declare function orphanSegmentIds(dataDir: string, listed: (dataDir: string) => Promise<string[]>): Promise<string[]>;
|