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.
Files changed (274) hide show
  1. package/ARCHITECTURE.md +163 -14
  2. package/CHANGELOG.md +223 -1
  3. package/CONTROL_PLANE.md +2 -2
  4. package/LICENSE +1 -1
  5. package/README.md +108 -22
  6. package/THIRD_PARTY.md +2 -2
  7. package/THREAT_MODEL.md +23 -4
  8. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/chunk-access.d.ts +26 -4
  9. package/dist/packages/sealkeep-crypto/src/chunk-access.js +219 -0
  10. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/envelope.d.ts +11 -1
  11. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/envelope.js +58 -21
  12. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/format.d.ts +2 -2
  13. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/index.d.ts +1 -0
  14. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/index.js +1 -0
  15. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/recipients.js +4 -4
  16. package/dist/packages/sealkeep-crypto/src/squeeze.d.ts +6 -0
  17. package/dist/packages/sealkeep-crypto/src/squeeze.js +39 -0
  18. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/stream.d.ts +36 -3
  19. package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/stream.js +196 -37
  20. package/dist/site/index.html +1808 -1904
  21. package/dist/site/llms.txt +67 -0
  22. package/dist/site/trust/architecture-data-flow.html +53 -0
  23. package/dist/site/trust/audit-roadmap.html +37 -0
  24. package/dist/site/trust/deployment-responsibility.html +11 -0
  25. package/dist/site/trust/dpa-sample.html +30 -0
  26. package/dist/site/trust/release-provenance.html +21 -0
  27. package/dist/site/trust/subprocessors.html +15 -0
  28. package/dist/site/trust/threat-model.html +34 -0
  29. package/dist/site/trust/toms.html +41 -0
  30. package/dist/site/trust-document.css +32 -0
  31. package/dist/site/trust.html +73 -0
  32. package/dist/site/visual/assets/index-8Nxnkc7o.js +207 -0
  33. package/dist/site/visual/assets/index-oxLy1bpy.css +1 -0
  34. package/dist/site/visual/index.html +18 -0
  35. package/dist/site.zip +0 -0
  36. package/dist/src/activity.d.ts +9 -0
  37. package/dist/src/activity.js +90 -1
  38. package/dist/src/adapters.d.ts +175 -5
  39. package/dist/src/adapters.js +961 -73
  40. package/dist/src/agent-context.d.ts +135 -0
  41. package/dist/src/agent-context.js +1059 -0
  42. package/dist/src/archive-copies.d.ts +47 -0
  43. package/dist/src/archive-copies.js +179 -0
  44. package/dist/src/audit.d.ts +1 -1
  45. package/dist/src/audit.js +29 -4
  46. package/dist/src/autopilot.d.ts +52 -7
  47. package/dist/src/autopilot.js +143 -25
  48. package/dist/src/background-bandwidth.d.ts +46 -0
  49. package/dist/src/background-bandwidth.js +301 -0
  50. package/dist/src/background-cpu.d.ts +82 -0
  51. package/dist/src/background-cpu.js +212 -0
  52. package/dist/src/background-worker-error.d.ts +12 -0
  53. package/dist/src/background-worker-error.js +18 -0
  54. package/dist/src/branding.d.ts +24 -1
  55. package/dist/src/branding.js +31 -1
  56. package/dist/src/bridge.d.ts +233 -0
  57. package/dist/src/bridge.js +604 -0
  58. package/dist/src/byte-stream.d.ts +91 -0
  59. package/dist/src/byte-stream.js +385 -0
  60. package/dist/src/chunk-store.d.ts +41 -8
  61. package/dist/src/chunk-store.js +161 -65
  62. package/dist/src/cli.js +1698 -163
  63. package/dist/src/cloud.d.ts +841 -31
  64. package/dist/src/cloud.js +3196 -277
  65. package/dist/src/context-background.d.ts +37 -0
  66. package/dist/src/context-background.js +309 -0
  67. package/dist/src/context-drain-child.d.ts +1 -0
  68. package/dist/src/context-drain-child.js +98 -0
  69. package/dist/src/context-reader.d.ts +118 -0
  70. package/dist/src/context-reader.js +447 -0
  71. package/dist/src/control-plane/auth.d.ts +32 -4
  72. package/dist/src/control-plane/auth.js +85 -24
  73. package/dist/src/control-plane/server.js +19 -6
  74. package/dist/src/control-plane.d.ts +17 -1
  75. package/dist/src/control-plane.js +32 -6
  76. package/dist/src/crypto.d.ts +1 -1
  77. package/dist/src/crypto.js +5 -5
  78. package/dist/src/daemon-lease.d.ts +70 -0
  79. package/dist/src/daemon-lease.js +420 -0
  80. package/dist/src/daemon.d.ts +94 -1
  81. package/dist/src/daemon.js +1082 -105
  82. package/dist/src/darwin-service-policy.d.ts +41 -0
  83. package/dist/src/darwin-service-policy.js +60 -0
  84. package/dist/src/dashboard-cli.js +15 -15
  85. package/dist/src/device-authorization.d.ts +37 -0
  86. package/dist/src/device-authorization.js +199 -0
  87. package/dist/src/device-enrollment.d.ts +91 -0
  88. package/dist/src/device-enrollment.js +349 -0
  89. package/dist/src/disk.d.ts +17 -12
  90. package/dist/src/disk.js +43 -17
  91. package/dist/src/doctor.d.ts +35 -1
  92. package/dist/src/doctor.js +316 -41
  93. package/dist/src/durable-ticket-lock.d.ts +24 -0
  94. package/dist/src/durable-ticket-lock.js +232 -0
  95. package/dist/src/enroll.d.ts +1 -1
  96. package/dist/src/enroll.js +13 -7
  97. package/dist/src/env.d.ts +10 -1
  98. package/dist/src/env.js +11 -3
  99. package/dist/src/errors.d.ts +8 -8
  100. package/dist/src/errors.js +6 -6
  101. package/dist/src/flush.d.ts +12 -0
  102. package/dist/src/flush.js +37 -0
  103. package/dist/src/heartbeat.d.ts +86 -12
  104. package/dist/src/heartbeat.js +415 -29
  105. package/dist/src/index-background-watchdog.d.ts +1 -0
  106. package/dist/src/index-background-watchdog.js +94 -0
  107. package/dist/src/index-background-work.d.ts +21 -0
  108. package/dist/src/index-background-work.js +25 -0
  109. package/dist/src/index-background.d.ts +64 -0
  110. package/dist/src/index-background.js +394 -0
  111. package/dist/src/index-build-child.d.ts +1 -0
  112. package/dist/src/index-build-child.js +109 -0
  113. package/dist/src/index-manifest.d.ts +52 -0
  114. package/dist/src/index-manifest.js +444 -0
  115. package/dist/src/index-publication-proof.d.ts +84 -0
  116. package/dist/src/index-publication-proof.js +380 -0
  117. package/dist/src/index-publication-state.d.ts +149 -0
  118. package/dist/src/index-publication-state.js +696 -0
  119. package/dist/src/index-publication-verifier.d.ts +89 -0
  120. package/dist/src/index-publication-verifier.js +341 -0
  121. package/dist/src/index-publish.d.ts +62 -0
  122. package/dist/src/index-publish.js +540 -0
  123. package/dist/src/index-scratch-cleanup.d.ts +19 -0
  124. package/dist/src/index-scratch-cleanup.js +166 -0
  125. package/dist/src/index-segment-types.d.ts +132 -0
  126. package/dist/src/index-segment-types.js +21 -0
  127. package/dist/src/index-segments.d.ts +9 -0
  128. package/dist/src/index-segments.js +516 -0
  129. package/dist/src/index-store.d.ts +123 -0
  130. package/dist/src/index-store.js +495 -0
  131. package/dist/src/index-sync.d.ts +91 -15
  132. package/dist/src/index-sync.js +286 -53
  133. package/dist/src/index-upgrade-publication.d.ts +30 -0
  134. package/dist/src/index-upgrade-publication.js +179 -0
  135. package/dist/src/integration-manager.d.ts +32 -0
  136. package/dist/src/integration-manager.js +394 -0
  137. package/dist/src/leakscan.js +1 -1
  138. package/dist/src/local-api.d.ts +56 -15
  139. package/dist/src/local-api.js +4979 -541
  140. package/dist/src/machine-settings.d.ts +51 -0
  141. package/dist/src/machine-settings.js +166 -0
  142. package/dist/src/managed-chunks.d.ts +5 -2
  143. package/dist/src/managed-chunks.js +14 -14
  144. package/dist/src/mcp-install.d.ts +11 -9
  145. package/dist/src/mcp-install.js +73 -28
  146. package/dist/src/mcp-workspace.d.ts +18 -0
  147. package/dist/src/mcp-workspace.js +50 -0
  148. package/dist/src/mcp.js +294 -25
  149. package/dist/src/migrate.js +27 -21
  150. package/dist/src/notify.d.ts +1 -1
  151. package/dist/src/notify.js +5 -5
  152. package/dist/src/offload.d.ts +201 -14
  153. package/dist/src/offload.js +1848 -140
  154. package/dist/src/onboarding.d.ts +8 -1
  155. package/dist/src/onboarding.js +4 -4
  156. package/dist/src/packages.d.ts +2 -2
  157. package/dist/src/packages.js +10 -2
  158. package/dist/src/passkey.d.ts +0 -1
  159. package/dist/src/passkey.js +2 -7
  160. package/dist/src/password-lock.d.ts +2 -2
  161. package/dist/src/password-lock.js +6 -6
  162. package/dist/src/paths.d.ts +2 -0
  163. package/dist/src/paths.js +2 -0
  164. package/dist/src/presence.d.ts +86 -0
  165. package/dist/src/presence.js +240 -0
  166. package/dist/src/progress-deadline.d.ts +21 -0
  167. package/dist/src/progress-deadline.js +91 -0
  168. package/dist/src/project-repair.d.ts +55 -0
  169. package/dist/src/project-repair.js +131 -0
  170. package/dist/src/providers/gcs.d.ts +28 -7
  171. package/dist/src/providers/gcs.js +35 -24
  172. package/dist/src/providers/gdrive.d.ts +71 -8
  173. package/dist/src/providers/gdrive.js +223 -50
  174. package/dist/src/providers/index.d.ts +11 -3
  175. package/dist/src/providers/index.js +30 -10
  176. package/dist/src/providers/s3.d.ts +30 -8
  177. package/dist/src/providers/s3.js +41 -30
  178. package/dist/src/providers/safe-storage-fetch.d.ts +12 -0
  179. package/dist/src/providers/safe-storage-fetch.js +72 -0
  180. package/dist/src/queue.d.ts +136 -19
  181. package/dist/src/queue.js +862 -96
  182. package/dist/src/reclaim-transaction.d.ts +156 -0
  183. package/dist/src/reclaim-transaction.js +1027 -0
  184. package/dist/src/recovery-codes.d.ts +32 -0
  185. package/dist/src/recovery-codes.js +338 -0
  186. package/dist/src/recovery.js +12 -9
  187. package/dist/src/rehydrate.d.ts +25 -22
  188. package/dist/src/rehydrate.js +319 -23
  189. package/dist/src/restore.d.ts +57 -4
  190. package/dist/src/restore.js +272 -36
  191. package/dist/src/resume-stub.d.ts +92 -0
  192. package/dist/src/resume-stub.js +417 -0
  193. package/dist/src/retention.d.ts +98 -7
  194. package/dist/src/retention.js +1066 -62
  195. package/dist/src/rotate.js +3 -3
  196. package/dist/src/search.d.ts +566 -8
  197. package/dist/src/search.js +5897 -290
  198. package/dist/src/secrets.d.ts +51 -7
  199. package/dist/src/secrets.js +316 -24
  200. package/dist/src/service.d.ts +49 -11
  201. package/dist/src/service.js +776 -35
  202. package/dist/src/share.js +3 -3
  203. package/dist/src/shared-spaces.d.ts +98 -0
  204. package/dist/src/shared-spaces.js +214 -0
  205. package/dist/src/source-reader.d.ts +73 -0
  206. package/dist/src/source-reader.js +715 -0
  207. package/dist/src/spool.d.ts +1 -1
  208. package/dist/src/spool.js +1 -1
  209. package/dist/src/start-tui.js +2 -1
  210. package/dist/src/start.js +2 -2
  211. package/dist/src/storage-endpoint.d.ts +21 -0
  212. package/dist/src/storage-endpoint.js +122 -0
  213. package/dist/src/storage-setup.js +12 -12
  214. package/dist/src/storage-targets.d.ts +109 -6
  215. package/dist/src/storage-targets.js +975 -67
  216. package/dist/src/stream-to-cloud.d.ts +5 -1
  217. package/dist/src/stream-to-cloud.js +34 -14
  218. package/dist/src/sync-rules.d.ts +31 -6
  219. package/dist/src/sync-rules.js +153 -14
  220. package/dist/src/team-backfill-scheduling.d.ts +8 -0
  221. package/dist/src/team-backfill-scheduling.js +33 -0
  222. package/dist/src/team-backfill.d.ts +116 -0
  223. package/dist/src/team-backfill.js +1429 -0
  224. package/dist/src/team-index-cache.d.ts +16 -0
  225. package/dist/src/team-index-cache.js +152 -0
  226. package/dist/src/team-offboarding.d.ts +38 -0
  227. package/dist/src/team-offboarding.js +1043 -0
  228. package/dist/src/team-presence.d.ts +127 -0
  229. package/dist/src/team-presence.js +904 -0
  230. package/dist/src/team-publication-policy.d.ts +20 -0
  231. package/dist/src/team-publication-policy.js +140 -0
  232. package/dist/src/team-realtime.d.ts +68 -0
  233. package/dist/src/team-realtime.js +816 -0
  234. package/dist/src/team-source-facts-cache.d.ts +23 -0
  235. package/dist/src/team-source-facts-cache.js +255 -0
  236. package/dist/src/trash.d.ts +1 -1
  237. package/dist/src/trash.js +2 -2
  238. package/dist/src/tui.js +11 -12
  239. package/dist/src/types.d.ts +173 -7
  240. package/dist/src/types.js +20 -0
  241. package/dist/src/ui-server.d.ts +163 -35
  242. package/dist/src/ui-server.js +712 -72
  243. package/dist/src/ui.d.ts +1 -2
  244. package/dist/src/ui.js +1 -2
  245. package/dist/src/upload.d.ts +27 -0
  246. package/dist/src/upload.js +383 -43
  247. package/dist/src/vault.d.ts +226 -30
  248. package/dist/src/vault.js +1776 -192
  249. package/dist/src/watcher.d.ts +7 -1
  250. package/dist/src/watcher.js +198 -55
  251. package/dist/src/worker.d.ts +27 -3
  252. package/dist/src/worker.js +274 -55
  253. package/package.json +33 -12
  254. package/scripts/native-reboot-rehearsal.mjs +90 -0
  255. package/web/app.js +6032 -343
  256. package/web/bootstrap.js +17 -0
  257. package/web/index.html +255 -57
  258. package/web/rail.js +317 -40
  259. package/web/retention.html +2 -2
  260. package/web/rules-view.js +188 -16
  261. package/web/sessions-view.js +485 -62
  262. package/web/sessions.html +2 -2
  263. package/web/setup-api.js +152 -29
  264. package/web/setup-logic.js +68 -9
  265. package/web/setup.html +113 -44
  266. package/web/setup.js +604 -71
  267. package/web/style.css +513 -98
  268. package/dist/packages/vaultline-crypto/src/chunk-access.js +0 -93
  269. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/aead.d.ts +0 -0
  270. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/aead.js +0 -0
  271. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/format.js +0 -0
  272. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/recipients.d.ts +0 -0
  273. /package/dist/packages/{vaultline-crypto → sealkeep-crypto}/src/sha256-stream.d.ts +0 -0
  274. /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
+ }>;