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,232 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { link, mkdir, open, readdir, readFile, rename, rm } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
const CLAIM_NAME = /^(\d{20})\.json$/;
|
|
5
|
+
const MAX_TICKET = BigInt("99999999999999999999");
|
|
6
|
+
function errorCode(error) {
|
|
7
|
+
return typeof error === "object" && error !== null && "code" in error
|
|
8
|
+
? String(error.code)
|
|
9
|
+
: undefined;
|
|
10
|
+
}
|
|
11
|
+
function codedError(message, code) {
|
|
12
|
+
const error = new Error(message);
|
|
13
|
+
error.code = code;
|
|
14
|
+
return error;
|
|
15
|
+
}
|
|
16
|
+
async function syncDirectory(path) {
|
|
17
|
+
let handle;
|
|
18
|
+
try {
|
|
19
|
+
handle = await open(path, "r");
|
|
20
|
+
await handle.sync();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (process.platform !== "win32")
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await handle?.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function ensureDirectory(path) {
|
|
31
|
+
const created = await mkdir(path, { recursive: true, mode: 0o700 });
|
|
32
|
+
if (created) {
|
|
33
|
+
await syncDirectory(dirname(resolve(created)));
|
|
34
|
+
await syncDirectory(path);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function publishJsonExclusive(path, value) {
|
|
38
|
+
const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
|
|
39
|
+
let handle;
|
|
40
|
+
try {
|
|
41
|
+
handle = await open(temporary, "wx", 0o600);
|
|
42
|
+
await handle.writeFile(`${JSON.stringify(value)}\n`, "utf8");
|
|
43
|
+
await handle.sync();
|
|
44
|
+
await handle.close();
|
|
45
|
+
handle = undefined;
|
|
46
|
+
await link(temporary, path);
|
|
47
|
+
await syncDirectory(dirname(path));
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
await handle?.close().catch(() => undefined);
|
|
51
|
+
await rm(temporary, { force: true }).catch(() => undefined);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function replaceJsonDurably(path, value) {
|
|
55
|
+
const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
|
|
56
|
+
let handle;
|
|
57
|
+
try {
|
|
58
|
+
handle = await open(temporary, "wx", 0o600);
|
|
59
|
+
await handle.writeFile(`${JSON.stringify(value)}\n`, "utf8");
|
|
60
|
+
await handle.sync();
|
|
61
|
+
await handle.close();
|
|
62
|
+
handle = undefined;
|
|
63
|
+
await rename(temporary, path);
|
|
64
|
+
await syncDirectory(dirname(path));
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await handle?.close().catch(() => undefined);
|
|
68
|
+
await rm(temporary, { force: true }).catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function parseClaim(raw, expectedTicket) {
|
|
72
|
+
try {
|
|
73
|
+
const value = JSON.parse(raw);
|
|
74
|
+
if (value.version !== 1
|
|
75
|
+
|| typeof value.ticket !== "string"
|
|
76
|
+
|| !/^\d{20}$/.test(value.ticket)
|
|
77
|
+
|| (expectedTicket !== undefined && value.ticket !== expectedTicket)
|
|
78
|
+
|| typeof value.token !== "string"
|
|
79
|
+
|| typeof value.pid !== "number"
|
|
80
|
+
|| !Number.isSafeInteger(value.pid)
|
|
81
|
+
|| value.pid <= 0
|
|
82
|
+
|| typeof value.resource !== "string"
|
|
83
|
+
|| typeof value.createdAt !== "string"
|
|
84
|
+
|| (value.state !== "waiting" && value.state !== "released"))
|
|
85
|
+
return null;
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function processIsAlive(pid) {
|
|
93
|
+
try {
|
|
94
|
+
process.kill(pid, 0);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return errorCode(error) === "EPERM";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function wait(ms, signal) {
|
|
102
|
+
signal?.throwIfAborted();
|
|
103
|
+
await new Promise((resolveWait, reject) => {
|
|
104
|
+
const timer = setTimeout(done, ms);
|
|
105
|
+
function done() {
|
|
106
|
+
signal?.removeEventListener("abort", abort);
|
|
107
|
+
resolveWait();
|
|
108
|
+
}
|
|
109
|
+
function abort() {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
signal?.removeEventListener("abort", abort);
|
|
112
|
+
reject(signal?.reason ?? codedError("Lock acquisition aborted", "ABORT_ERR"));
|
|
113
|
+
}
|
|
114
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function allocateClaim(queuePath, resource) {
|
|
118
|
+
for (;;) {
|
|
119
|
+
const names = await readdir(queuePath);
|
|
120
|
+
let maximum = 0n;
|
|
121
|
+
for (const name of names) {
|
|
122
|
+
const match = CLAIM_NAME.exec(name);
|
|
123
|
+
if (match)
|
|
124
|
+
maximum = maximum > BigInt(match[1]) ? maximum : BigInt(match[1]);
|
|
125
|
+
}
|
|
126
|
+
const next = maximum + 1n;
|
|
127
|
+
if (next > MAX_TICKET)
|
|
128
|
+
throw codedError(`Lock ticket space exhausted for ${resource}`, "DURABLE_LOCK_EXHAUSTED");
|
|
129
|
+
const ticket = next.toString().padStart(20, "0");
|
|
130
|
+
const path = join(queuePath, `${ticket}.json`);
|
|
131
|
+
const wire = {
|
|
132
|
+
version: 1,
|
|
133
|
+
ticket,
|
|
134
|
+
token: randomUUID(),
|
|
135
|
+
pid: process.pid,
|
|
136
|
+
resource,
|
|
137
|
+
createdAt: new Date().toISOString(),
|
|
138
|
+
state: "waiting",
|
|
139
|
+
};
|
|
140
|
+
try {
|
|
141
|
+
await publishJsonExclusive(path, wire);
|
|
142
|
+
return { path, wire };
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (errorCode(error) !== "EEXIST")
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Cross-process FIFO lock without stale-owner unlink.
|
|
152
|
+
*
|
|
153
|
+
* Every contender atomically allocates a monotonically increasing immutable
|
|
154
|
+
* ticket. Released and dead-owner tickets stay as tiny tombstones, so no later
|
|
155
|
+
* process can recreate an older pathname or race a compare-then-unlink stale
|
|
156
|
+
* takeover. A dead process is skipped by later tickets; a malformed earlier
|
|
157
|
+
* ticket fails closed until an operator repairs it.
|
|
158
|
+
*/
|
|
159
|
+
export async function acquireDurableTicketLock(requestedQueuePath, resource, options = {}) {
|
|
160
|
+
const queuePath = resolve(requestedQueuePath);
|
|
161
|
+
const timeoutMs = Math.max(1, options.timeoutMs ?? 5_000);
|
|
162
|
+
const pollMs = Math.max(1, options.pollMs ?? 25);
|
|
163
|
+
const alive = options.isProcessAlive ?? processIsAlive;
|
|
164
|
+
const started = Date.now();
|
|
165
|
+
await ensureDirectory(queuePath);
|
|
166
|
+
const claim = await allocateClaim(queuePath, resource);
|
|
167
|
+
let released = false;
|
|
168
|
+
const release = async () => {
|
|
169
|
+
if (released)
|
|
170
|
+
return;
|
|
171
|
+
const current = parseClaim(await readFile(claim.path, "utf8").catch(() => ""), claim.wire.ticket);
|
|
172
|
+
if (!current || current.token !== claim.wire.token || current.resource !== resource) {
|
|
173
|
+
throw codedError(`Durable lock claim changed unexpectedly: ${claim.path}`, "DURABLE_LOCK_CONFLICT");
|
|
174
|
+
}
|
|
175
|
+
if (current.state !== "released") {
|
|
176
|
+
await replaceJsonDurably(claim.path, {
|
|
177
|
+
...current,
|
|
178
|
+
state: "released",
|
|
179
|
+
releasedAt: new Date().toISOString(),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
released = true;
|
|
183
|
+
};
|
|
184
|
+
try {
|
|
185
|
+
for (;;) {
|
|
186
|
+
options.signal?.throwIfAborted();
|
|
187
|
+
const own = parseClaim(await readFile(claim.path, "utf8").catch(() => ""), claim.wire.ticket);
|
|
188
|
+
if (!own || own.token !== claim.wire.token || own.resource !== resource || own.state !== "waiting") {
|
|
189
|
+
throw codedError(`Durable lock claim ownership was lost: ${claim.path}`, "DURABLE_LOCK_CONFLICT");
|
|
190
|
+
}
|
|
191
|
+
const names = (await readdir(queuePath)).filter((name) => CLAIM_NAME.test(name)).sort();
|
|
192
|
+
let blocked = false;
|
|
193
|
+
for (const name of names) {
|
|
194
|
+
const ticket = name.slice(0, 20);
|
|
195
|
+
if (ticket >= claim.wire.ticket)
|
|
196
|
+
break;
|
|
197
|
+
const prior = parseClaim(await readFile(join(queuePath, name), "utf8").catch(() => ""), ticket);
|
|
198
|
+
// A malformed ticket might be a durability fault in a live owner. It
|
|
199
|
+
// cannot be skipped safely, so acquisition times out without mutation.
|
|
200
|
+
if (!prior || (prior.state === "waiting" && await alive(prior.pid))) {
|
|
201
|
+
blocked = true;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (!blocked)
|
|
206
|
+
break;
|
|
207
|
+
if (Date.now() - started >= timeoutMs) {
|
|
208
|
+
throw codedError(`Timed out waiting for durable lock: ${resource}`, "DURABLE_LOCK_TIMEOUT");
|
|
209
|
+
}
|
|
210
|
+
await wait(Math.min(pollMs, Math.max(1, timeoutMs - (Date.now() - started))), options.signal);
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
queuePath,
|
|
214
|
+
claimPath: claim.path,
|
|
215
|
+
ticket: claim.wire.ticket,
|
|
216
|
+
token: claim.wire.token,
|
|
217
|
+
assertOwned: async () => {
|
|
218
|
+
if (released)
|
|
219
|
+
throw codedError(`Durable lock was already released: ${resource}`, "DURABLE_LOCK_CONFLICT");
|
|
220
|
+
const current = parseClaim(await readFile(claim.path, "utf8").catch(() => ""), claim.wire.ticket);
|
|
221
|
+
if (!current || current.token !== claim.wire.token || current.resource !== resource || current.state !== "waiting") {
|
|
222
|
+
throw codedError(`Durable lock ownership was lost: ${resource}`, "DURABLE_LOCK_CONFLICT");
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
release,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
await release().catch(() => undefined);
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
package/dist/src/enroll.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type SyncRules } from "./sync-rules.js";
|
|
2
2
|
import { type RetentionSettings } from "./retention.js";
|
|
3
3
|
export type EnrollOptions = {
|
|
4
|
-
/** The short-lived code shown in the account panel under
|
|
4
|
+
/** The short-lived code shown in the account panel under Machines > Show a pairing code. */
|
|
5
5
|
code: string;
|
|
6
6
|
/** How this machine will show up in the device list. */
|
|
7
7
|
label: string;
|
package/dist/src/enroll.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fail } from "./errors.js";
|
|
2
2
|
import { chooseBackend } from "./secrets.js";
|
|
3
3
|
import { readConfig } from "./vault.js";
|
|
4
|
-
import { DEFAULT_CLOUD_URL, tokenRefFor } from "./cloud.js";
|
|
4
|
+
import { commitCloudAuthCredentials, DEFAULT_CLOUD_URL, tokenRefFor } from "./cloud.js";
|
|
5
5
|
import { cacheSyncRules, cachedSyncRules, DEFAULT_SYNC_RULES } from "./sync-rules.js";
|
|
6
6
|
import { setRetentionPolicy, retentionSettings, DEFAULT_RETENTION } from "./retention.js";
|
|
7
7
|
/**
|
|
@@ -58,7 +58,7 @@ export async function enroll(dataDir, options) {
|
|
|
58
58
|
const code = options.code?.trim() ?? "";
|
|
59
59
|
const label = options.label?.trim() ?? "";
|
|
60
60
|
if (!code)
|
|
61
|
-
fail("invalid_argument", "An enrollment code is required. Find it in the account panel under
|
|
61
|
+
fail("invalid_argument", "An enrollment code is required. Find it in the account panel under Machines > Show a pairing code.");
|
|
62
62
|
if (!label)
|
|
63
63
|
fail("invalid_argument", `A device label is required, e.g. --label "work-laptop", so devices can be told apart later.`);
|
|
64
64
|
// No vault means nowhere to keep the token or the settings it unlocks,
|
|
@@ -97,11 +97,17 @@ export async function enroll(dataDir, options) {
|
|
|
97
97
|
fail("internal", `Sealkeep Cloud accepted code ${code} but did not return a device token. Nothing was stored; run \`sealkeep enroll ${code}\` again.`);
|
|
98
98
|
}
|
|
99
99
|
const enrolled = parsed;
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
await backend
|
|
100
|
+
// Publish the token and its cache generation as one fail-closed transition.
|
|
101
|
+
// The pairing code is single-use, so a full disk must be detected before the
|
|
102
|
+
// credential changes; otherwise the machine could retain the new account
|
|
103
|
+
// under the previous account's cache scope.
|
|
104
|
+
await commitCloudAuthCredentials(dataDir, backend, [{ ref, value: enrolled.token }]);
|
|
105
|
+
// Claim a machine identity too. Without it this machine can seal and upload
|
|
106
|
+
// but cannot delete anything another machine sealed, because the control
|
|
107
|
+
// plane has no way to tell it apart from every other laptop on the account.
|
|
108
|
+
// Best-effort: enrolment succeeding matters more than being nameable.
|
|
109
|
+
const { registerMachine } = await import("./cloud.js");
|
|
110
|
+
await registerMachine(dataDir, enrolled.token);
|
|
105
111
|
let settingsCached = true;
|
|
106
112
|
let cacheWarning;
|
|
107
113
|
let sync;
|
package/dist/src/env.d.ts
CHANGED
|
@@ -6,8 +6,17 @@
|
|
|
6
6
|
* break because we renamed a brand — but everything the product prints, writes,
|
|
7
7
|
* and documents now says SEALKEEP_. When both are set the new name wins, so a
|
|
8
8
|
* machine can be migrated one variable at a time.
|
|
9
|
+
*
|
|
10
|
+
* This fallback was deleted once, on the reasoning that one machine had no
|
|
11
|
+
* VAULTLINE_ variables set. That reasoning was wrong twice over: it generalised
|
|
12
|
+
* from a single machine, and it missed that `mcp install` WROTE the old names
|
|
13
|
+
* into agent config files that are still on disk. An agent spawned from one of
|
|
14
|
+
* those configs would have silently ignored its own configured vault and used
|
|
15
|
+
* the default instead — two vaults, no error, which is the exact failure this
|
|
16
|
+
* product has an acceptance test for.
|
|
9
17
|
*/
|
|
10
|
-
|
|
18
|
+
export declare const PREFIX = "SEALKEEP_";
|
|
19
|
+
/** Reads `SEALKEEP_<name>`, falling back to the Vaultline-era spelling. */
|
|
11
20
|
export declare function envVar(name: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
12
21
|
/** True when either spelling is set to something truthy. */
|
|
13
22
|
export declare function envFlag(name: string, env?: NodeJS.ProcessEnv): boolean;
|
package/dist/src/env.js
CHANGED
|
@@ -6,16 +6,24 @@
|
|
|
6
6
|
* break because we renamed a brand — but everything the product prints, writes,
|
|
7
7
|
* and documents now says SEALKEEP_. When both are set the new name wins, so a
|
|
8
8
|
* machine can be migrated one variable at a time.
|
|
9
|
+
*
|
|
10
|
+
* This fallback was deleted once, on the reasoning that one machine had no
|
|
11
|
+
* VAULTLINE_ variables set. That reasoning was wrong twice over: it generalised
|
|
12
|
+
* from a single machine, and it missed that `mcp install` WROTE the old names
|
|
13
|
+
* into agent config files that are still on disk. An agent spawned from one of
|
|
14
|
+
* those configs would have silently ignored its own configured vault and used
|
|
15
|
+
* the default instead — two vaults, no error, which is the exact failure this
|
|
16
|
+
* product has an acceptance test for.
|
|
9
17
|
*/
|
|
18
|
+
export const PREFIX = "SEALKEEP_";
|
|
10
19
|
const LEGACY_PREFIX = "VAULTLINE_";
|
|
11
|
-
|
|
12
|
-
/** Reads `SEALKEEP_<name>`, falling back to the Vaultline-era name. */
|
|
20
|
+
/** Reads `SEALKEEP_<name>`, falling back to the Vaultline-era spelling. */
|
|
13
21
|
export function envVar(name, env = process.env) {
|
|
14
22
|
const current = env[PREFIX + name];
|
|
15
23
|
if (current !== undefined && current !== "")
|
|
16
24
|
return current;
|
|
17
25
|
const legacy = env[LEGACY_PREFIX + name];
|
|
18
|
-
return legacy === "" ? undefined : legacy;
|
|
26
|
+
return legacy === undefined || legacy === "" ? undefined : legacy;
|
|
19
27
|
}
|
|
20
28
|
/** True when either spelling is set to something truthy. */
|
|
21
29
|
export function envFlag(name, env = process.env) {
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -3,24 +3,24 @@
|
|
|
3
3
|
* between releases; these codes are a contract for the CLI, local API, and MCP.
|
|
4
4
|
* Details are operational only: never place plaintext, phrases, or keys here.
|
|
5
5
|
*/
|
|
6
|
-
export type
|
|
7
|
-
export declare class
|
|
8
|
-
readonly code:
|
|
6
|
+
export type SealkeepErrorCode = "invalid_argument" | "vault_not_initialized" | "vault_already_initialized" | "config_unsupported_version" | "recovery_phrase_missing" | "recovery_phrase_mismatch" | "recipient_key_mismatch" | "payment_required" | "archive_not_found" | "source_unreadable" | "destination_exists" | "destination_unwritable" | "insufficient_disk_space" | "scratch_space_busy" | "path_not_permitted" | "native_restore_unavailable" | "ciphertext_integrity_failed" | "plaintext_integrity_failed" | "hook_payload_invalid" | "queue_job_not_found" | "queue_lease_invalid" | "queue_lease_expired" | "queue_job_exhausted" | "daemon_already_running" | "storage_not_configured" | "storage_read_timeout" | "operation_timeout" | "cloud_account_unavailable" | "provider_unsupported" | "signer_not_configured" | "lease_expired" | "upload_incomplete" | "team_state_changed" | "team_membership_revoked" | "rate_limited" | "unauthorized" | "forbidden" | "internal";
|
|
7
|
+
export declare class SealkeepError extends Error {
|
|
8
|
+
readonly code: SealkeepErrorCode;
|
|
9
9
|
readonly details: Record<string, unknown>;
|
|
10
|
-
constructor(code:
|
|
10
|
+
constructor(code: SealkeepErrorCode, message: string, details?: Record<string, unknown>);
|
|
11
11
|
toJSON(): {
|
|
12
12
|
error: {
|
|
13
|
-
code:
|
|
13
|
+
code: SealkeepErrorCode;
|
|
14
14
|
message: string;
|
|
15
15
|
};
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
-
export declare function fail(code:
|
|
19
|
-
export declare function
|
|
18
|
+
export declare function fail(code: SealkeepErrorCode, message: string, details?: Record<string, unknown>): never;
|
|
19
|
+
export declare function isSealkeepError(value: unknown): value is SealkeepError;
|
|
20
20
|
/** Safe wire/log shape. Unknown failures are never echoed with a stack. */
|
|
21
21
|
export declare function errorPayload(error: unknown): {
|
|
22
22
|
error: {
|
|
23
|
-
code:
|
|
23
|
+
code: SealkeepErrorCode;
|
|
24
24
|
message: string;
|
|
25
25
|
};
|
|
26
26
|
};
|
package/dist/src/errors.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
export class
|
|
1
|
+
export class SealkeepError extends Error {
|
|
2
2
|
code;
|
|
3
3
|
details;
|
|
4
4
|
constructor(code, message, details = {}) {
|
|
5
5
|
super(message);
|
|
6
|
-
this.name = "
|
|
6
|
+
this.name = "SealkeepError";
|
|
7
7
|
this.code = code;
|
|
8
8
|
this.details = details;
|
|
9
9
|
}
|
|
10
10
|
toJSON() { return { error: { code: this.code, message: this.message, ...this.details } }; }
|
|
11
11
|
}
|
|
12
12
|
export function fail(code, message, details) {
|
|
13
|
-
throw new
|
|
13
|
+
throw new SealkeepError(code, message, details);
|
|
14
14
|
}
|
|
15
|
-
export function
|
|
16
|
-
return value instanceof
|
|
15
|
+
export function isSealkeepError(value) {
|
|
16
|
+
return value instanceof SealkeepError;
|
|
17
17
|
}
|
|
18
18
|
/** Safe wire/log shape. Unknown failures are never echoed with a stack. */
|
|
19
19
|
export function errorPayload(error) {
|
|
20
|
-
if (
|
|
20
|
+
if (isSealkeepError(error))
|
|
21
21
|
return error.toJSON();
|
|
22
22
|
return { error: { code: "internal", message: error instanceof Error ? error.message : "Unexpected error" } };
|
|
23
23
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type FileHandle } from "node:fs/promises";
|
|
2
|
+
/** Flags that reject a symlink where a regular file is expected. */
|
|
3
|
+
export declare const FLUSH_NO_FOLLOW: number;
|
|
4
|
+
export type FlushHandle = {
|
|
5
|
+
handle: FileHandle;
|
|
6
|
+
writable: boolean;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* A handle suitable for `handle.sync()`. `extraFlags` carries the caller's own
|
|
10
|
+
* guards (typically `FLUSH_NO_FOLLOW`); pass 0 for none.
|
|
11
|
+
*/
|
|
12
|
+
export declare function openForFlush(path: string, extraFlags?: number): Promise<FlushHandle>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opening a file in order to flush it.
|
|
3
|
+
*
|
|
4
|
+
* `fsync` on POSIX works through any descriptor, so durability code here used
|
|
5
|
+
* to open read-only — often with `O_NOFOLLOW`, because the thing being made
|
|
6
|
+
* durable must be the exact file that was just written and never a symlink
|
|
7
|
+
* planted in its place. Windows implements the same call as
|
|
8
|
+
* FlushFileBuffers, which requires write access: every one of those flushes
|
|
9
|
+
* failed there with EPERM, and with them every archive record write, every
|
|
10
|
+
* reclaim transaction and the Codex resume pointer.
|
|
11
|
+
*
|
|
12
|
+
* So: ask for write access, keep the caller's guard flags, and fall back to a
|
|
13
|
+
* read-only handle when the file genuinely cannot be opened for writing (a
|
|
14
|
+
* restored archive made read-only, a read-only mount). On POSIX the fallback
|
|
15
|
+
* still flushes; on Windows nothing more can be done for such a file, and a
|
|
16
|
+
* caller that must not silently lose durability checks the returned mode.
|
|
17
|
+
*/
|
|
18
|
+
import { constants as fsConstants } from "node:fs";
|
|
19
|
+
import { open } from "node:fs/promises";
|
|
20
|
+
const NO_FOLLOW = typeof fsConstants.O_NOFOLLOW === "number" ? fsConstants.O_NOFOLLOW : 0;
|
|
21
|
+
/** Flags that reject a symlink where a regular file is expected. */
|
|
22
|
+
export const FLUSH_NO_FOLLOW = NO_FOLLOW;
|
|
23
|
+
/**
|
|
24
|
+
* A handle suitable for `handle.sync()`. `extraFlags` carries the caller's own
|
|
25
|
+
* guards (typically `FLUSH_NO_FOLLOW`); pass 0 for none.
|
|
26
|
+
*/
|
|
27
|
+
export async function openForFlush(path, extraFlags = 0) {
|
|
28
|
+
try {
|
|
29
|
+
return { handle: await open(path, fsConstants.O_RDWR | extraFlags), writable: true };
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const code = error.code;
|
|
33
|
+
if (code !== "EACCES" && code !== "EPERM" && code !== "EROFS" && code !== "EISDIR")
|
|
34
|
+
throw error;
|
|
35
|
+
return { handle: await open(path, fsConstants.O_RDONLY | extraFlags), writable: false };
|
|
36
|
+
}
|
|
37
|
+
}
|
package/dist/src/heartbeat.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { UploadOptions, UploadOutcome } from "./upload.js";
|
|
2
|
-
import type { offloadArchives } from "./offload.js";
|
|
2
|
+
import type { FetchClient, offloadArchives } from "./offload.js";
|
|
3
3
|
/**
|
|
4
4
|
* The background service's sign of life.
|
|
5
5
|
*
|
|
@@ -18,17 +18,69 @@ export type Heartbeat = {
|
|
|
18
18
|
intervalMs: number;
|
|
19
19
|
totals: {
|
|
20
20
|
archived: number;
|
|
21
|
+
indexed?: number;
|
|
21
22
|
uploaded: number;
|
|
22
23
|
reclaimed: number;
|
|
23
24
|
failed: number;
|
|
24
25
|
};
|
|
25
26
|
last: {
|
|
26
27
|
archived: number;
|
|
28
|
+
indexed?: number;
|
|
27
29
|
uploaded: number;
|
|
28
30
|
reclaimed: number;
|
|
29
31
|
failed: number;
|
|
30
32
|
notes: string[];
|
|
31
33
|
};
|
|
34
|
+
/** What this running build can do; absent on heartbeats written by older versions. */
|
|
35
|
+
capabilities?: {
|
|
36
|
+
uploads: boolean;
|
|
37
|
+
reclaim: boolean;
|
|
38
|
+
};
|
|
39
|
+
/** Derived search catch-up state; optional for heartbeats written by older versions. */
|
|
40
|
+
indexing?: {
|
|
41
|
+
state: "idle" | "building" | "retry-wait";
|
|
42
|
+
/** Current build attempt, or consecutive failed attempts while waiting. */
|
|
43
|
+
attempts: number;
|
|
44
|
+
startedAt?: string;
|
|
45
|
+
nextAttemptAt?: string;
|
|
46
|
+
/** Stable error name/code only — never an exception message or secret. */
|
|
47
|
+
lastError?: string;
|
|
48
|
+
};
|
|
49
|
+
/** Cloud access requests are independent of archive preservation. */
|
|
50
|
+
teamAccess?: {
|
|
51
|
+
state: "idle" | "working" | "attention";
|
|
52
|
+
failures: number;
|
|
53
|
+
lastAttemptAt?: string;
|
|
54
|
+
lastSuccessAt?: string;
|
|
55
|
+
/** Stable error code only; never an exception message, path, or token. */
|
|
56
|
+
lastError?: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The automatic recall lane (hook-context passes). Hooks fail open by
|
|
60
|
+
* design, so this is the one place a person can see that recall is dying
|
|
61
|
+
* instead of merely absent.
|
|
62
|
+
*/
|
|
63
|
+
context?: {
|
|
64
|
+
state: "idle" | "working" | "attention";
|
|
65
|
+
/** Consecutive failed passes; reset by the next success. */
|
|
66
|
+
failures: number;
|
|
67
|
+
lastAttemptAt?: string;
|
|
68
|
+
lastSuccessAt?: string;
|
|
69
|
+
/** Stable reason only: stalled, absolute, or an error code; never request contents. */
|
|
70
|
+
lastError?: string;
|
|
71
|
+
};
|
|
72
|
+
/** Existing private history becoming usable by a newly connected team. */
|
|
73
|
+
backfill?: {
|
|
74
|
+
state: "idle" | "working" | "attention";
|
|
75
|
+
total: number;
|
|
76
|
+
indexed: number;
|
|
77
|
+
registered: number;
|
|
78
|
+
pending: number;
|
|
79
|
+
failed: number;
|
|
80
|
+
blocked: number;
|
|
81
|
+
/** Stable error name/code only; archive content and paths never appear. */
|
|
82
|
+
lastError?: string;
|
|
83
|
+
};
|
|
32
84
|
paused: boolean;
|
|
33
85
|
watching: string[];
|
|
34
86
|
};
|
|
@@ -63,27 +115,49 @@ export type UploadOffloadOutcome = {
|
|
|
63
115
|
offloaded: string[];
|
|
64
116
|
/** Local bytes given back by offloading verified copies as the pass went. */
|
|
65
117
|
freedBytes: number;
|
|
118
|
+
/** Missed managed team grants recovered after the upload loop. */
|
|
119
|
+
teamRegistered: number;
|
|
66
120
|
};
|
|
121
|
+
type ArchiveCacheSweepOutcome = {
|
|
122
|
+
offloaded: string[];
|
|
123
|
+
freedBytes: number;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Gives the daemon a bounded, retry-cooled cleanup lane for encrypted local
|
|
127
|
+
* duplicates. This runs even when the upload backlog is empty, so a service
|
|
128
|
+
* upgrade or an earlier provider outage cannot strand old `.skarchive` files.
|
|
129
|
+
*/
|
|
130
|
+
export declare function sweepExpiredArchiveCache(dataDir: string, options: {
|
|
131
|
+
cacheDays: number | null;
|
|
132
|
+
offload?: typeof offloadArchives;
|
|
133
|
+
maxArchives?: number;
|
|
134
|
+
now?: number;
|
|
135
|
+
freshlyVerified?: ReadonlySet<string>;
|
|
136
|
+
signal?: AbortSignal;
|
|
137
|
+
client?: FetchClient;
|
|
138
|
+
maxBytesPerSecond?: number;
|
|
139
|
+
}): Promise<ArchiveCacheSweepOutcome>;
|
|
67
140
|
/**
|
|
68
141
|
* The daemon's upload pass, finished properly: upload, verify, then offload.
|
|
69
142
|
*
|
|
70
143
|
* `uploadPending` alone leaves a sealed local copy of every archive it just
|
|
71
|
-
* proved the bucket holds — on the machines this product is for, that
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* lets a full disk breathe with each verify instead of never.
|
|
144
|
+
* proved the bucket holds — on the machines this product is for, that cache
|
|
145
|
+
* must not grow forever. The visible per-machine cache setting decides when a
|
|
146
|
+
* verified duplicate leaves; a value of zero completes that cleanup in this
|
|
147
|
+
* same pass.
|
|
76
148
|
*
|
|
77
149
|
* Offload failure is tolerated per archive: the upload is still verified and
|
|
78
|
-
* recorded, and
|
|
79
|
-
*
|
|
80
|
-
*
|
|
150
|
+
* recorded, and a bounded, backed-off sweep gets another chance. A fresh
|
|
151
|
+
* verify still receives one final streamed whole-chain proof at the actual
|
|
152
|
+
* local purge boundary when the cache setting is zero.
|
|
81
153
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* this change's file list).
|
|
154
|
+
* The daemon calls this combined pass so routing, remote verification, retry
|
|
155
|
+
* fairness, and reclaim all share one ordering boundary.
|
|
85
156
|
*/
|
|
86
157
|
export declare function uploadAndOffloadPending(dataDir: string, options?: UploadOptions & {
|
|
87
158
|
offload?: typeof offloadArchives;
|
|
159
|
+
maxArchives?: number;
|
|
160
|
+
maxOffloads?: number;
|
|
88
161
|
}): Promise<UploadOffloadOutcome>;
|
|
89
162
|
export declare function describeGap(seconds: number): string;
|
|
163
|
+
export {};
|