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,89 @@
|
|
|
1
|
+
import { type IndexPublicationReceipt, type IndexPublicationRequirement } from "./index-publication-state.js";
|
|
2
|
+
import type { RemoteIndexStore } from "./index-sync.js";
|
|
3
|
+
export type IndexPublicationChainLink = {
|
|
4
|
+
archiveId: string;
|
|
5
|
+
archiveRef: string;
|
|
6
|
+
};
|
|
7
|
+
export type IndexPublicationVerificationReason = "invalid_request" | "managed_v2_required" | "ledger_requirement_missing" | "ledger_receipt_missing" | "ledger_receipt_mismatch" | "head_missing" | "head_changed" | "object_integrity" | "index_integrity" | "coverage_mismatch" | "head_not_searchable";
|
|
8
|
+
export declare class IndexPublicationVerificationError extends Error {
|
|
9
|
+
readonly code: "INDEX_PUBLICATION_VERIFICATION_FAILED";
|
|
10
|
+
readonly reason: IndexPublicationVerificationReason;
|
|
11
|
+
readonly cause?: unknown;
|
|
12
|
+
constructor(reason: IndexPublicationVerificationReason, message: string, cause?: unknown);
|
|
13
|
+
}
|
|
14
|
+
export type VerifiedManagedIndexHead = {
|
|
15
|
+
kind: "managed-account-index-v2";
|
|
16
|
+
vaultId: string;
|
|
17
|
+
revision: number;
|
|
18
|
+
generationId: string;
|
|
19
|
+
committedAt: string;
|
|
20
|
+
coverageSha256: string;
|
|
21
|
+
blob: {
|
|
22
|
+
objectKey: string;
|
|
23
|
+
bytes: number;
|
|
24
|
+
sha256: string;
|
|
25
|
+
};
|
|
26
|
+
meta: {
|
|
27
|
+
objectKey: string;
|
|
28
|
+
bytes: number;
|
|
29
|
+
sha256: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export type VerifiedManagedIndexChainLink = IndexPublicationChainLink & {
|
|
33
|
+
entryDigest: string;
|
|
34
|
+
effectivePostingCount: number;
|
|
35
|
+
requirement: IndexPublicationRequirement;
|
|
36
|
+
receipt: IndexPublicationReceipt;
|
|
37
|
+
};
|
|
38
|
+
export type VerifiedManagedAccountIndexPublication = {
|
|
39
|
+
head: VerifiedManagedIndexHead;
|
|
40
|
+
requestedHead: IndexPublicationChainLink;
|
|
41
|
+
extractionPolicy: number;
|
|
42
|
+
/** Physical restore dependencies. Delta bases may be catalog-only. */
|
|
43
|
+
chain: VerifiedManagedIndexChainLink[];
|
|
44
|
+
/** Logical Codex continuation heads. Every one must have searchable postings. */
|
|
45
|
+
searchableHeads: VerifiedManagedIndexChainLink[];
|
|
46
|
+
};
|
|
47
|
+
export type ManagedAccountIndexVerificationOptions = {
|
|
48
|
+
dataDir: string;
|
|
49
|
+
vaultId: string;
|
|
50
|
+
phrase: string;
|
|
51
|
+
extractionPolicy: number;
|
|
52
|
+
chain: readonly IndexPublicationChainLink[];
|
|
53
|
+
requestedHead: IndexPublicationChainLink;
|
|
54
|
+
/**
|
|
55
|
+
* Logical continuation heads that must remain independently discoverable.
|
|
56
|
+
* Defaults to requestedHead for callers that have no repeated-resume chain.
|
|
57
|
+
*/
|
|
58
|
+
searchableHeads?: readonly IndexPublicationChainLink[];
|
|
59
|
+
/** Omit in production to construct the managed account store directly. */
|
|
60
|
+
store?: RemoteIndexStore;
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
idleMs?: number;
|
|
63
|
+
maxBytesPerSecond?: number;
|
|
64
|
+
onProgress?: (bytes: number) => void;
|
|
65
|
+
};
|
|
66
|
+
export type ManagedAccountIndexRecheckOptions = {
|
|
67
|
+
dataDir: string;
|
|
68
|
+
verification: VerifiedManagedAccountIndexPublication;
|
|
69
|
+
/** Omit in production to construct the managed account store directly. */
|
|
70
|
+
store?: RemoteIndexStore;
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
idleMs?: number;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Full positive proof used before reclaim can become eligible.
|
|
76
|
+
*
|
|
77
|
+
* It accepts only the managed immutable/CAS lane. Local ledger receipts alone,
|
|
78
|
+
* legacy v9 slots, Google Drive, and own-bucket stores can never authorize a
|
|
79
|
+
* source deletion. The returned head token is deliberately sufficient for a
|
|
80
|
+
* cheap exact-head recheck immediately before unlink.
|
|
81
|
+
*/
|
|
82
|
+
export declare function verifyManagedAccountIndexPublication(options: ManagedAccountIndexVerificationOptions): Promise<VerifiedManagedAccountIndexPublication>;
|
|
83
|
+
/**
|
|
84
|
+
* Last destructive-boundary check. It performs no large download: every local
|
|
85
|
+
* obligation/receipt must still be exact, and the registry must still expose
|
|
86
|
+
* the same immutable head (revision, generation, object digests, and coverage).
|
|
87
|
+
* Any advance causes the caller to repeat the full verification.
|
|
88
|
+
*/
|
|
89
|
+
export declare function recheckManagedAccountIndexPublicationBeforeUnlink(options: ManagedAccountIndexRecheckOptions): Promise<void>;
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { canonicalIndexPublicationCoverageJson, } from "./index-publication-proof.js";
|
|
3
|
+
import { getIndexPublicationReceipt, listIndexPublicationRequirements, } from "./index-publication-state.js";
|
|
4
|
+
import { canonicalPhrase } from "./mnemonic.js";
|
|
5
|
+
import { createProgressDeadline } from "./progress-deadline.js";
|
|
6
|
+
import { openVerifiedManagedIndexV3Streaming } from "./search.js";
|
|
7
|
+
const DEFAULT_VERIFY_IDLE_MS = 2 * 60_000;
|
|
8
|
+
const MANAGED_PUBLISHER = "managed-cas-v2";
|
|
9
|
+
export class IndexPublicationVerificationError extends Error {
|
|
10
|
+
code = "INDEX_PUBLICATION_VERIFICATION_FAILED";
|
|
11
|
+
reason;
|
|
12
|
+
cause;
|
|
13
|
+
constructor(reason, message, cause) {
|
|
14
|
+
super(message, cause === undefined ? undefined : { cause });
|
|
15
|
+
this.name = "IndexPublicationVerificationError";
|
|
16
|
+
this.reason = reason;
|
|
17
|
+
this.cause = cause;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function refuse(reason, message, cause) {
|
|
21
|
+
throw new IndexPublicationVerificationError(reason, message, cause);
|
|
22
|
+
}
|
|
23
|
+
function text(value, label) {
|
|
24
|
+
if (typeof value !== "string" || value.length === 0)
|
|
25
|
+
return refuse("object_integrity", `The managed index has no ${label}`);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function sha(value, label) {
|
|
29
|
+
const digest = text(value, label);
|
|
30
|
+
if (!/^[0-9a-f]{64}$/.test(digest))
|
|
31
|
+
return refuse("object_integrity", `The managed index has an invalid ${label}`);
|
|
32
|
+
return digest;
|
|
33
|
+
}
|
|
34
|
+
function integer(value, label, minimum) {
|
|
35
|
+
if (!Number.isSafeInteger(value) || Number(value) < minimum) {
|
|
36
|
+
return refuse("object_integrity", `The managed index has an invalid ${label}`);
|
|
37
|
+
}
|
|
38
|
+
return Number(value);
|
|
39
|
+
}
|
|
40
|
+
function sameObject(left, right) {
|
|
41
|
+
return left.objectKey === right.objectKey && left.bytes === right.bytes && left.sha256 === right.sha256;
|
|
42
|
+
}
|
|
43
|
+
function managedHeadToken(vaultId, receipt) {
|
|
44
|
+
if (receipt.membershipVersion !== undefined) {
|
|
45
|
+
return refuse("managed_v2_required", "A team index head cannot authorize account-session reclaim");
|
|
46
|
+
}
|
|
47
|
+
const coverage = canonicalIndexPublicationCoverageJson(receipt.coverageJson);
|
|
48
|
+
return {
|
|
49
|
+
kind: "managed-account-index-v2",
|
|
50
|
+
vaultId,
|
|
51
|
+
revision: integer(receipt.revision, "head revision", 1),
|
|
52
|
+
generationId: text(receipt.generationId, "generation id"),
|
|
53
|
+
committedAt: text(receipt.committedAt, "commit time"),
|
|
54
|
+
coverageSha256: createHash("sha256").update(coverage, "utf8").digest("hex"),
|
|
55
|
+
blob: {
|
|
56
|
+
objectKey: text(receipt.blob?.objectKey, "blob object key"),
|
|
57
|
+
bytes: integer(receipt.blob?.bytes, "blob byte count", 1),
|
|
58
|
+
sha256: sha(receipt.blob?.sha256, "blob SHA-256"),
|
|
59
|
+
},
|
|
60
|
+
meta: {
|
|
61
|
+
objectKey: text(receipt.meta?.objectKey, "metadata object key"),
|
|
62
|
+
bytes: integer(receipt.meta?.bytes, "metadata byte count", 1),
|
|
63
|
+
sha256: sha(receipt.meta?.sha256, "metadata SHA-256"),
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function sameHead(left, right) {
|
|
68
|
+
return left.kind === right.kind
|
|
69
|
+
&& left.vaultId === right.vaultId
|
|
70
|
+
&& left.revision === right.revision
|
|
71
|
+
&& left.generationId === right.generationId
|
|
72
|
+
&& left.committedAt === right.committedAt
|
|
73
|
+
&& left.coverageSha256 === right.coverageSha256
|
|
74
|
+
&& sameObject(left.blob, right.blob)
|
|
75
|
+
&& sameObject(left.meta, right.meta);
|
|
76
|
+
}
|
|
77
|
+
function sameReceipt(left, right) {
|
|
78
|
+
return left.requirementKey === right.requirementKey
|
|
79
|
+
&& left.policyVersion === right.policyVersion
|
|
80
|
+
&& left.publisherId === right.publisherId
|
|
81
|
+
&& left.generationId === right.generationId
|
|
82
|
+
&& left.registryReceiptId === right.registryReceiptId
|
|
83
|
+
&& left.registryRevision === right.registryRevision
|
|
84
|
+
&& left.objectRef === right.objectRef
|
|
85
|
+
&& left.ciphertextSha256 === right.ciphertextSha256
|
|
86
|
+
&& left.ciphertextBytes === right.ciphertextBytes
|
|
87
|
+
&& left.committedAt === right.committedAt;
|
|
88
|
+
}
|
|
89
|
+
function receiptMatchesHead(receipt, head) {
|
|
90
|
+
return receipt.publisherId === MANAGED_PUBLISHER
|
|
91
|
+
&& receipt.generationId === head.generationId
|
|
92
|
+
&& receipt.registryReceiptId === `index-v2:${head.revision}:${head.generationId}`
|
|
93
|
+
&& receipt.registryRevision === head.revision
|
|
94
|
+
&& receipt.objectRef === JSON.stringify([head.blob.objectKey, head.meta.objectKey])
|
|
95
|
+
&& receipt.ciphertextSha256 === head.blob.sha256
|
|
96
|
+
&& receipt.ciphertextBytes === head.blob.bytes
|
|
97
|
+
&& receipt.committedAt === head.committedAt;
|
|
98
|
+
}
|
|
99
|
+
function validateLinks(links, label) {
|
|
100
|
+
const validated = links.map((link) => {
|
|
101
|
+
if (!link || typeof link.archiveId !== "string" || !link.archiveId
|
|
102
|
+
|| typeof link.archiveRef !== "string" || !link.archiveRef) {
|
|
103
|
+
return refuse("invalid_request", `Every ${label} link needs an archive id and reference`);
|
|
104
|
+
}
|
|
105
|
+
return { archiveId: link.archiveId, archiveRef: link.archiveRef };
|
|
106
|
+
});
|
|
107
|
+
if (new Set(validated.map((link) => link.archiveId)).size !== validated.length
|
|
108
|
+
|| new Set(validated.map((link) => link.archiveRef)).size !== validated.length) {
|
|
109
|
+
return refuse("invalid_request", `The ${label} contains duplicate archive identities`);
|
|
110
|
+
}
|
|
111
|
+
return validated;
|
|
112
|
+
}
|
|
113
|
+
function validateRequest(options) {
|
|
114
|
+
if (!options.dataDir || !options.vaultId || !options.phrase) {
|
|
115
|
+
return refuse("invalid_request", "Index publication verification requires a data directory, vault id, and phrase");
|
|
116
|
+
}
|
|
117
|
+
if (!Number.isSafeInteger(options.extractionPolicy) || options.extractionPolicy < 1) {
|
|
118
|
+
return refuse("invalid_request", "Index publication verification requires a positive extraction policy");
|
|
119
|
+
}
|
|
120
|
+
if (!Array.isArray(options.chain) || options.chain.length === 0) {
|
|
121
|
+
return refuse("invalid_request", "Index publication verification requires an archive chain");
|
|
122
|
+
}
|
|
123
|
+
const chain = validateLinks(options.chain, "index publication chain");
|
|
124
|
+
if (!options.requestedHead
|
|
125
|
+
|| !chain.some((link) => link.archiveId === options.requestedHead.archiveId
|
|
126
|
+
&& link.archiveRef === options.requestedHead.archiveRef)) {
|
|
127
|
+
return refuse("invalid_request", "The requested session head is not in its archive chain");
|
|
128
|
+
}
|
|
129
|
+
const searchableHeads = validateLinks(options.searchableHeads ?? [options.requestedHead], "searchable continuation set");
|
|
130
|
+
if (searchableHeads.length === 0
|
|
131
|
+
|| !searchableHeads.some((link) => link.archiveId === options.requestedHead.archiveId
|
|
132
|
+
&& link.archiveRef === options.requestedHead.archiveRef)) {
|
|
133
|
+
return refuse("invalid_request", "The requested session head is not in its searchable continuation set");
|
|
134
|
+
}
|
|
135
|
+
const obligations = new Map();
|
|
136
|
+
for (const link of [...chain, ...searchableHeads]) {
|
|
137
|
+
const sameArchive = [...obligations.values()].find((candidate) => candidate.archiveId === link.archiveId);
|
|
138
|
+
const sameRef = [...obligations.values()].find((candidate) => candidate.archiveRef === link.archiveRef);
|
|
139
|
+
if ((sameArchive && sameArchive.archiveRef !== link.archiveRef)
|
|
140
|
+
|| (sameRef && sameRef.archiveId !== link.archiveId)) {
|
|
141
|
+
return refuse("invalid_request", "An archive obligation has conflicting ids or references");
|
|
142
|
+
}
|
|
143
|
+
obligations.set(`${link.archiveId}\u0000${link.archiveRef}`, link);
|
|
144
|
+
}
|
|
145
|
+
return { chain, searchableHeads, obligations: [...obligations.values()] };
|
|
146
|
+
}
|
|
147
|
+
async function accountStore(dataDir, injected, signal) {
|
|
148
|
+
if (injected)
|
|
149
|
+
return injected;
|
|
150
|
+
const { managedIndexStore } = await import("./index-sync.js");
|
|
151
|
+
return managedIndexStore(dataDir, { signal });
|
|
152
|
+
}
|
|
153
|
+
function verifyObjectBytes(body, expected, label) {
|
|
154
|
+
if (!Buffer.isBuffer(body)
|
|
155
|
+
|| body.length !== expected.bytes
|
|
156
|
+
|| createHash("sha256").update(body).digest("hex") !== expected.sha256) {
|
|
157
|
+
refuse("object_integrity", `The managed index ${label} does not match its immutable head receipt`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function envelopeBytes(envelope) {
|
|
161
|
+
try {
|
|
162
|
+
const raw = JSON.stringify(envelope);
|
|
163
|
+
if (!raw)
|
|
164
|
+
return refuse("object_integrity", "The managed index metadata is not serializable");
|
|
165
|
+
return Buffer.from(raw, "utf8");
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
return refuse("object_integrity", "The managed index metadata is not serializable", error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Full positive proof used before reclaim can become eligible.
|
|
173
|
+
*
|
|
174
|
+
* It accepts only the managed immutable/CAS lane. Local ledger receipts alone,
|
|
175
|
+
* legacy v9 slots, Google Drive, and own-bucket stores can never authorize a
|
|
176
|
+
* source deletion. The returned head token is deliberately sufficient for a
|
|
177
|
+
* cheap exact-head recheck immediately before unlink.
|
|
178
|
+
*/
|
|
179
|
+
export async function verifyManagedAccountIndexPublication(options) {
|
|
180
|
+
options.signal?.throwIfAborted();
|
|
181
|
+
const { chain, searchableHeads, obligations: requestedObligations } = validateRequest(options);
|
|
182
|
+
const statuses = await listIndexPublicationRequirements(options.dataDir, { vaultId: options.vaultId });
|
|
183
|
+
options.signal?.throwIfAborted();
|
|
184
|
+
const obligations = requestedObligations.map((link) => {
|
|
185
|
+
const matches = statuses.filter((status) => status.requirement.scope.kind === "account"
|
|
186
|
+
&& status.requirement.scope.vaultId === options.vaultId
|
|
187
|
+
&& status.requirement.archiveId === link.archiveId
|
|
188
|
+
&& status.requirement.archiveRef === link.archiveRef);
|
|
189
|
+
if (matches.length !== 1) {
|
|
190
|
+
return refuse("ledger_requirement_missing", `No exact current account-index requirement exists for ${link.archiveId}`);
|
|
191
|
+
}
|
|
192
|
+
const status = matches[0];
|
|
193
|
+
if (!status.receipt) {
|
|
194
|
+
return refuse("ledger_receipt_missing", `No positive managed-index receipt exists for ${link.archiveId}`);
|
|
195
|
+
}
|
|
196
|
+
return { link, requirement: status.requirement, receipt: status.receipt };
|
|
197
|
+
});
|
|
198
|
+
const store = await accountStore(options.dataDir, options.store, options.signal);
|
|
199
|
+
if (!store.cas)
|
|
200
|
+
return refuse("managed_v2_required", "This storage target has no immutable managed index protocol");
|
|
201
|
+
const deadline = createProgressDeadline({
|
|
202
|
+
operation: "Verifying the managed search index",
|
|
203
|
+
idleMs: options.idleMs ?? DEFAULT_VERIFY_IDLE_MS,
|
|
204
|
+
signal: options.signal,
|
|
205
|
+
});
|
|
206
|
+
try {
|
|
207
|
+
const pulled = await deadline.wait(store.cas.pullSnapshot({
|
|
208
|
+
signal: deadline.signal,
|
|
209
|
+
maxBytesPerSecond: options.maxBytesPerSecond,
|
|
210
|
+
onProgress: (bytes) => {
|
|
211
|
+
deadline.progress();
|
|
212
|
+
options.onProgress?.(bytes);
|
|
213
|
+
},
|
|
214
|
+
}));
|
|
215
|
+
deadline.progress();
|
|
216
|
+
deadline.signal.throwIfAborted();
|
|
217
|
+
if (pulled.status === "unsupported") {
|
|
218
|
+
return refuse("managed_v2_required", "The managed service does not support immutable index generations");
|
|
219
|
+
}
|
|
220
|
+
if (!pulled.snapshot)
|
|
221
|
+
return refuse("head_missing", "No committed managed index generation exists");
|
|
222
|
+
const snapshot = pulled.snapshot;
|
|
223
|
+
const head = managedHeadToken(options.vaultId, snapshot.receipt);
|
|
224
|
+
const nestedHead = managedHeadToken(options.vaultId, snapshot.head.receipt);
|
|
225
|
+
if (!sameHead(head, nestedHead)) {
|
|
226
|
+
return refuse("head_changed", "The downloaded index snapshot is not bound to one exact registry head");
|
|
227
|
+
}
|
|
228
|
+
verifyObjectBytes(snapshot.ciphertext, head.blob, "ciphertext");
|
|
229
|
+
verifyObjectBytes(envelopeBytes(snapshot.envelope), head.meta, "metadata");
|
|
230
|
+
if (snapshot.envelope.manifest?.adapter?.agent !== "sealkeep-index") {
|
|
231
|
+
return refuse("index_integrity", "The committed object is not a Sealkeep search index");
|
|
232
|
+
}
|
|
233
|
+
let proof;
|
|
234
|
+
try {
|
|
235
|
+
proof = await openVerifiedManagedIndexV3Streaming(snapshot.envelope, snapshot.ciphertext, { phrase: canonicalPhrase(options.phrase) }, snapshot.receipt.coverageJson, {
|
|
236
|
+
signal: deadline.signal,
|
|
237
|
+
onYield: () => { deadline.progress(); },
|
|
238
|
+
});
|
|
239
|
+
deadline.progress();
|
|
240
|
+
deadline.signal.throwIfAborted();
|
|
241
|
+
if (proof.extractionPolicy !== options.extractionPolicy) {
|
|
242
|
+
return refuse("index_integrity", "The committed search index uses a different extraction policy");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
if (error instanceof IndexPublicationVerificationError)
|
|
247
|
+
throw error;
|
|
248
|
+
if (error instanceof Error
|
|
249
|
+
&& error.message === "The committed search index does not contain the coverage recorded by its head") {
|
|
250
|
+
return refuse("coverage_mismatch", "The registry coverage does not describe the authenticated index bytes", error);
|
|
251
|
+
}
|
|
252
|
+
return refuse("index_integrity", "The committed search index could not be authenticated and parsed", error);
|
|
253
|
+
}
|
|
254
|
+
const recomputedCoverage = canonicalIndexPublicationCoverageJson(proof.coverageJson);
|
|
255
|
+
const advertisedCoverage = canonicalIndexPublicationCoverageJson(snapshot.receipt.coverageJson);
|
|
256
|
+
if (recomputedCoverage !== advertisedCoverage) {
|
|
257
|
+
return refuse("coverage_mismatch", "The registry coverage does not describe the authenticated index bytes");
|
|
258
|
+
}
|
|
259
|
+
const verifiedObligations = obligations.map(({ link, requirement, receipt }) => {
|
|
260
|
+
if (!receiptMatchesHead(receipt, head)) {
|
|
261
|
+
return refuse("ledger_receipt_mismatch", `The local publication receipt for ${link.archiveId} is not for the current managed head`);
|
|
262
|
+
}
|
|
263
|
+
if (proof.coverageJson[link.archiveRef] !== requirement.entryDigest) {
|
|
264
|
+
return refuse("coverage_mismatch", `The current managed index does not contain the exact entry for ${link.archiveId}`);
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
...link,
|
|
268
|
+
entryDigest: requirement.entryDigest,
|
|
269
|
+
effectivePostingCount: proof.effectivePostingCounts[link.archiveRef] ?? 0,
|
|
270
|
+
requirement: structuredClone(requirement),
|
|
271
|
+
receipt: structuredClone(receipt),
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
const verifiedByIdentity = new Map(verifiedObligations.map((link) => [
|
|
275
|
+
`${link.archiveId}\u0000${link.archiveRef}`,
|
|
276
|
+
link,
|
|
277
|
+
]));
|
|
278
|
+
const verifiedChain = chain.map((link) => verifiedByIdentity.get(`${link.archiveId}\u0000${link.archiveRef}`));
|
|
279
|
+
const verifiedSearchableHeads = searchableHeads.map((link) => verifiedByIdentity.get(`${link.archiveId}\u0000${link.archiveRef}`));
|
|
280
|
+
for (const searchable of verifiedSearchableHeads) {
|
|
281
|
+
if (searchable.effectivePostingCount <= 0) {
|
|
282
|
+
return refuse("head_not_searchable", `The searchable continuation ${searchable.archiveId} has no effective searchable postings`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
head,
|
|
287
|
+
requestedHead: { ...options.requestedHead },
|
|
288
|
+
extractionPolicy: options.extractionPolicy,
|
|
289
|
+
chain: verifiedChain,
|
|
290
|
+
searchableHeads: verifiedSearchableHeads,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
deadline.close();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Last destructive-boundary check. It performs no large download: every local
|
|
299
|
+
* obligation/receipt must still be exact, and the registry must still expose
|
|
300
|
+
* the same immutable head (revision, generation, object digests, and coverage).
|
|
301
|
+
* Any advance causes the caller to repeat the full verification.
|
|
302
|
+
*/
|
|
303
|
+
export async function recheckManagedAccountIndexPublicationBeforeUnlink(options) {
|
|
304
|
+
options.signal?.throwIfAborted();
|
|
305
|
+
const verification = options.verification;
|
|
306
|
+
const obligations = new Map();
|
|
307
|
+
for (const link of [...verification.chain, ...(verification.searchableHeads ?? [])]) {
|
|
308
|
+
obligations.set(`${link.archiveId}\u0000${link.archiveRef}`, link);
|
|
309
|
+
}
|
|
310
|
+
for (const link of obligations.values()) {
|
|
311
|
+
const receipt = await getIndexPublicationReceipt(options.dataDir, { vaultId: verification.head.vaultId }, link.requirement);
|
|
312
|
+
if (!receipt || !sameReceipt(receipt, link.receipt)) {
|
|
313
|
+
return refuse("ledger_receipt_mismatch", `The publication receipt changed before unlink for ${link.archiveId}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
options.signal?.throwIfAborted();
|
|
317
|
+
const store = await accountStore(options.dataDir, options.store, options.signal);
|
|
318
|
+
if (!store.cas)
|
|
319
|
+
return refuse("managed_v2_required", "This storage target has no immutable managed index protocol");
|
|
320
|
+
const deadline = createProgressDeadline({
|
|
321
|
+
operation: "Rechecking the managed search index head",
|
|
322
|
+
idleMs: options.idleMs ?? DEFAULT_VERIFY_IDLE_MS,
|
|
323
|
+
signal: options.signal,
|
|
324
|
+
});
|
|
325
|
+
try {
|
|
326
|
+
const current = await deadline.wait(store.cas.head({ signal: deadline.signal }));
|
|
327
|
+
deadline.progress();
|
|
328
|
+
if (current.status === "unsupported") {
|
|
329
|
+
return refuse("managed_v2_required", "The managed service no longer supports immutable index generations");
|
|
330
|
+
}
|
|
331
|
+
if (!current.head)
|
|
332
|
+
return refuse("head_missing", "The managed search index head disappeared before unlink");
|
|
333
|
+
const observed = managedHeadToken(verification.head.vaultId, current.head.receipt);
|
|
334
|
+
if (!sameHead(observed, verification.head)) {
|
|
335
|
+
return refuse("head_changed", "The managed search index head changed before unlink; full verification must run again");
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
deadline.close();
|
|
340
|
+
}
|
|
341
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { RemoteIndexStore } from "./index-sync.js";
|
|
2
|
+
export type PublishedBlobState = {
|
|
3
|
+
version: 1;
|
|
4
|
+
generation: string;
|
|
5
|
+
builtAt: string;
|
|
6
|
+
};
|
|
7
|
+
/** Called by `publishFromSegments` (src/index-store.ts) after a successful
|
|
8
|
+
* push, so `publishPending` and the blob-fallback `pullSegments` both know
|
|
9
|
+
* which generation and builtAt reached the store last. */
|
|
10
|
+
export declare function writePublishedBlobState(dataDir: string, state: PublishedBlobState): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* Uploads every local segment this machine has not yet published, deletes the
|
|
13
|
+
* remote copies of ids compaction or retirement has since replaced locally,
|
|
14
|
+
* republishes this machine's `head.json`, and re-registers it in the shared
|
|
15
|
+
* `heads.json`. Idempotent by construction: `head.json` is written only after
|
|
16
|
+
* every object it lists has actually landed, so a crash between any two steps
|
|
17
|
+
* is simply repaired by the next call, and nothing is re-uploaded because
|
|
18
|
+
* `published.json` is the last thing written.
|
|
19
|
+
*/
|
|
20
|
+
export declare function publishSegments(dataDir: string, phrase: string, store: RemoteIndexStore | null, options?: {
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
onYield?: () => void | Promise<void>;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
published: number;
|
|
25
|
+
removed: number;
|
|
26
|
+
skipped?: true;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* True when a segments-mode vault has local segment content the account
|
|
30
|
+
* store has not seen yet — the daemon's cue to run a catch-up pass even when
|
|
31
|
+
* nothing is left to index. `published.json` (a segment store) and
|
|
32
|
+
* `published-blob.json` (the blob fallback) each name the manifest
|
|
33
|
+
* generation their own kind of publish last reached; either matching the
|
|
34
|
+
* current manifest counts as "nothing pending", since a vault only ever uses
|
|
35
|
+
* one or the other depending on what the account store supports. A blob-mode
|
|
36
|
+
* vault (not segments) always answers false: `indexArchiveTokens` already
|
|
37
|
+
* publishes inline at seal time there, so the daemon needs no extra nudge.
|
|
38
|
+
*/
|
|
39
|
+
export declare function publishPending(dataDir: string): Promise<boolean>;
|
|
40
|
+
/**
|
|
41
|
+
* Reads the shared `heads.json`, then for every other machine whose
|
|
42
|
+
* generation has moved since this machine last reconciled it: fetches that
|
|
43
|
+
* machine's `head.json`, downloads whichever listed segments are not already
|
|
44
|
+
* local, verifies each, and replaces that machine's `origin: "remote"`
|
|
45
|
+
* entries in the local manifest with the current set in one `updateManifest`
|
|
46
|
+
* — dropping local files for ids it no longer lists only after the manifest
|
|
47
|
+
* no longer references them. A machine whose `head.json` has vanished (404)
|
|
48
|
+
* loses its remote entries entirely.
|
|
49
|
+
*
|
|
50
|
+
* A store with no generic keyed storage (managed today) has no `<machineId>`
|
|
51
|
+
* objects to read at all; `pullFromBlobFallback` converts its single legacy
|
|
52
|
+
* blob slot instead (step 2 addendum — see docs/design/index-publish.md).
|
|
53
|
+
*/
|
|
54
|
+
export declare function pullSegments(dataDir: string, phrase: string, store: RemoteIndexStore | null, options?: {
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
onYield?: () => void | Promise<void>;
|
|
57
|
+
}): Promise<{
|
|
58
|
+
machines: number;
|
|
59
|
+
added: number;
|
|
60
|
+
removed: number;
|
|
61
|
+
skipped?: true;
|
|
62
|
+
}>;
|