sealkeep 0.6.0 → 0.6.1

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/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  Notable changes, by published version. Sealkeep is pre-1.0: minor versions
4
4
  may change behavior, and say so here when they do.
5
5
 
6
+ ## 0.6.1 — 2026-08-19
7
+
8
+ - Reading an offloaded archive now checks for free space first. An index build
9
+ reads each archive in full, and on a machine already short of space that
10
+ filled the disk and died mid-build — the failure this product exists to
11
+ prevent. It refuses in words that name the archive and the shortfall, keeps a
12
+ 512 MB reserve, and lets the build carry on with the rest.
13
+
6
14
  ## 0.6.0 — 2026-08-19
7
15
 
8
16
  **A second machine no longer wipes your search index.** The content index is
@@ -3,7 +3,7 @@
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 VaultlineErrorCode = "invalid_argument" | "vault_not_initialized" | "vault_already_initialized" | "config_unsupported_version" | "recovery_phrase_missing" | "recovery_phrase_mismatch" | "archive_not_found" | "source_unreadable" | "destination_exists" | "destination_unwritable" | "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" | "storage_not_configured" | "provider_unsupported" | "signer_not_configured" | "lease_expired" | "unauthorized" | "forbidden" | "internal";
6
+ export type VaultlineErrorCode = "invalid_argument" | "vault_not_initialized" | "vault_already_initialized" | "config_unsupported_version" | "recovery_phrase_missing" | "recovery_phrase_mismatch" | "archive_not_found" | "source_unreadable" | "destination_exists" | "destination_unwritable" | "insufficient_disk_space" | "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" | "storage_not_configured" | "provider_unsupported" | "signer_not_configured" | "lease_expired" | "unauthorized" | "forbidden" | "internal";
7
7
  export declare class VaultlineError extends Error {
8
8
  readonly code: VaultlineErrorCode;
9
9
  readonly details: Record<string, unknown>;
@@ -320,6 +320,19 @@ export async function checkRemoteCopyMany(dataDir, ids, client) {
320
320
  export async function materialiseArchive(dataDir, record, client) {
321
321
  if (!isV2(record) || !record.offloaded)
322
322
  return { path: record.objectPath, release: async () => { } };
323
+ // Reading an offloaded archive means writing it back to disk in full, and
324
+ // this runs in a loop during an index build. On a machine that offloaded
325
+ // BECAUSE it was short of space, that filled the disk and died on ENOSPC
326
+ // mid-build — the exact failure this product exists to prevent. Ask first,
327
+ // and refuse in words that name the archive and the shortfall.
328
+ const { freeBytes } = await import("./disk.js");
329
+ const free = await freeBytes(tmpdir());
330
+ const needed = record.cipher.storedBytes ?? record.remote?.bytes ?? 0;
331
+ const RESERVE = 512 * 1024 * 1024;
332
+ if (free !== undefined && needed > 0 && free < needed + RESERVE) {
333
+ const gb = (n) => `${(n / 1024 ** 3).toFixed(1)} GB`;
334
+ fail("insufficient_disk_space", `Reading archive ${record.id.slice(0, 8)} needs about ${gb(needed)} of temporary space and this machine has ${gb(free)} free. Nothing was written. Free some space, or reclaim what is already archived, and try again.`, { archiveId: record.id, free, needed });
335
+ }
323
336
  const ciphertext = await fetchCiphertext(dataDir, record, client);
324
337
  if (sha256(ciphertext) !== record.cipher.ciphertextSha256) {
325
338
  fail("ciphertext_integrity_failed", `The copy of archive ${record.id} in ${record.offloaded.bucket} is not the archive that was stored there`, { archiveId: record.id });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sealkeep",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "type": "module",
5
5
  "description": "Sealkeep by SPALA AI \u2014 your AI coding-agent history, sealed, searchable, and shared across your machines.",
6
6
  "license": "SEE LICENSE IN LICENSE",