sealkeep 0.11.3 → 0.11.4
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 +29 -0
- package/dist/site.zip +0 -0
- package/dist/src/daemon.js +13 -3
- package/dist/src/search.js +12 -3
- package/dist/src/vault.d.ts +14 -0
- package/dist/src/vault.js +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,35 @@
|
|
|
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.11.4 — 2026-09-23 — history stays findable
|
|
7
|
+
|
|
8
|
+
- **An archive can no longer be dropped from search because one sidecar could
|
|
9
|
+
not be read.** The build treats an id missing from the archive ledger as
|
|
10
|
+
deleted and retires it, which in segments mode is permanent: the id vanishes
|
|
11
|
+
from every later lookup and no build indexes it again. But the ledger scan
|
|
12
|
+
skips any sidecar it cannot parse — a partial write being replaced, one
|
|
13
|
+
transient read error — and that scan is cached per directory, so the damage
|
|
14
|
+
only surfaces when something else changes the directory: a seal arriving
|
|
15
|
+
while a build runs, which is what a busy machine does all day. On one real
|
|
16
|
+
vault ten archives had been retired with nothing superseding them, and two
|
|
17
|
+
sessions were genuinely unfindable — words in one returned no hits from it
|
|
18
|
+
while matching hundreds of other sessions. Absence now counts as deletion
|
|
19
|
+
only when the scan read every sidecar.
|
|
20
|
+
|
|
21
|
+
A vault that already lost archives this way recovers with `sealkeep index
|
|
22
|
+
drop` followed by `sealkeep index build`, which clears every retirement and
|
|
23
|
+
indexes the archives again. Repairing it automatically was tried and dropped:
|
|
24
|
+
a retirement also travels between your machines, so a build that lifted one
|
|
25
|
+
could undo a removal another machine meant, and the suite caught that.
|
|
26
|
+
|
|
27
|
+
- **The team check no longer runs on every seal.** 0.11.3 gave the lane its own
|
|
28
|
+
backing-off cadence, but a tick still asked it for a pass, and a tick is
|
|
29
|
+
scheduled whenever the queue has work — so cloud traffic still followed local
|
|
30
|
+
sealing. Measured on a busy machine: passes 4-6 seconds apart while a backlog
|
|
31
|
+
drained, against the 6-to-120-second schedule the lane had chosen. Nothing is
|
|
32
|
+
lost by waiting for that schedule; a pass this machine's own sealing triggers
|
|
33
|
+
cannot learn anything sooner than one the clock triggers.
|
|
34
|
+
|
|
6
35
|
## 0.11.3 — 2026-09-23 — the context tax, and a vault that catches up
|
|
7
36
|
|
|
8
37
|
- **The team check stops asking the cloud every three seconds.** The daemon
|
package/dist/site.zip
CHANGED
|
Binary file
|
package/dist/src/daemon.js
CHANGED
|
@@ -780,9 +780,19 @@ export async function startDaemon(dataDir, options) {
|
|
|
780
780
|
notes.push(`rescanned transcript roots (${found} seen)`);
|
|
781
781
|
}
|
|
782
782
|
// Human access decisions live in SealKeep Cloud; private-key work stays on
|
|
783
|
-
// this machine
|
|
784
|
-
// new invitations and accepted teammates/fresh
|
|
785
|
-
|
|
783
|
+
// this machine, and the access lane above closes both loops on its own
|
|
784
|
+
// cadence: owners wrap new invitations and accepted teammates/fresh
|
|
785
|
+
// machines bind the project.
|
|
786
|
+
//
|
|
787
|
+
// A tick deliberately does NOT ask for a pass. A tick is scheduled
|
|
788
|
+
// whenever the queue has claimable work, so asking here tied cloud
|
|
789
|
+
// traffic to local sealing: on a machine sealing a session every couple
|
|
790
|
+
// of minutes the lane ran that often no matter how quiet the team was.
|
|
791
|
+
// Measured on a busy host, passes came 4-6 seconds apart while a backlog
|
|
792
|
+
// drained, against the 6-to-120-second schedule the lane had chosen.
|
|
793
|
+
// Nothing is lost by waiting for that schedule — a pass triggered by this
|
|
794
|
+
// machine's own sealing cannot learn anything sooner than one triggered
|
|
795
|
+
// by the clock.
|
|
786
796
|
// This used to read `options.diskPressureFreeBytes` with no default, and
|
|
787
797
|
// nothing anywhere set it — so the guard was dead code and the installed
|
|
788
798
|
// service ran with no floor at all, on machines chosen for being short of
|
package/dist/src/search.js
CHANGED
|
@@ -8,7 +8,7 @@ import { createGunzip, gunzipSync } from "node:zlib";
|
|
|
8
8
|
import { decryptArchive as openEnvelope, decryptArchiveBodyChunks, encryptArchive as sealEnvelope, openArchiveToFile, sealArchiveToFile, unsqueezeStream, unsqueezeSync, validateEnvelope, rawPublicKey, x25519PrivateKeyFromRaw, x25519PublicKeyFromRaw } from "../packages/sealkeep-crypto/src/index.js";
|
|
9
9
|
import { fail, isSealkeepError } from "./errors.js";
|
|
10
10
|
import { canonicalPhrase } from "./mnemonic.js";
|
|
11
|
-
import { configuredRecipients, decryptRecord, deltaOf, listArchives, readConfig, resolveDeltaChain, restoreRecordToFile, teamSpaceOf } from "./vault.js";
|
|
11
|
+
import { configuredRecipients, decryptRecord, deltaOf, listArchives, listArchivesWithIntegrity, readConfig, resolveDeltaChain, restoreRecordToFile, teamSpaceOf } from "./vault.js";
|
|
12
12
|
import { chunkWindowForRange, openChunkWindow, supportsChunkAccess } from "../packages/sealkeep-crypto/src/chunk-access.js";
|
|
13
13
|
import { isV2, teamCustodyRecordIsValid } from "./types.js";
|
|
14
14
|
import { archiveWrapsEveryMember } from "./upload.js";
|
|
@@ -849,7 +849,9 @@ export async function buildContentIndex(dataDir, rawPhrase, options = {}) {
|
|
|
849
849
|
await readConfig(dataDir);
|
|
850
850
|
await cleanupStaleContentIndexTemps(dataDir);
|
|
851
851
|
await adoptLegacyIndexFile(dataDir);
|
|
852
|
-
|
|
852
|
+
// Absence is only evidence of deletion when every sidecar could be read.
|
|
853
|
+
const { records, unreadable } = await listArchivesWithIntegrity(dataDir);
|
|
854
|
+
const absenceMeansGone = unreadable.length === 0;
|
|
853
855
|
// Segments mode is decided once, up front: a vault either has a manifest
|
|
854
856
|
// with at least one segment (sealkeep index migrate created it) or it
|
|
855
857
|
// stays on the legacy blob path below, unchanged.
|
|
@@ -970,12 +972,19 @@ export async function buildContentIndex(dataDir, rawPhrase, options = {}) {
|
|
|
970
972
|
// file's index entry, the newer snapshot named by its `supersedes` field
|
|
971
973
|
// is — and every superseded id is named here even one this machine never
|
|
972
974
|
// got around to covering, so the manifest need not remember it later.
|
|
973
|
-
|
|
975
|
+
// Retiring an id from a segment is permanent: it disappears from every
|
|
976
|
+
// later lookup and compaction, and no build ever indexes it again. So it
|
|
977
|
+
// may only follow proof. A superseded snapshot is proof. Absence is not,
|
|
978
|
+
// unless the scan read every sidecar — see listArchivesWithIntegrity.
|
|
979
|
+
const stale = [...new Set([...mine, ...superseded])]
|
|
980
|
+
.filter((id) => superseded.has(id) || (absenceMeansGone && !liveIds.has(id)));
|
|
974
981
|
if (stale.length)
|
|
975
982
|
await retireArchiveIds(dataDir, stale);
|
|
976
983
|
}
|
|
977
984
|
else {
|
|
978
985
|
for (const id of Object.keys(index.archives)) {
|
|
986
|
+
if (!absenceMeansGone)
|
|
987
|
+
break; // a short scan is not a deletion
|
|
979
988
|
if (liveIds.has(id))
|
|
980
989
|
continue;
|
|
981
990
|
if (!mine.has(id))
|
package/dist/src/vault.d.ts
CHANGED
|
@@ -181,6 +181,20 @@ export declare function teamRecipientsForProject(dataDir: string, project: strin
|
|
|
181
181
|
* problem into a missing one. The archives that do parse still answer.
|
|
182
182
|
*/
|
|
183
183
|
export declare function listArchives(dataDir: string): Promise<ArchiveRecord[]>;
|
|
184
|
+
/**
|
|
185
|
+
* The same list, plus whether the scan could read every sidecar.
|
|
186
|
+
*
|
|
187
|
+
* A caller that concludes "this archive no longer exists" from absence needs
|
|
188
|
+
* this: one sidecar that could not be read — a partial write being replaced, a
|
|
189
|
+
* transient EIO — silently shortens the list. The index build drew exactly that
|
|
190
|
+
* conclusion and retired the archive from search permanently. Two of a real
|
|
191
|
+
* vault's sessions became unfindable that way; words in one returned no hits
|
|
192
|
+
* from it while matching hundreds of other sessions.
|
|
193
|
+
*/
|
|
194
|
+
export declare function listArchivesWithIntegrity(dataDir: string): Promise<{
|
|
195
|
+
records: ArchiveRecord[];
|
|
196
|
+
unreadable: string[];
|
|
197
|
+
}>;
|
|
184
198
|
/**
|
|
185
199
|
* Registers an X25519 public key that may open future archives. Only the public
|
|
186
200
|
* key is stored; the matching private key stays on its own device.
|
package/dist/src/vault.js
CHANGED
|
@@ -1373,6 +1373,22 @@ export async function listArchives(dataDir) {
|
|
|
1373
1373
|
// dashboard requests shared the underlying filesystem scan.
|
|
1374
1374
|
return scan.records.slice();
|
|
1375
1375
|
}
|
|
1376
|
+
/**
|
|
1377
|
+
* The same list, plus whether the scan could read every sidecar.
|
|
1378
|
+
*
|
|
1379
|
+
* A caller that concludes "this archive no longer exists" from absence needs
|
|
1380
|
+
* this: one sidecar that could not be read — a partial write being replaced, a
|
|
1381
|
+
* transient EIO — silently shortens the list. The index build drew exactly that
|
|
1382
|
+
* conclusion and retired the archive from search permanently. Two of a real
|
|
1383
|
+
* vault's sessions became unfindable that way; words in one returned no hits
|
|
1384
|
+
* from it while matching hundreds of other sessions.
|
|
1385
|
+
*/
|
|
1386
|
+
export async function listArchivesWithIntegrity(dataDir) {
|
|
1387
|
+
const config = await readConfig(dataDir);
|
|
1388
|
+
const scan = await currentArchiveLedgerScan(config.storage.root);
|
|
1389
|
+
reportUnreadableArchiveRecords(config.storage.root, scan.unreadable);
|
|
1390
|
+
return { records: scan.records.slice(), unreadable: scan.unreadable.slice() };
|
|
1391
|
+
}
|
|
1376
1392
|
// The dashboard loads several archive-derived endpoints together. A per-call
|
|
1377
1393
|
// batch bound alone still multiplied 32 opens by every endpoint. Share both an
|
|
1378
1394
|
// in-flight scan and a completed, generation-validated scan. Archive record
|