rag-memory-epf-mcp 6.4.2 → 6.4.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/README.md +42 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.js +238 -40
- package/dist/src/backfillCoordinator.d.ts +12 -0
- package/dist/src/backfillCoordinator.js +163 -42
- package/dist/src/observations/projection.js +4 -3
- package/dist/src/vecSimilarity.d.ts +17 -0
- package/dist/src/vecSimilarity.js +23 -0
- package/dist/src/vecSlots.d.ts +51 -0
- package/dist/src/vecSlots.js +323 -0
- package/docs/UPDATING.md +18 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -154,6 +154,48 @@ storeDocument(id, content, metadata)
|
|
|
154
154
|
|
|
155
155
|
## Changelog
|
|
156
156
|
|
|
157
|
+
### v6.4.4 (2026-11-07)
|
|
158
|
+
|
|
159
|
+
- **Fixed — the similarity numbers are real cosine similarities.** The vec0 tables use the default L2 metric and the
|
|
160
|
+
embeddings are unit-normalized, so cosine similarity = 1 − d²/2. The engine used 1 − d/2 (L2 read as a cosine
|
|
161
|
+
distance): `searchNodes` `similarity` and the default `hybridSearch` `vector_similarity` were too low above cos 0.5
|
|
162
|
+
(cos 0.8 showed ≈0.684) and too high below it (orthogonal vectors showed ≈0.293 instead of 0). Ranking of the default
|
|
163
|
+
search is unchanged (rank fusion uses ranks only).
|
|
164
|
+
- **Unchanged on purpose — the legacy paths keep the 6.3.2 score.** `hybridSearch` with `useGraph: true` or with
|
|
165
|
+
summaries on, and the graph-seed threshold (`> 0.4`, about cosine 0.28), stay byte-identical to 6.3.2 for evaluation
|
|
166
|
+
comparability; that score is named `legacyL2AsCosineScore` and is not a cosine.
|
|
167
|
+
- **Fixed — chunk vectors without metadata are cleaned up.** Reconciliation now deletes searchable chunk vectors that
|
|
168
|
+
have no `chunk_metadata` row (same rule and batching as entity orphans; off mode only classifies it as deferred).
|
|
169
|
+
`importGraph` no longer creates them: a full replace (`merge: false`) and a same-id document replace delete that
|
|
170
|
+
document's chunk vectors first. Rowids past 2^53 are kept exact.
|
|
171
|
+
- **Added — `getKnowledgeGraphStats().server.coverage.{chunk,entity}.orphan_vectors`**: searchable vectors with no
|
|
172
|
+
metadata row (`null` when the slot map cannot be resolved). `missing` keeps its meaning.
|
|
173
|
+
- Not measured: Windows, Drive-synced DBs under two machines.
|
|
174
|
+
|
|
175
|
+
### v6.4.3 (2026-11-02)
|
|
176
|
+
|
|
177
|
+
- **Fixed — re-embedding no longer leaks vector slots.** sqlite-vec puts a new row only into a free slot of the latest
|
|
178
|
+
1,024-slot chunk, so the old DELETE + INSERT of the same rowid left a hole whenever the row sat in an older chunk (one
|
|
179
|
+
project DB reached 84 chunks for 5,578 vectors, 93.5% empty, 337 of 439 MB). Same-rowid re-embeds (document chunks,
|
|
180
|
+
KG chunks, bulk `embedChunks`, entities) now rewrite the vector in place; entity re-embeds keep their rowid.
|
|
181
|
+
- **Added — idle verified rebuild of the vector tables.** When the engine is idle (no tool call, backfill or
|
|
182
|
+
reconciliation; 60 s quiet; checked at most every 10 min) and a table has at least 8 reclaimable chunks that outweigh
|
|
183
|
+
its live vectors, or holds damaged slots, it is rebuilt in one transaction keeping every searchable rowid and its
|
|
184
|
+
exact vector bytes (verified before commit; rolled back otherwise). Skipped at once if another connection holds the
|
|
185
|
+
write lock. Automatic only up to 20,000 live vectors per table (measured on one Mac: 5,600 = 0.44 s, 20,000 =
|
|
186
|
+
1.24 s and +267 MB transient). No VACUUM: the file stops growing; it shrinks only through a VACUUM.
|
|
187
|
+
- **Fixed — coverage counts only searchable vectors.** A row whose slot validity bit is 0 (invisible to search) was
|
|
188
|
+
counted as embedded; it is now missing and is repaired: writes and deletes that meet such a slot, or a slot shared by
|
|
189
|
+
two rowids, rebuild the table first in the same transaction, then the backfill re-embeds at the original rowid.
|
|
190
|
+
An unresolvable slot position is reported as an error (`missing: null`), never as a count.
|
|
191
|
+
- **Added — `getKnowledgeGraphStats().server.vector_slots`**: per table slots, valid, validity-0, shared, empty,
|
|
192
|
+
reclaimable chunks, whether a rebuild is due and why, and this process's last attempt.
|
|
193
|
+
- **Added — write-seq signal** (`[rag-memory] write-seq N` on stderr when the engine's own connection changed rows),
|
|
194
|
+
used by the framework launcher to record a DB handoff only after the engine wrote.
|
|
195
|
+
- **Changed — an entity metadata row whose vector row is gone is kept unstamped (missing) instead of deleted**, so the
|
|
196
|
+
backfill re-inserts at the same rowid; new entity rowids are allocated past both tables.
|
|
197
|
+
- Not measured: Windows, Drive-synced DBs under two machines, tables above 20,000 vectors.
|
|
198
|
+
|
|
157
199
|
### v6.4.2 (2026-10-31)
|
|
158
200
|
|
|
159
201
|
- **Fixed — the engine no longer keeps the peak memory of its largest embedding.** The ONNX session is opened with
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { EVIDENCE_FORMAT, type EvidenceItemInput, type EvidenceStatus, type Evid
|
|
|
4
4
|
import { EmbeddingGate } from './src/embeddingGate.js';
|
|
5
5
|
import type { EmbedPriority } from './src/embeddingGate.js';
|
|
6
6
|
import { BackfillCoordinator } from './src/backfillCoordinator.js';
|
|
7
|
+
import type { RebuildResult, VecTable } from './src/vecSlots.js';
|
|
7
8
|
export declare const LEGACY_QUERY_INSTRUCTION = "Represent this sentence for searching relevant passages: ";
|
|
8
9
|
export declare function embeddingInput(text: string, isQuery: boolean, defaultModel?: boolean): string;
|
|
9
10
|
export declare function makeEmbedFn(model: (input: string, opts: any) => Promise<{
|
|
@@ -132,6 +133,20 @@ export declare class RAGKnowledgeGraphManager {
|
|
|
132
133
|
__testForceFkOff?: boolean;
|
|
133
134
|
}): Promise<void>;
|
|
134
135
|
private openConnection;
|
|
136
|
+
writeSeq: number;
|
|
137
|
+
private tcBase;
|
|
138
|
+
private lastActivityAt;
|
|
139
|
+
private lastWriteAt;
|
|
140
|
+
private lastSlotCheckAt;
|
|
141
|
+
readonly vectorSlotLast: Partial<Record<VecTable, RebuildResult & {
|
|
142
|
+
at: string;
|
|
143
|
+
}>>;
|
|
144
|
+
private static slotCheckMs;
|
|
145
|
+
private static slotQuietMs;
|
|
146
|
+
maybeRebuildVectors(now?: number): RebuildResult[] | null;
|
|
147
|
+
vectorSlotStats(): Record<string, unknown>;
|
|
148
|
+
private totalChanges;
|
|
149
|
+
noteWrites(): void;
|
|
135
150
|
private ensureCurrentProfile;
|
|
136
151
|
private buildRealLoader;
|
|
137
152
|
startReconciliation(): Promise<void>;
|
|
@@ -474,7 +489,7 @@ export declare class RAGKnowledgeGraphManager {
|
|
|
474
489
|
}>;
|
|
475
490
|
/**
|
|
476
491
|
* Diagnostic seam for the graph re-ranker (evaluation change graph-role-evaluation, R2).
|
|
477
|
-
* Returns the seed entities (query-vector matched,
|
|
492
|
+
* Returns the seed entities (query-vector matched, legacy score > 0.4 = cosine ≈0.28, top-10 per variant) and the
|
|
478
493
|
* 1-hop connected entities exactly as hybridSearch(useGraph:true) computes them, plus edge detail
|
|
479
494
|
* that hybridSearch itself does not use (edge id, type, direction, confidence). It never generates
|
|
480
495
|
* candidates and never changes ranking; hybridSearch consumes only the name sets.
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,8 @@ import { migrations } from './src/migrations/migrations.js';
|
|
|
34
34
|
import { EmbeddingGate, GateNotReadyError, GateDisabledError, TerminalConfigError } from './src/embeddingGate.js';
|
|
35
35
|
import { resolveModelCacheDir, preflightCacheDir, artifactKey, ModelDownloadLock, handleLoaderFailure } from './src/modelCache.js';
|
|
36
36
|
import { BackfillCoordinator } from './src/backfillCoordinator.js';
|
|
37
|
+
import { l2ToCosineSimilarity, legacyL2AsCosineScore } from './src/vecSimilarity.js';
|
|
38
|
+
import { writeVector, deleteVector, registerVecSlotFunctions, InvalidSlotError, slotSummary, rebuildVecTable, rebuildReason, DEFAULT_REBUILD_POLICY } from './src/vecSlots.js';
|
|
37
39
|
import { calendarDate, resolveCalendarTimeZone, stampDatePrefix, stripDatePrefix } from './src/observations/date-prefix.js';
|
|
38
40
|
import os from 'node:os';
|
|
39
41
|
import { createHash } from 'crypto';
|
|
@@ -326,6 +328,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
326
328
|
openConnection(forceFkOff = false) {
|
|
327
329
|
this.db = new Database(DB_FILE_PATH);
|
|
328
330
|
sqliteVec.load(this.db);
|
|
331
|
+
registerVecSlotFunctions(this.db); // vec_slot_valid() for the D1c searchability predicate
|
|
329
332
|
this.db.pragma('journal_mode = WAL');
|
|
330
333
|
this.db.pragma('synchronous = NORMAL');
|
|
331
334
|
this.db.pragma('busy_timeout = 5000');
|
|
@@ -356,6 +359,119 @@ export class RAGKnowledgeGraphManager {
|
|
|
356
359
|
`for history integrity; refusing to run migrations without it.`);
|
|
357
360
|
}
|
|
358
361
|
}
|
|
362
|
+
// Write-sequence baseline for THIS connection (see noteWrites). A fresh connection starts
|
|
363
|
+
// its own total_changes() count, so the baseline is reset on every open; the announced
|
|
364
|
+
// sequence itself never goes back.
|
|
365
|
+
this.tcBase = this.totalChanges();
|
|
366
|
+
}
|
|
367
|
+
// --- Write-sequence signal (test/write-seq.test.mjs) ------------------------------------
|
|
368
|
+
// One stderr line per observation that THIS connection changed rows:
|
|
369
|
+
// [rag-memory] write-seq <N> (N = 1, 2, 3, … for the life of the process)
|
|
370
|
+
// The framework launcher (mcp-proxy.cjs) records "the DB state this machine left" only
|
|
371
|
+
// after such a line. Before this signal it recorded whenever the file changed and went
|
|
372
|
+
// quiet — which also fired when a cloud sync client swapped the other machine's file in
|
|
373
|
+
// under an idle engine, and that record then refused a correct DB (measured 2026-11-02).
|
|
374
|
+
// total_changes() counts rows changed by this connection (vec0 shadow tables included);
|
|
375
|
+
// reads and other connections do not move it. It is read outside a transaction only, so a
|
|
376
|
+
// statement still inside one is announced after it ends. It also counts rows of a
|
|
377
|
+
// transaction that was rolled back: a line means "this connection's change count went up",
|
|
378
|
+
// not "a commit happened". The launcher consumes a pending sequence once the file it last
|
|
379
|
+
// recorded is still there, the WAL is empty and the file is stable; until then a sequence that
|
|
380
|
+
// came from a rollback can still publish a file another writer or a sync client changed, and
|
|
381
|
+
// that period has no fixed upper bound (hub session 251 review r1 M1 · r3. r2: a WAL-size
|
|
382
|
+
// test was tried and removed — a header-only WAL looks non-empty and another connection's
|
|
383
|
+
// checkpoint hides a real commit).
|
|
384
|
+
// Written with fs.writeSync: the last call runs inside process 'exit', where an async
|
|
385
|
+
// stderr pipe write (macOS) can be lost.
|
|
386
|
+
writeSeq = 0;
|
|
387
|
+
tcBase = 0;
|
|
388
|
+
// ---- vec-slot-reuse D1b: idle verified rebuild of the vec0 tables -------------------------------
|
|
389
|
+
// Initial operating values (2_ s253 r2 Q3: starting values, not optima): look at most every 10 min,
|
|
390
|
+
// only after 60 s without a tool call or an own-connection write, never while a tool call, a backfill
|
|
391
|
+
// scan or a reconciliation is running. A repair request (validity-0 rows seen) skips the 10-min wait.
|
|
392
|
+
// The saving threshold and the live-vector cap live in DEFAULT_REBUILD_POLICY (src/vecSlots.ts).
|
|
393
|
+
lastActivityAt = Date.now();
|
|
394
|
+
lastWriteAt = 0;
|
|
395
|
+
lastSlotCheckAt = 0;
|
|
396
|
+
// Last attempt per table. Process memory only: unknown after a restart (stats says so).
|
|
397
|
+
vectorSlotLast = {};
|
|
398
|
+
static slotCheckMs = Number(process.env.RAG_MEMORY_SLOT_CHECK_MS ?? 600_000);
|
|
399
|
+
static slotQuietMs = Number(process.env.RAG_MEMORY_SLOT_QUIET_MS ?? 60_000);
|
|
400
|
+
maybeRebuildVectors(now = Date.now()) {
|
|
401
|
+
if (!this.db || this.shuttingDown || this.inFlight > 0 || this.db.inTransaction)
|
|
402
|
+
return null;
|
|
403
|
+
if (this.coordinator?.busy)
|
|
404
|
+
return null;
|
|
405
|
+
if (now - Math.max(this.lastActivityAt, this.lastWriteAt) < RAGKnowledgeGraphManager.slotQuietMs)
|
|
406
|
+
return null;
|
|
407
|
+
// A repair request skips the 10-min wait once: only if it arrived after the last check (r3 M5 — otherwise
|
|
408
|
+
// a busy / failed / over-cap attempt would be retried on every 1 s tick).
|
|
409
|
+
const repair = this.coordinator?.repairRequested ?? false;
|
|
410
|
+
const freshRepair = repair && (this.coordinator?.repairRequestedAt ?? 0) > this.lastSlotCheckAt;
|
|
411
|
+
if (!freshRepair && now - this.lastSlotCheckAt < RAGKnowledgeGraphManager.slotCheckMs)
|
|
412
|
+
return null;
|
|
413
|
+
this.lastSlotCheckAt = now;
|
|
414
|
+
const results = [];
|
|
415
|
+
for (const table of ['chunks', 'entity_embeddings']) {
|
|
416
|
+
const r = rebuildVecTable(this.db, table);
|
|
417
|
+
this.vectorSlotLast[table] = { ...r, at: new Date(now).toISOString() };
|
|
418
|
+
results.push(r);
|
|
419
|
+
if (r.status === 'rebuilt') {
|
|
420
|
+
console.error(`🧹 vec0 ${table} rebuilt (${r.reason}): chunks ${r.before?.chunks} -> ${r.after?.chunks}, ` +
|
|
421
|
+
`valid ${r.after?.valid}, dropped validity-0 ${r.before?.invalid}, shared-slot ${r.before?.aliased}, ${r.ms} ms`);
|
|
422
|
+
}
|
|
423
|
+
else if (r.status !== 'not-needed') {
|
|
424
|
+
console.error(`⚠️ vec0 ${table} rebuild ${r.status}${r.error ? `: ${r.error}` : ''}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (repair && results.every(r => r.status === 'rebuilt' || r.status === 'not-needed'))
|
|
428
|
+
this.coordinator?.takeRepairRequest();
|
|
429
|
+
if (results.some(r => r.status === 'rebuilt')) {
|
|
430
|
+
this.coordinator?.invalidateCoverage();
|
|
431
|
+
this.noteWrites();
|
|
432
|
+
this.coordinator?.kick(); // rows dropped as validity-0 are now missing -> re-embedded at their rowid
|
|
433
|
+
}
|
|
434
|
+
return results;
|
|
435
|
+
}
|
|
436
|
+
vectorSlotStats() {
|
|
437
|
+
const out = {};
|
|
438
|
+
for (const table of ['chunks', 'entity_embeddings']) {
|
|
439
|
+
let summary;
|
|
440
|
+
try {
|
|
441
|
+
summary = slotSummary(this.db, table);
|
|
442
|
+
}
|
|
443
|
+
catch (e) {
|
|
444
|
+
summary = { error: e.message };
|
|
445
|
+
}
|
|
446
|
+
const pending = 'error' in summary ? 'position-error'
|
|
447
|
+
: (summary.valid > DEFAULT_REBUILD_POLICY.maxLive ? 'over-cap' : (rebuildReason(summary) ?? null));
|
|
448
|
+
const last = this.vectorSlotLast[table];
|
|
449
|
+
out[table] = { ...summary, due: pending,
|
|
450
|
+
last_attempt: last ? { at: last.at, status: last.status, reason: last.reason ?? null, ms: last.ms, error: last.error ?? null }
|
|
451
|
+
: 'unknown (process memory; none since start)' };
|
|
452
|
+
}
|
|
453
|
+
out.policy = { ...DEFAULT_REBUILD_POLICY, check_ms: RAGKnowledgeGraphManager.slotCheckMs, quiet_ms: RAGKnowledgeGraphManager.slotQuietMs,
|
|
454
|
+
repair_requested: this.coordinator?.repairRequested ?? false, vacuum: 'never (maintenance script only)' };
|
|
455
|
+
return out;
|
|
456
|
+
}
|
|
457
|
+
totalChanges() {
|
|
458
|
+
if (!this.db)
|
|
459
|
+
return 0;
|
|
460
|
+
return Number(this.db.prepare('select total_changes()').pluck().get());
|
|
461
|
+
}
|
|
462
|
+
noteWrites() {
|
|
463
|
+
try {
|
|
464
|
+
if (!this.db || this.db.inTransaction)
|
|
465
|
+
return;
|
|
466
|
+
const tc = this.totalChanges();
|
|
467
|
+
if (tc <= this.tcBase)
|
|
468
|
+
return;
|
|
469
|
+
this.tcBase = tc;
|
|
470
|
+
this.lastWriteAt = Date.now();
|
|
471
|
+
this.writeSeq += 1;
|
|
472
|
+
fsSync.writeSync(2, `[rag-memory] write-seq ${this.writeSeq}\n`);
|
|
473
|
+
}
|
|
474
|
+
catch { /* a dead handle is the reopen path's business; the next tick tries again */ }
|
|
359
475
|
}
|
|
360
476
|
// Upsert the stored-vector compatibility profile (spec §6c layer 2) and record
|
|
361
477
|
// retrieval config in server_meta (layer 3 — never a backfill trigger).
|
|
@@ -552,7 +668,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
552
668
|
const meta = this.db.prepare(`SELECT rowid FROM entity_embedding_metadata WHERE entity_id = ?`)
|
|
553
669
|
.get(entityId);
|
|
554
670
|
if (meta) {
|
|
555
|
-
this.db
|
|
671
|
+
deleteVector(this.db, 'entity_embeddings', Number(meta.rowid));
|
|
556
672
|
this.db.prepare(`DELETE FROM entity_embedding_metadata WHERE entity_id = ?`).run(entityId);
|
|
557
673
|
}
|
|
558
674
|
deleteStaleKgChunks(this.db, entityId);
|
|
@@ -567,7 +683,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
567
683
|
if (!meta)
|
|
568
684
|
return;
|
|
569
685
|
const tx = this.db.transaction(() => {
|
|
570
|
-
this.db
|
|
686
|
+
deleteVector(this.db, 'entity_embeddings', Number(meta.rowid));
|
|
571
687
|
this.db.prepare(`DELETE FROM entity_embedding_metadata WHERE entity_id = ?`).run(entityId);
|
|
572
688
|
});
|
|
573
689
|
tx();
|
|
@@ -597,8 +713,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
597
713
|
return false; // inputs changed
|
|
598
714
|
if (this.currentProfileId !== captured.profile_id)
|
|
599
715
|
return false; // profile changed during inference
|
|
600
|
-
this.db.
|
|
601
|
-
this.db.prepare(`INSERT INTO chunks (rowid, embedding) VALUES (${rowid}, ?)`).run(Buffer.from(embedding.buffer));
|
|
716
|
+
writeVector(this.db, 'chunks', rowid, Buffer.from(embedding.buffer)); // same slot when valid (vec-slot-reuse)
|
|
602
717
|
this.db.prepare(`UPDATE chunk_metadata SET input_hash = ?, profile_id = ?, provenance_state = 'verified' WHERE rowid = ?`)
|
|
603
718
|
.run(captured.text_sha256, captured.profile_id, rowid);
|
|
604
719
|
const meta = parseEvidenceMetadata(cur.metadata);
|
|
@@ -610,7 +725,17 @@ export class RAGKnowledgeGraphManager {
|
|
|
610
725
|
this.db.prepare(`DELETE FROM embedding_backfill_failures WHERE kind = 'chunk' AND target_id = ?`).run(String(rowid));
|
|
611
726
|
return true;
|
|
612
727
|
});
|
|
613
|
-
|
|
728
|
+
let written;
|
|
729
|
+
try {
|
|
730
|
+
written = tx();
|
|
731
|
+
}
|
|
732
|
+
catch (e) {
|
|
733
|
+
if (e instanceof InvalidSlotError) {
|
|
734
|
+
this.coordinator?.requestRepair();
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
throw e;
|
|
738
|
+
}
|
|
614
739
|
this.coordinator?.invalidateCoverage();
|
|
615
740
|
return written;
|
|
616
741
|
}
|
|
@@ -648,14 +773,23 @@ export class RAGKnowledgeGraphManager {
|
|
|
648
773
|
const cur = this.db.prepare(`SELECT text FROM chunk_metadata WHERE rowid = ?`).get(rowid);
|
|
649
774
|
if (!cur || cur.text !== row.text)
|
|
650
775
|
return false; // superseded — discard
|
|
651
|
-
this.db
|
|
652
|
-
this.db.prepare(`INSERT INTO chunks (rowid, embedding) VALUES (${safe}, ?)`).run(Buffer.from(embedding.buffer));
|
|
776
|
+
writeVector(this.db, 'chunks', safe, Buffer.from(embedding.buffer));
|
|
653
777
|
this.db.prepare(`UPDATE chunk_metadata SET input_hash = ?, profile_id = ?, provenance_state = 'verified' WHERE rowid = ?`)
|
|
654
778
|
.run(hash, this.currentProfileId, rowid);
|
|
655
779
|
this.db.prepare(`DELETE FROM embedding_backfill_failures WHERE kind = 'chunk' AND target_id = ?`).run(String(rowid));
|
|
656
780
|
return true;
|
|
657
781
|
});
|
|
658
|
-
|
|
782
|
+
let written;
|
|
783
|
+
try {
|
|
784
|
+
written = tx();
|
|
785
|
+
}
|
|
786
|
+
catch (e) {
|
|
787
|
+
if (e instanceof InvalidSlotError) {
|
|
788
|
+
this.coordinator?.requestRepair();
|
|
789
|
+
return false;
|
|
790
|
+
}
|
|
791
|
+
throw e;
|
|
792
|
+
}
|
|
659
793
|
this.coordinator?.invalidateCoverage();
|
|
660
794
|
return written;
|
|
661
795
|
}
|
|
@@ -872,6 +1006,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
872
1006
|
console.error(`⚠️ reopen storm (${REOPEN_STORM_MAX} in ${REOPEN_STORM_WINDOW_MS / 1000}s) — watch paused, reason=${reason}`);
|
|
873
1007
|
return false;
|
|
874
1008
|
}
|
|
1009
|
+
this.noteWrites(); // rows changed on the old connection are announced before its count is gone
|
|
875
1010
|
const old = this.db;
|
|
876
1011
|
// Salvage: frames committed to the old WAL after the last checkpoint (≤1 s) go to the main
|
|
877
1012
|
// file if the old descriptor is still readable. An I/O error here just means they are gone.
|
|
@@ -977,14 +1112,22 @@ export class RAGKnowledgeGraphManager {
|
|
|
977
1112
|
catch (e) {
|
|
978
1113
|
console.error(`⚠️ wal tick: ${e.message}`);
|
|
979
1114
|
}
|
|
1115
|
+
try {
|
|
1116
|
+
this.maybeRebuildVectors();
|
|
1117
|
+
}
|
|
1118
|
+
catch (e) {
|
|
1119
|
+
console.error(`⚠️ vec0 rebuild check: ${e.message}`);
|
|
1120
|
+
}
|
|
980
1121
|
this.checkpointWal();
|
|
1122
|
+
this.noteWrites();
|
|
981
1123
|
}, tickMs);
|
|
982
1124
|
this.walTimer.unref();
|
|
983
1125
|
}
|
|
984
1126
|
noteActivity(delayMs = 200) {
|
|
1127
|
+
this.lastActivityAt = Date.now();
|
|
985
1128
|
if (this.walSoon)
|
|
986
1129
|
return;
|
|
987
|
-
this.walSoon = setTimeout(() => { this.walSoon = null; this.checkpointWal(); }, delayMs);
|
|
1130
|
+
this.walSoon = setTimeout(() => { this.walSoon = null; this.checkpointWal(); this.noteWrites(); }, delayMs);
|
|
988
1131
|
this.walSoon.unref();
|
|
989
1132
|
}
|
|
990
1133
|
cleanup() {
|
|
@@ -1006,6 +1149,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
1006
1149
|
// close() folds the WAL only for the last connection; with other engines attached it
|
|
1007
1150
|
// would leave every frame behind.
|
|
1008
1151
|
this.checkpointWal();
|
|
1152
|
+
this.noteWrites();
|
|
1009
1153
|
this.db.close();
|
|
1010
1154
|
this.db = null;
|
|
1011
1155
|
}
|
|
@@ -1319,13 +1463,11 @@ export class RAGKnowledgeGraphManager {
|
|
|
1319
1463
|
SELECT rowid FROM entity_embedding_metadata WHERE entity_id = ?
|
|
1320
1464
|
`).get(eid);
|
|
1321
1465
|
if (embeddingMetadata) {
|
|
1322
|
-
const
|
|
1323
|
-
DELETE FROM entity_embeddings WHERE rowid = ?
|
|
1324
|
-
`).run(embeddingMetadata.rowid);
|
|
1466
|
+
const vecState = deleteVector(this.db, 'entity_embeddings', Number(embeddingMetadata.rowid));
|
|
1325
1467
|
const metadata = this.db.prepare(`
|
|
1326
1468
|
DELETE FROM entity_embedding_metadata WHERE entity_id = ?
|
|
1327
1469
|
`).run(eid);
|
|
1328
|
-
if (
|
|
1470
|
+
if (vecState === 'valid' || metadata.changes > 0) {
|
|
1329
1471
|
console.error(` ├─ Removed entity embeddings for '${name}'`);
|
|
1330
1472
|
}
|
|
1331
1473
|
}
|
|
@@ -1446,7 +1588,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
1446
1588
|
const meta = this.db.prepare(`SELECT rowid FROM entity_embedding_metadata WHERE entity_id = ?`)
|
|
1447
1589
|
.get(p.entityId);
|
|
1448
1590
|
if (meta) {
|
|
1449
|
-
this.db
|
|
1591
|
+
deleteVector(this.db, 'entity_embeddings', Number(meta.rowid));
|
|
1450
1592
|
this.db.prepare(`DELETE FROM entity_embedding_metadata WHERE entity_id = ?`)
|
|
1451
1593
|
.run(p.entityId);
|
|
1452
1594
|
}
|
|
@@ -1753,7 +1895,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
1753
1895
|
name: result.name,
|
|
1754
1896
|
entityType: result.entityType,
|
|
1755
1897
|
observations: JSON.parse(result.observations),
|
|
1756
|
-
similarity:
|
|
1898
|
+
similarity: l2ToCosineSimilarity(result.distance) // vec0 L2 distance of unit vectors -> cosine similarity
|
|
1757
1899
|
}));
|
|
1758
1900
|
// hybrid-partial (spec §4): entities without vectors must not vanish from
|
|
1759
1901
|
// search while backfill catches up — merge lexical FTS hits for the gap.
|
|
@@ -2059,19 +2201,40 @@ export class RAGKnowledgeGraphManager {
|
|
|
2059
2201
|
SELECT rowid FROM entity_embedding_metadata WHERE entity_id = ?
|
|
2060
2202
|
`).get(entityId);
|
|
2061
2203
|
if (existingMetadata) {
|
|
2062
|
-
|
|
2063
|
-
|
|
2204
|
+
// vec-slot-reuse: keep the rowid and rewrite the vector in place (DELETE + INSERT with a new
|
|
2205
|
+
// rowid leaked one vec0 slot per re-embed). The metadata row is updated, not replaced.
|
|
2206
|
+
writeVector(this.db, 'entity_embeddings', Number(existingMetadata.rowid), Buffer.from(embedding.buffer));
|
|
2207
|
+
this.db.prepare(`
|
|
2208
|
+
UPDATE entity_embedding_metadata
|
|
2209
|
+
SET embedding_text = ?, input_hash = ?, profile_id = ?, provenance_state = 'verified'
|
|
2210
|
+
WHERE rowid = ?
|
|
2211
|
+
`).run(embeddingText, inputHash, this.currentProfileId, existingMetadata.rowid);
|
|
2212
|
+
return true;
|
|
2064
2213
|
}
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2214
|
+
// r4 M2: metadata rows can now outlive their vector (kept unstamped to keep the rowid), so the vec0
|
|
2215
|
+
// auto rowid could collide with one. Allocate past both tables, in this transaction.
|
|
2216
|
+
const nextRowid = BigInt(this.db.prepare(`SELECT MAX(r) m FROM (
|
|
2217
|
+
SELECT MAX(rowid) r FROM entity_embedding_metadata UNION ALL SELECT MAX(rowid) r FROM entity_embeddings_rowids)`)
|
|
2218
|
+
.safeIntegers(true).get().m ?? 0n) + 1n;
|
|
2219
|
+
writeVector(this.db, 'entity_embeddings', nextRowid, Buffer.from(embedding.buffer));
|
|
2220
|
+
const result = { lastInsertRowid: nextRowid };
|
|
2068
2221
|
this.db.prepare(`
|
|
2069
2222
|
INSERT INTO entity_embedding_metadata (rowid, entity_id, embedding_text, input_hash, profile_id, provenance_state)
|
|
2070
2223
|
VALUES (?, ?, ?, ?, ?, 'verified')
|
|
2071
2224
|
`).run(result.lastInsertRowid, entityId, embeddingText, inputHash, this.currentProfileId);
|
|
2072
2225
|
return true;
|
|
2073
2226
|
});
|
|
2074
|
-
|
|
2227
|
+
let written;
|
|
2228
|
+
try {
|
|
2229
|
+
written = writeTx();
|
|
2230
|
+
}
|
|
2231
|
+
catch (e) {
|
|
2232
|
+
if (e instanceof InvalidSlotError) {
|
|
2233
|
+
this.coordinator?.requestRepair();
|
|
2234
|
+
return false;
|
|
2235
|
+
}
|
|
2236
|
+
throw e;
|
|
2237
|
+
}
|
|
2075
2238
|
if (!written) {
|
|
2076
2239
|
console.error(`⏭️ discarded superseded embedding for ${entityId} (entity changed during inference)`);
|
|
2077
2240
|
}
|
|
@@ -2293,7 +2456,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
2293
2456
|
let deletedAssociations = 0;
|
|
2294
2457
|
for (const chunk of existingChunks) {
|
|
2295
2458
|
// Delete vector embeddings (vec0 needs literal integer, not parameterized)
|
|
2296
|
-
this.db
|
|
2459
|
+
deleteVector(this.db, 'chunks', safeRowid(chunk.rowid));
|
|
2297
2460
|
deletedVectors++;
|
|
2298
2461
|
const associations = this.db.prepare(`
|
|
2299
2462
|
DELETE FROM chunk_entities WHERE chunk_rowid = ?
|
|
@@ -2638,7 +2801,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
2638
2801
|
const existing = db.prepare(`SELECT rowid FROM chunk_metadata WHERE document_id = ?`).all(documentId);
|
|
2639
2802
|
for (const ch of existing) {
|
|
2640
2803
|
db.prepare(`DELETE FROM chunk_entities WHERE chunk_rowid = ?`).run(ch.rowid);
|
|
2641
|
-
db
|
|
2804
|
+
deleteVector(db, 'chunks', safeRowid(ch.rowid));
|
|
2642
2805
|
}
|
|
2643
2806
|
db.prepare(`DELETE FROM chunk_metadata WHERE document_id = ?`).run(documentId);
|
|
2644
2807
|
deleteKgChunkRows(db, citedEvidence);
|
|
@@ -2653,9 +2816,23 @@ export class RAGKnowledgeGraphManager {
|
|
|
2653
2816
|
`).run(chunkId, documentId, seg.chunk_index, seg.text, seg.start_pos, seg.end_pos, seg.start_token, seg.end_token);
|
|
2654
2817
|
const rowid = Number(info.lastInsertRowid);
|
|
2655
2818
|
if (vec) {
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2819
|
+
// A validity-0 row left at a reused rowid cannot be written over (vec-slot-reuse): the chunk then stays
|
|
2820
|
+
// vectorless (missing) until the idle rebuild drops that row and the backfill embeds it.
|
|
2821
|
+
let wrote = true;
|
|
2822
|
+
try {
|
|
2823
|
+
writeVector(db, 'chunks', rowid, vec);
|
|
2824
|
+
}
|
|
2825
|
+
catch (e) {
|
|
2826
|
+
if (e instanceof InvalidSlotError) {
|
|
2827
|
+
wrote = false;
|
|
2828
|
+
this.coordinator?.requestRepair();
|
|
2829
|
+
}
|
|
2830
|
+
else
|
|
2831
|
+
throw e;
|
|
2832
|
+
}
|
|
2833
|
+
if (wrote)
|
|
2834
|
+
db.prepare(`UPDATE chunk_metadata SET input_hash = ?, profile_id = ?, provenance_state = ? WHERE rowid = ?`)
|
|
2835
|
+
.run(shaHex(seg.text), this.currentProfileId, provenance, rowid);
|
|
2659
2836
|
}
|
|
2660
2837
|
}
|
|
2661
2838
|
if (oldRowids.length > 0) {
|
|
@@ -2802,10 +2979,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
2802
2979
|
// Store in vector table (+ verified provenance, §6a-2 atomic)
|
|
2803
2980
|
try {
|
|
2804
2981
|
const tx = this.db.transaction(() => {
|
|
2805
|
-
this.db
|
|
2806
|
-
this.db.prepare(`
|
|
2807
|
-
INSERT INTO chunks (rowid, embedding) VALUES (${rowid}, ?)
|
|
2808
|
-
`).run(Buffer.from(embedding.buffer));
|
|
2982
|
+
writeVector(this.db, 'chunks', safeRowid(rowid), Buffer.from(embedding.buffer));
|
|
2809
2983
|
this.db.prepare(`UPDATE chunk_metadata SET input_hash = ?, profile_id = ?, provenance_state = 'verified' WHERE rowid = ?`)
|
|
2810
2984
|
.run(createHash('sha256').update(chunk.text).digest('hex'), this.currentProfileId, rowid);
|
|
2811
2985
|
});
|
|
@@ -2813,6 +2987,8 @@ export class RAGKnowledgeGraphManager {
|
|
|
2813
2987
|
embeddedCount++;
|
|
2814
2988
|
}
|
|
2815
2989
|
catch (error) {
|
|
2990
|
+
if (error instanceof InvalidSlotError)
|
|
2991
|
+
this.coordinator?.requestRepair();
|
|
2816
2992
|
const errMsg = `chunk ${chunk.chunk_id} (rowid=${rowid}, type=${typeof chunk.rowid}): ${error instanceof Error ? error.message : String(error)}`;
|
|
2817
2993
|
console.error(`Failed to embed ${errMsg}`);
|
|
2818
2994
|
errors.push(errMsg);
|
|
@@ -3251,7 +3427,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
3251
3427
|
`).run(chunk.rowid);
|
|
3252
3428
|
deletedAssociations += associations.changes;
|
|
3253
3429
|
// Delete vector embeddings (vec0 needs literal integer, not parameterized)
|
|
3254
|
-
db
|
|
3430
|
+
deleteVector(db, 'chunks', safeRowid(chunk.rowid));
|
|
3255
3431
|
deletedVectors++;
|
|
3256
3432
|
}
|
|
3257
3433
|
const metadata = db.prepare(`
|
|
@@ -3490,19 +3666,27 @@ export class RAGKnowledgeGraphManager {
|
|
|
3490
3666
|
this.db.exec(`DELETE FROM entity_observations`);
|
|
3491
3667
|
this.db.exec(`DELETE FROM observation_roots`);
|
|
3492
3668
|
this.db.exec(`DELETE FROM entities`);
|
|
3669
|
+
// documents → chunk_metadata is a CASCADE, but vec0 has no FK: delete the document chunk vectors first,
|
|
3670
|
+
// or they stay searchable-less orphans (IN-42 · same order as storeDocument's replace path).
|
|
3671
|
+
// rowids stay BigInt end to end (2_ r1 R1: a Number cast past 2^53 deletes the wrong vector)
|
|
3672
|
+
for (const c of this.db.prepare(`SELECT rowid FROM chunk_metadata WHERE document_id IS NOT NULL`).safeIntegers(true).all()) {
|
|
3673
|
+
deleteVector(this.db, 'chunks', c.rowid);
|
|
3674
|
+
}
|
|
3493
3675
|
this.db.exec(`DELETE FROM documents`);
|
|
3494
3676
|
// entities 를 지워도 파생 데이터는 따라오지 않는다: chunk_metadata 에는
|
|
3495
3677
|
// entities 로 가는 FK 가 없고 entity_embedding_metadata.entity_id 는 UNIQUE 일
|
|
3496
3678
|
// 뿐이다. 그래서 replace-import 뒤에 **사라진 entity 의 벡터와 KG chunk 가
|
|
3497
|
-
// 검색에 남았다**(advisor beta 발견 2). document chunk
|
|
3498
|
-
// CASCADE 로
|
|
3679
|
+
// 검색에 남았다**(advisor beta 발견 2). document chunk 메타는 documents 의
|
|
3680
|
+
// CASCADE 로 정리되고 그 벡터는 위에서 먼저 지웠다(IN-42) — 여기서는 entity·relationship chunk 만 지운다.
|
|
3499
3681
|
const orphanChunks = this.db.prepare(`SELECT rowid FROM chunk_metadata WHERE chunk_type IN ('entity','relationship')`)
|
|
3500
3682
|
.all();
|
|
3501
3683
|
for (const c of orphanChunks) {
|
|
3502
|
-
this.db
|
|
3684
|
+
deleteVector(this.db, 'chunks', Number(c.rowid));
|
|
3503
3685
|
this.db.prepare(`DELETE FROM chunk_metadata WHERE rowid = ?`).run(c.rowid);
|
|
3504
3686
|
}
|
|
3505
|
-
this.db.
|
|
3687
|
+
for (const m of this.db.prepare(`SELECT rowid FROM entity_embedding_metadata`).all()) {
|
|
3688
|
+
deleteVector(this.db, 'entity_embeddings', Number(m.rowid)); // validity-0 rows are left to the rebuild
|
|
3689
|
+
}
|
|
3506
3690
|
this.db.exec(`DELETE FROM entity_embedding_metadata`);
|
|
3507
3691
|
console.error('🗑️ Cleared existing data for full import');
|
|
3508
3692
|
}
|
|
@@ -3555,6 +3739,10 @@ export class RAGKnowledgeGraphManager {
|
|
|
3555
3739
|
// doc.id is a string here (checked above, before any write).
|
|
3556
3740
|
for (const r of collectEvidenceRowidsCitingDocument(this.db, doc.id))
|
|
3557
3741
|
citedEvidence.add(r);
|
|
3742
|
+
// REPLACE deletes an existing row → its chunk_metadata goes by CASCADE, the vec0 vectors would not (IN-42).
|
|
3743
|
+
for (const c of this.db.prepare(`SELECT rowid FROM chunk_metadata WHERE document_id = ?`).safeIntegers(true).all(doc.id)) {
|
|
3744
|
+
deleteVector(this.db, 'chunks', c.rowid);
|
|
3745
|
+
}
|
|
3558
3746
|
const result = stmt.run(doc.id, doc.content, JSON.stringify(doc.metadata || {}), doc.created_at || new Date().toISOString());
|
|
3559
3747
|
if (result.changes > 0) {
|
|
3560
3748
|
imported.documents++;
|
|
@@ -3724,7 +3912,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
3724
3912
|
}
|
|
3725
3913
|
/**
|
|
3726
3914
|
* Diagnostic seam for the graph re-ranker (evaluation change graph-role-evaluation, R2).
|
|
3727
|
-
* Returns the seed entities (query-vector matched,
|
|
3915
|
+
* Returns the seed entities (query-vector matched, legacy score > 0.4 = cosine ≈0.28, top-10 per variant) and the
|
|
3728
3916
|
* 1-hop connected entities exactly as hybridSearch(useGraph:true) computes them, plus edge detail
|
|
3729
3917
|
* that hybridSearch itself does not use (edge id, type, direction, confidence). It never generates
|
|
3730
3918
|
* candidates and never changes ranking; hybridSearch consumes only the name sets.
|
|
@@ -3776,7 +3964,7 @@ export class RAGKnowledgeGraphManager {
|
|
|
3776
3964
|
WHERE r.source_entity = ? OR r.target_entity = ?
|
|
3777
3965
|
ORDER BY r.id`);
|
|
3778
3966
|
for (const entity of similar) {
|
|
3779
|
-
const similarity =
|
|
3967
|
+
const similarity = legacyL2AsCosineScore(entity.distance); // legacy seed score, pinned (4ac3155) — not a cosine
|
|
3780
3968
|
if (similarity > 0.4) {
|
|
3781
3969
|
seeds.push({ entity_id: entity.entity_id, name: entity.name, similarity });
|
|
3782
3970
|
for (const row of edgeStmt.all(entity.entity_id, entity.entity_id, entity.entity_id, entity.entity_id, entity.entity_id)) {
|
|
@@ -4123,7 +4311,8 @@ export class RAGKnowledgeGraphManager {
|
|
|
4123
4311
|
({ summary, keyHighlight, relevanceScore } = await this.generateContentSummary(result.text, primaryQueryEmbedding, chunkEntities, result.chunk_type === 'relationship' ? 1 : 2 // Shorter summary for relationships
|
|
4124
4312
|
));
|
|
4125
4313
|
}
|
|
4126
|
-
|
|
4314
|
+
// default (rank fusion) = real cosine · legacy paths (useGraph:true or summaries on) keep the pinned 6.3.2 score
|
|
4315
|
+
const vectorSimilarity = rankFusion ? l2ToCosineSimilarity(result.distance) : legacyL2AsCosineScore(result.distance);
|
|
4127
4316
|
const ftsBoost = ftsBoostMap.get(result.chunk_id) || 0; // = 1/(60 + FTS rank), 0 if not an FTS hit
|
|
4128
4317
|
const vectorRank = vectorRankOf.get(result.chunk_id);
|
|
4129
4318
|
const finalScore = rankFusion
|
|
@@ -4387,9 +4576,17 @@ export class RAGKnowledgeGraphManager {
|
|
|
4387
4576
|
reconciliation_state: this.coordinator?.reconState ?? 'n/a',
|
|
4388
4577
|
reconciliation_last_error: this.coordinator?.reconLastError ?? null,
|
|
4389
4578
|
coverage: cov ? {
|
|
4390
|
-
|
|
4391
|
-
|
|
4579
|
+
// vec-slot-reuse D1c: an unresolvable slot map is reported as error with missing = null, not as a count.
|
|
4580
|
+
chunk: cov.chunk.error
|
|
4581
|
+
? { total: cov.chunk.total, embedded: null, verified: null, legacy_assumed: null, missing: null, orphan_vectors: null, error: cov.chunk.error }
|
|
4582
|
+
: { total: cov.chunk.total, embedded: cov.chunk.embedded, verified: cov.chunk.verified, legacy_assumed: cov.chunk.legacy_assumed, missing: cov.chunk.total - cov.chunk.embedded, orphan_vectors: cov.chunk.orphan_vectors ?? null },
|
|
4583
|
+
entity: cov.entity.error
|
|
4584
|
+
? { total: cov.entity.total, embedded: null, verified: null, legacy_assumed: null, missing: null, orphan_vectors: null, error: cov.entity.error }
|
|
4585
|
+
: { total: cov.entity.total, embedded: cov.entity.embedded, verified: cov.entity.verified, legacy_assumed: cov.entity.legacy_assumed, missing: cov.entity.total - cov.entity.embedded, orphan_vectors: cov.entity.orphan_vectors ?? null },
|
|
4392
4586
|
} : null,
|
|
4587
|
+
// vec-slot-reuse: per vec0 table = slots, searchable, validity-0 rows, reclaimable chunks, why a rebuild
|
|
4588
|
+
// is (not) due, and the last attempt of this process. Read-only: computing it writes nothing.
|
|
4589
|
+
vector_slots: this.db ? this.vectorSlotStats() : null,
|
|
4393
4590
|
}
|
|
4394
4591
|
};
|
|
4395
4592
|
}
|
|
@@ -4901,6 +5098,7 @@ async function main() {
|
|
|
4901
5098
|
// Synchronously, before anything is awaited: a host may SIGKILL before the settle
|
|
4902
5099
|
// sequence below completes (codex: ~185 ms after SIGTERM).
|
|
4903
5100
|
ragKgManager.checkpointWal();
|
|
5101
|
+
ragKgManager.noteWrites();
|
|
4904
5102
|
void (async () => {
|
|
4905
5103
|
try {
|
|
4906
5104
|
await server.close();
|
|
@@ -6,12 +6,16 @@ export interface CoverageSnapshot {
|
|
|
6
6
|
embedded: number;
|
|
7
7
|
verified: number;
|
|
8
8
|
legacy_assumed: number;
|
|
9
|
+
orphan_vectors?: number;
|
|
10
|
+
error?: string;
|
|
9
11
|
};
|
|
10
12
|
entity: {
|
|
11
13
|
total: number;
|
|
12
14
|
embedded: number;
|
|
13
15
|
verified: number;
|
|
14
16
|
legacy_assumed: number;
|
|
17
|
+
orphan_vectors?: number;
|
|
18
|
+
error?: string;
|
|
15
19
|
};
|
|
16
20
|
}
|
|
17
21
|
export interface CoordinatorDeps {
|
|
@@ -37,17 +41,25 @@ export declare class BackfillCoordinator {
|
|
|
37
41
|
private shuttingDown;
|
|
38
42
|
private connLossReruns;
|
|
39
43
|
private scanning;
|
|
44
|
+
private repairWanted;
|
|
45
|
+
readonly slotErrors: Map<string, string>;
|
|
46
|
+
repairRequestedAt: number;
|
|
47
|
+
requestRepair(): void;
|
|
48
|
+
takeRepairRequest(): boolean;
|
|
49
|
+
get repairRequested(): boolean;
|
|
40
50
|
private scanPromise;
|
|
41
51
|
private rerunRequested;
|
|
42
52
|
private snapshot;
|
|
43
53
|
constructor(deps: CoordinatorDeps);
|
|
44
54
|
get reconState(): ReconState;
|
|
55
|
+
get busy(): boolean;
|
|
45
56
|
get reconLastError(): string | undefined;
|
|
46
57
|
get eligible(): boolean;
|
|
47
58
|
private countNullWithVector;
|
|
48
59
|
private countRepairables;
|
|
49
60
|
private sanitize;
|
|
50
61
|
runReconciliation(): Promise<void>;
|
|
62
|
+
private reconActive;
|
|
51
63
|
private reconcileOnce;
|
|
52
64
|
private reconcileEntities;
|
|
53
65
|
private reconcileChunks;
|