@fgv/ts-agent-memory 5.1.0-49 → 5.1.0-50
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/dist/packlets/index/memoryIndex.js +23 -16
- package/dist/packlets/index/memoryIndex.js.map +1 -1
- package/dist/packlets/ingest/orchestrator.js +13 -1
- package/dist/packlets/ingest/orchestrator.js.map +1 -1
- package/dist/packlets/retrieve/linkTraversalRetriever.js +12 -26
- package/dist/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/dist/packlets/retrieve/recencyRetriever.js +7 -7
- package/dist/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/dist/packlets/retrieve/retriever.js +91 -10
- package/dist/packlets/retrieve/retriever.js.map +1 -1
- package/dist/packlets/retrieve/semanticRetriever.js +16 -16
- package/dist/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/dist/packlets/retrieve/structuredFilterRetriever.js +7 -7
- package/dist/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/dist/packlets/retrieve/tagRetriever.js +7 -7
- package/dist/packlets/retrieve/tagRetriever.js.map +1 -1
- package/dist/packlets/retrieve/temporalRetrievers.js +23 -20
- package/dist/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/dist/packlets/store/coverage.js +6 -0
- package/dist/packlets/store/coverage.js.map +1 -0
- package/dist/packlets/store/fileTreeMemoryStore.js +221 -79
- package/dist/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/dist/packlets/store/index.js +4 -0
- package/dist/packlets/store/index.js.map +1 -1
- package/dist/packlets/store/listSelection.js +36 -0
- package/dist/packlets/store/listSelection.js.map +1 -0
- package/dist/packlets/store/memoryStore.js +6 -0
- package/dist/packlets/store/memoryStore.js.map +1 -0
- package/dist/packlets/store/reconcile.js +6 -0
- package/dist/packlets/store/reconcile.js.map +1 -0
- package/dist/packlets/store/storeCoverage.js +102 -0
- package/dist/packlets/store/storeCoverage.js.map +1 -0
- package/dist/packlets/store/storeReconcile.js +122 -0
- package/dist/packlets/store/storeReconcile.js.map +1 -0
- package/dist/packlets/store/vectorMaintenance.js +116 -8
- package/dist/packlets/store/vectorMaintenance.js.map +1 -1
- package/dist/packlets/store/vectorRecordSource.js +44 -0
- package/dist/packlets/store/vectorRecordSource.js.map +1 -0
- package/dist/packlets/tools/memoryTools.js +25 -2
- package/dist/packlets/tools/memoryTools.js.map +1 -1
- package/dist/packlets/types/envelope.js +25 -0
- package/dist/packlets/types/envelope.js.map +1 -1
- package/dist/packlets/types/index.js +1 -0
- package/dist/packlets/types/index.js.map +1 -1
- package/dist/packlets/types/recordResolver.js +6 -0
- package/dist/packlets/types/recordResolver.js.map +1 -0
- package/dist/packlets/types/temporal.js.map +1 -1
- package/dist/packlets/vector/inMemoryCosineIndex.js +39 -18
- package/dist/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js +67 -12
- package/dist/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
- package/dist/packlets/vector/rebuildHelpers.js +38 -0
- package/dist/packlets/vector/rebuildHelpers.js.map +1 -0
- package/dist/packlets/vector/vectorIndex.js.map +1 -1
- package/dist/ts-agent-memory.d.ts +1035 -106
- package/lib/packlets/index/memoryIndex.d.ts +118 -27
- package/lib/packlets/index/memoryIndex.d.ts.map +1 -1
- package/lib/packlets/index/memoryIndex.js +23 -16
- package/lib/packlets/index/memoryIndex.js.map +1 -1
- package/lib/packlets/ingest/orchestrator.d.ts.map +1 -1
- package/lib/packlets/ingest/orchestrator.js +13 -1
- package/lib/packlets/ingest/orchestrator.js.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts +3 -10
- package/lib/packlets/retrieve/linkTraversalRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/linkTraversalRetriever.js +11 -25
- package/lib/packlets/retrieve/linkTraversalRetriever.js.map +1 -1
- package/lib/packlets/retrieve/recencyRetriever.d.ts +3 -3
- package/lib/packlets/retrieve/recencyRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/recencyRetriever.js +6 -6
- package/lib/packlets/retrieve/recencyRetriever.js.map +1 -1
- package/lib/packlets/retrieve/retriever.d.ts +88 -7
- package/lib/packlets/retrieve/retriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/retriever.js +94 -9
- package/lib/packlets/retrieve/retriever.js.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.d.ts +3 -5
- package/lib/packlets/retrieve/semanticRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/semanticRetriever.js +15 -15
- package/lib/packlets/retrieve/semanticRetriever.js.map +1 -1
- package/lib/packlets/retrieve/structuredFilterRetriever.d.ts +3 -3
- package/lib/packlets/retrieve/structuredFilterRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/structuredFilterRetriever.js +6 -6
- package/lib/packlets/retrieve/structuredFilterRetriever.js.map +1 -1
- package/lib/packlets/retrieve/tagRetriever.d.ts +3 -3
- package/lib/packlets/retrieve/tagRetriever.d.ts.map +1 -1
- package/lib/packlets/retrieve/tagRetriever.js +6 -6
- package/lib/packlets/retrieve/tagRetriever.js.map +1 -1
- package/lib/packlets/retrieve/temporalRetrievers.d.ts +7 -5
- package/lib/packlets/retrieve/temporalRetrievers.d.ts.map +1 -1
- package/lib/packlets/retrieve/temporalRetrievers.js +22 -19
- package/lib/packlets/retrieve/temporalRetrievers.js.map +1 -1
- package/lib/packlets/store/coverage.d.ts +102 -0
- package/lib/packlets/store/coverage.d.ts.map +1 -0
- package/lib/packlets/store/coverage.js +7 -0
- package/lib/packlets/store/coverage.js.map +1 -0
- package/lib/packlets/store/fileTreeMemoryStore.d.ts +53 -166
- package/lib/packlets/store/fileTreeMemoryStore.d.ts.map +1 -1
- package/lib/packlets/store/fileTreeMemoryStore.js +221 -79
- package/lib/packlets/store/fileTreeMemoryStore.js.map +1 -1
- package/lib/packlets/store/index.d.ts +4 -0
- package/lib/packlets/store/index.d.ts.map +1 -1
- package/lib/packlets/store/index.js +4 -0
- package/lib/packlets/store/index.js.map +1 -1
- package/lib/packlets/store/listSelection.d.ts +101 -0
- package/lib/packlets/store/listSelection.d.ts.map +1 -0
- package/lib/packlets/store/listSelection.js +40 -0
- package/lib/packlets/store/listSelection.js.map +1 -0
- package/lib/packlets/store/memoryStore.d.ts +237 -0
- package/lib/packlets/store/memoryStore.d.ts.map +1 -0
- package/lib/packlets/store/memoryStore.js +7 -0
- package/lib/packlets/store/memoryStore.js.map +1 -0
- package/lib/packlets/store/reconcile.d.ts +82 -0
- package/lib/packlets/store/reconcile.d.ts.map +1 -0
- package/lib/packlets/store/reconcile.js +7 -0
- package/lib/packlets/store/reconcile.js.map +1 -0
- package/lib/packlets/store/storeCoverage.d.ts +45 -0
- package/lib/packlets/store/storeCoverage.d.ts.map +1 -0
- package/lib/packlets/store/storeCoverage.js +105 -0
- package/lib/packlets/store/storeCoverage.js.map +1 -0
- package/lib/packlets/store/storeReconcile.d.ts +41 -0
- package/lib/packlets/store/storeReconcile.d.ts.map +1 -0
- package/lib/packlets/store/storeReconcile.js +125 -0
- package/lib/packlets/store/storeReconcile.js.map +1 -0
- package/lib/packlets/store/vectorMaintenance.d.ts +74 -0
- package/lib/packlets/store/vectorMaintenance.d.ts.map +1 -1
- package/lib/packlets/store/vectorMaintenance.js +117 -8
- package/lib/packlets/store/vectorMaintenance.js.map +1 -1
- package/lib/packlets/store/vectorRecordSource.d.ts +36 -0
- package/lib/packlets/store/vectorRecordSource.d.ts.map +1 -0
- package/lib/packlets/store/vectorRecordSource.js +47 -0
- package/lib/packlets/store/vectorRecordSource.js.map +1 -0
- package/lib/packlets/tools/memoryTools.d.ts.map +1 -1
- package/lib/packlets/tools/memoryTools.js +25 -2
- package/lib/packlets/tools/memoryTools.js.map +1 -1
- package/lib/packlets/types/envelope.d.ts +24 -2
- package/lib/packlets/types/envelope.d.ts.map +1 -1
- package/lib/packlets/types/envelope.js +26 -0
- package/lib/packlets/types/envelope.js.map +1 -1
- package/lib/packlets/types/index.d.ts +1 -0
- package/lib/packlets/types/index.d.ts.map +1 -1
- package/lib/packlets/types/index.js +1 -0
- package/lib/packlets/types/index.js.map +1 -1
- package/lib/packlets/types/recordResolver.d.ts +39 -0
- package/lib/packlets/types/recordResolver.d.ts.map +1 -0
- package/lib/packlets/types/recordResolver.js +7 -0
- package/lib/packlets/types/recordResolver.js.map +1 -0
- package/lib/packlets/types/temporal.d.ts +26 -6
- package/lib/packlets/types/temporal.d.ts.map +1 -1
- package/lib/packlets/types/temporal.js.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts +9 -2
- package/lib/packlets/vector/inMemoryCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryCosineIndex.js +40 -19
- package/lib/packlets/vector/inMemoryCosineIndex.js.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts +6 -3
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.d.ts.map +1 -1
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js +66 -11
- package/lib/packlets/vector/inMemoryFragmentCosineIndex.js.map +1 -1
- package/lib/packlets/vector/rebuildHelpers.d.ts +30 -0
- package/lib/packlets/vector/rebuildHelpers.d.ts.map +1 -0
- package/lib/packlets/vector/rebuildHelpers.js +42 -0
- package/lib/packlets/vector/rebuildHelpers.js.map +1 -0
- package/lib/packlets/vector/vectorIndex.d.ts +270 -15
- package/lib/packlets/vector/vectorIndex.d.ts.map +1 -1
- package/lib/packlets/vector/vectorIndex.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/index.ts"],"names":[],"mappings":"AAKA,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/index.ts"],"names":[],"mappings":"AAKA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC"}
|
|
@@ -18,6 +18,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
18
18
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
__exportStar(require("./coverage"), exports);
|
|
22
|
+
__exportStar(require("./reconcile"), exports);
|
|
23
|
+
__exportStar(require("./listSelection"), exports);
|
|
24
|
+
__exportStar(require("./memoryStore"), exports);
|
|
21
25
|
__exportStar(require("./fileTreeMemoryStore"), exports);
|
|
22
26
|
__exportStar(require("./scopeEncoding"), exports);
|
|
23
27
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/store/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;AAEH,wDAAsC;AACtC,kDAAgC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './fileTreeMemoryStore';\nexport * from './scopeEncoding';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/packlets/store/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;;;;;;;;;;;AAEH,6CAA2B;AAC3B,8CAA4B;AAC5B,kDAAgC;AAChC,gDAA8B;AAC9B,wDAAsC;AACtC,kDAAgC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nexport * from './coverage';\nexport * from './reconcile';\nexport * from './listSelection';\nexport * from './memoryStore';\nexport * from './fileTreeMemoryStore';\nexport * from './scopeEncoding';\n"]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Kind, MemoryScopeKey, Tag } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Filter for {@link IMemoryStore.list}. All present fields are ANDed together.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* **At least one of `scope` / `kind` / `tag` must be present.** `asOf` is a
|
|
7
|
+
* temporal *projection*, not a narrowing — it collapses versions rather than
|
|
8
|
+
* excluding entities — so it does not satisfy the requirement on its own. A
|
|
9
|
+
* selection that narrows nothing fails with a message naming
|
|
10
|
+
* {@link scanEveryRecord}.
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export interface IMemoryStoreListFilter {
|
|
14
|
+
/**
|
|
15
|
+
* Never present on a narrowing filter.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* Exclusivity marker, paired with the `never`s on {@link IWholeVaultScan}.
|
|
19
|
+
* Without it `{ scanEveryRecord: true, kind }` type-checks — TypeScript's
|
|
20
|
+
* excess-property check on a union admits any property declared by *any*
|
|
21
|
+
* member — and `list` then takes the scan branch and silently discards the
|
|
22
|
+
* narrowing. Since the whole point of requiring a selection is that a
|
|
23
|
+
* whole-vault read must be deliberate, a call that reads the whole vault while
|
|
24
|
+
* *looking* narrowed is the one outcome this surface must not permit.
|
|
25
|
+
*/
|
|
26
|
+
readonly scanEveryRecord?: never;
|
|
27
|
+
/** Restrict to records in this scope. */
|
|
28
|
+
readonly scope?: MemoryScopeKey;
|
|
29
|
+
/** Restrict to records of this kind. */
|
|
30
|
+
readonly kind?: Kind;
|
|
31
|
+
/** Restrict to records carrying this tag (exact match). */
|
|
32
|
+
readonly tag?: Tag;
|
|
33
|
+
/**
|
|
34
|
+
* For temporal (versioned) kinds: collapse each entity to the single version
|
|
35
|
+
* valid at this epoch ms. Non-temporal records are timeless and pass through
|
|
36
|
+
* unchanged. Absent = no temporal projection (every version is returned).
|
|
37
|
+
*/
|
|
38
|
+
readonly asOf?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The named, deliberately uncomfortable opt-out from
|
|
42
|
+
* {@link IMemoryStore.list}'s narrowing requirement — build one with
|
|
43
|
+
* {@link scanEveryRecord}.
|
|
44
|
+
* @public
|
|
45
|
+
*/
|
|
46
|
+
export interface IWholeVaultScan {
|
|
47
|
+
/** Discriminator. Always `true`; produced only by {@link scanEveryRecord}. */
|
|
48
|
+
readonly scanEveryRecord: true;
|
|
49
|
+
/**
|
|
50
|
+
* Never present on a whole-vault scan — see
|
|
51
|
+
* {@link IMemoryStoreListFilter.scanEveryRecord} for why these markers exist.
|
|
52
|
+
* A scan that also carried a narrowing axis would have that axis dropped.
|
|
53
|
+
*/
|
|
54
|
+
readonly scope?: never;
|
|
55
|
+
/** Never present on a whole-vault scan. See {@link IWholeVaultScan.scope}. */
|
|
56
|
+
readonly kind?: never;
|
|
57
|
+
/** Never present on a whole-vault scan. See {@link IWholeVaultScan.scope}. */
|
|
58
|
+
readonly tag?: never;
|
|
59
|
+
/**
|
|
60
|
+
* Optional temporal projection, exactly as on {@link IMemoryStoreListFilter}.
|
|
61
|
+
* NOT excluded, because `asOf` projects rather than narrows and composes with
|
|
62
|
+
* a whole-vault read exactly as it does with a filtered one.
|
|
63
|
+
*/
|
|
64
|
+
readonly asOf?: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* What {@link IMemoryStore.list} accepts: a narrowing filter, or the explicit
|
|
68
|
+
* whole-vault scan.
|
|
69
|
+
* @public
|
|
70
|
+
*/
|
|
71
|
+
export type MemoryListSelection = IMemoryStoreListFilter | IWholeVaultScan;
|
|
72
|
+
/**
|
|
73
|
+
* Ask {@link IMemoryStore.list} for **every record in the vault**, bodies
|
|
74
|
+
* included.
|
|
75
|
+
*
|
|
76
|
+
* @remarks
|
|
77
|
+
* `list` requires a selection and rejects one that narrows nothing, so this is
|
|
78
|
+
* the only way to get the whole vault — and that is the point. Since the index
|
|
79
|
+
* holds envelopes only, `list` materializes every survivor from storage, so an
|
|
80
|
+
* unnarrowed call reads one file per record. Making it impossible to write by
|
|
81
|
+
* accident is worth more than making it fast.
|
|
82
|
+
*
|
|
83
|
+
* It is named for what it costs rather than for what it returns, and it is
|
|
84
|
+
* greppable: `scanEveryRecord` enumerates every whole-vault read in a codebase in
|
|
85
|
+
* one search. This mirrors `@fgv/ts-extras`' `safer-fetch`, where `addressGuard`
|
|
86
|
+
* is required with no default and `allowAnyAddress()` is the named opt-out.
|
|
87
|
+
*
|
|
88
|
+
* **If you only need to select, you do not need this.** Use
|
|
89
|
+
* {@link IMemoryStore.listEntries}, which returns every entry's scope and
|
|
90
|
+
* envelope, reads no files, and needs no selection.
|
|
91
|
+
* @public
|
|
92
|
+
*/
|
|
93
|
+
export declare function scanEveryRecord(options?: {
|
|
94
|
+
readonly asOf?: number;
|
|
95
|
+
}): IWholeVaultScan;
|
|
96
|
+
/**
|
|
97
|
+
* Whether a selection is the explicit whole-vault opt-out.
|
|
98
|
+
* @public
|
|
99
|
+
*/
|
|
100
|
+
export declare function isWholeVaultScan(selection: MemoryListSelection): selection is IWholeVaultScan;
|
|
101
|
+
//# sourceMappingURL=listSelection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listSelection.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/listSelection.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAErD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,CAAC;IACjC,yCAAyC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC;IAChC,wCAAwC;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IACrB,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IACnB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,EAAE,IAAI,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC;IACrB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,sBAAsB,GAAG,eAAe,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,CAKrF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,mBAAmB,GAAG,SAAS,IAAI,eAAe,CAE7F"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2026 Erik Fortune
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.scanEveryRecord = scanEveryRecord;
|
|
8
|
+
exports.isWholeVaultScan = isWholeVaultScan;
|
|
9
|
+
/**
|
|
10
|
+
* Ask {@link IMemoryStore.list} for **every record in the vault**, bodies
|
|
11
|
+
* included.
|
|
12
|
+
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* `list` requires a selection and rejects one that narrows nothing, so this is
|
|
15
|
+
* the only way to get the whole vault — and that is the point. Since the index
|
|
16
|
+
* holds envelopes only, `list` materializes every survivor from storage, so an
|
|
17
|
+
* unnarrowed call reads one file per record. Making it impossible to write by
|
|
18
|
+
* accident is worth more than making it fast.
|
|
19
|
+
*
|
|
20
|
+
* It is named for what it costs rather than for what it returns, and it is
|
|
21
|
+
* greppable: `scanEveryRecord` enumerates every whole-vault read in a codebase in
|
|
22
|
+
* one search. This mirrors `@fgv/ts-extras`' `safer-fetch`, where `addressGuard`
|
|
23
|
+
* is required with no default and `allowAnyAddress()` is the named opt-out.
|
|
24
|
+
*
|
|
25
|
+
* **If you only need to select, you do not need this.** Use
|
|
26
|
+
* {@link IMemoryStore.listEntries}, which returns every entry's scope and
|
|
27
|
+
* envelope, reads no files, and needs no selection.
|
|
28
|
+
* @public
|
|
29
|
+
*/
|
|
30
|
+
function scanEveryRecord(options) {
|
|
31
|
+
return Object.assign({ scanEveryRecord: true }, ((options === null || options === void 0 ? void 0 : options.asOf) !== undefined ? { asOf: options.asOf } : {}));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Whether a selection is the explicit whole-vault opt-out.
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
function isWholeVaultScan(selection) {
|
|
38
|
+
return selection.scanEveryRecord === true;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=listSelection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listSelection.js","sourceRoot":"","sources":["../../../src/packlets/store/listSelection.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAkGH,0CAKC;AAMD,4CAEC;AAlCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAgB,eAAe,CAAC,OAAoC;IAClE,uBACE,eAAe,EAAE,IAAI,IAClB,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,IAAI,MAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAC9D;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,gBAAgB,CAAC,SAA8B;IAC7D,OAAQ,SAA6B,CAAC,eAAe,KAAK,IAAI,CAAC;AACjE,CAAC","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Kind, MemoryScopeKey, Tag } from '../types';\n\n/**\n * Filter for {@link IMemoryStore.list}. All present fields are ANDed together.\n *\n * @remarks\n * **At least one of `scope` / `kind` / `tag` must be present.** `asOf` is a\n * temporal *projection*, not a narrowing — it collapses versions rather than\n * excluding entities — so it does not satisfy the requirement on its own. A\n * selection that narrows nothing fails with a message naming\n * {@link scanEveryRecord}.\n * @public\n */\nexport interface IMemoryStoreListFilter {\n /**\n * Never present on a narrowing filter.\n *\n * @remarks\n * Exclusivity marker, paired with the `never`s on {@link IWholeVaultScan}.\n * Without it `{ scanEveryRecord: true, kind }` type-checks — TypeScript's\n * excess-property check on a union admits any property declared by *any*\n * member — and `list` then takes the scan branch and silently discards the\n * narrowing. Since the whole point of requiring a selection is that a\n * whole-vault read must be deliberate, a call that reads the whole vault while\n * *looking* narrowed is the one outcome this surface must not permit.\n */\n readonly scanEveryRecord?: never;\n /** Restrict to records in this scope. */\n readonly scope?: MemoryScopeKey;\n /** Restrict to records of this kind. */\n readonly kind?: Kind;\n /** Restrict to records carrying this tag (exact match). */\n readonly tag?: Tag;\n /**\n * For temporal (versioned) kinds: collapse each entity to the single version\n * valid at this epoch ms. Non-temporal records are timeless and pass through\n * unchanged. Absent = no temporal projection (every version is returned).\n */\n readonly asOf?: number;\n}\n\n/**\n * The named, deliberately uncomfortable opt-out from\n * {@link IMemoryStore.list}'s narrowing requirement — build one with\n * {@link scanEveryRecord}.\n * @public\n */\nexport interface IWholeVaultScan {\n /** Discriminator. Always `true`; produced only by {@link scanEveryRecord}. */\n readonly scanEveryRecord: true;\n /**\n * Never present on a whole-vault scan — see\n * {@link IMemoryStoreListFilter.scanEveryRecord} for why these markers exist.\n * A scan that also carried a narrowing axis would have that axis dropped.\n */\n readonly scope?: never;\n /** Never present on a whole-vault scan. See {@link IWholeVaultScan.scope}. */\n readonly kind?: never;\n /** Never present on a whole-vault scan. See {@link IWholeVaultScan.scope}. */\n readonly tag?: never;\n /**\n * Optional temporal projection, exactly as on {@link IMemoryStoreListFilter}.\n * NOT excluded, because `asOf` projects rather than narrows and composes with\n * a whole-vault read exactly as it does with a filtered one.\n */\n readonly asOf?: number;\n}\n\n/**\n * What {@link IMemoryStore.list} accepts: a narrowing filter, or the explicit\n * whole-vault scan.\n * @public\n */\nexport type MemoryListSelection = IMemoryStoreListFilter | IWholeVaultScan;\n\n/**\n * Ask {@link IMemoryStore.list} for **every record in the vault**, bodies\n * included.\n *\n * @remarks\n * `list` requires a selection and rejects one that narrows nothing, so this is\n * the only way to get the whole vault — and that is the point. Since the index\n * holds envelopes only, `list` materializes every survivor from storage, so an\n * unnarrowed call reads one file per record. Making it impossible to write by\n * accident is worth more than making it fast.\n *\n * It is named for what it costs rather than for what it returns, and it is\n * greppable: `scanEveryRecord` enumerates every whole-vault read in a codebase in\n * one search. This mirrors `@fgv/ts-extras`' `safer-fetch`, where `addressGuard`\n * is required with no default and `allowAnyAddress()` is the named opt-out.\n *\n * **If you only need to select, you do not need this.** Use\n * {@link IMemoryStore.listEntries}, which returns every entry's scope and\n * envelope, reads no files, and needs no selection.\n * @public\n */\nexport function scanEveryRecord(options?: { readonly asOf?: number }): IWholeVaultScan {\n return {\n scanEveryRecord: true,\n ...(options?.asOf !== undefined ? { asOf: options.asOf } : {})\n };\n}\n\n/**\n * Whether a selection is the explicit whole-vault opt-out.\n * @public\n */\nexport function isWholeVaultScan(selection: MemoryListSelection): selection is IWholeVaultScan {\n return (selection as IWholeVaultScan).scanEveryRecord === true;\n}\n"]}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { Result } from '@fgv/ts-utils';
|
|
2
|
+
import { DedupScope, EntityId, IMemoryRecord, IMemoryRecordResolver, Kind, MemoryId, MemoryScopeKey } from '../types';
|
|
3
|
+
import { IIndexedMemoryEntry } from '../index';
|
|
4
|
+
import { IMemoryRecordSource, IScopedMemoryRecord } from '../vector';
|
|
5
|
+
import { MemoryListSelection } from './listSelection';
|
|
6
|
+
import { IDerivedStateCoverage } from './coverage';
|
|
7
|
+
import { DerivedArtifact, ReconcileReport } from './reconcile';
|
|
8
|
+
/**
|
|
9
|
+
* The writable, FileTree-backed, content-hash-deduped memory store.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* Extends {@link IMemoryRecordResolver} because the retrievers take a resolver
|
|
13
|
+
* and a store is the obvious one to hand them — `{ index, resolver: store }` is
|
|
14
|
+
* the documented wiring, and it has to type-check for the `IMemoryStore` handle
|
|
15
|
+
* a consumer actually holds, not only for the concrete `FileTreeMemoryStore`.
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export interface IMemoryStore extends IMemoryRecordResolver {
|
|
19
|
+
/**
|
|
20
|
+
* Keyed read by entity id. Resolves `entityId` to a storage address via the
|
|
21
|
+
* registered {@link IIdentityCodec} for `kind`. Returns `undefined` when no
|
|
22
|
+
* record exists. For a versioned (temporal) kind the current version is
|
|
23
|
+
* *selected* from the derived in-memory index — the version history is walked
|
|
24
|
+
* over envelopes, never bodies — and then that one version is read from
|
|
25
|
+
* storage. So the selection costs no file reads and the result costs exactly
|
|
26
|
+
* one, rather than one per version.
|
|
27
|
+
*/
|
|
28
|
+
get(kind: Kind, entityId: EntityId): Promise<Result<IMemoryRecord<unknown> | undefined>>;
|
|
29
|
+
/**
|
|
30
|
+
* Direct read by `(scope, MemoryId)`. Returns `undefined` when not found.
|
|
31
|
+
*/
|
|
32
|
+
getById(scope: MemoryScopeKey, id: MemoryId): Promise<Result<IMemoryRecord<unknown> | undefined>>;
|
|
33
|
+
/**
|
|
34
|
+
* List records: select over the derived index, then materialize the survivors.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* **The selection is required and must narrow.** Since the index holds
|
|
38
|
+
* envelopes only, every returned record is read from storage — so a call that
|
|
39
|
+
* narrows nothing reads the whole vault, and that has to be a decision rather
|
|
40
|
+
* than a default. Omitting the argument is a compile error; passing one with no
|
|
41
|
+
* `scope` / `kind` / `tag` fails with a message naming {@link scanEveryRecord},
|
|
42
|
+
* which is how a caller says it meant it.
|
|
43
|
+
*
|
|
44
|
+
* The requirement buys **explicitness, not a cost bound** — `{ kind }` on a
|
|
45
|
+
* vault dominated by that kind still materializes most of it. What it prevents
|
|
46
|
+
* is the whole-vault read nobody chose.
|
|
47
|
+
*
|
|
48
|
+
* If you only need to select, use {@link IMemoryStore.listEntries} instead: no
|
|
49
|
+
* selection, no file reads.
|
|
50
|
+
*/
|
|
51
|
+
list(selection: MemoryListSelection): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;
|
|
52
|
+
/**
|
|
53
|
+
* How much of the store's **derived state** exists, resolved by {@link Kind}.
|
|
54
|
+
*
|
|
55
|
+
* @remarks
|
|
56
|
+
* Answers *"is my derived state consistent with my records, and if not by how
|
|
57
|
+
* much?"* for every artifact the store derives — `rank`, record vectors, and
|
|
58
|
+
* fragment vectors — in one call.
|
|
59
|
+
*
|
|
60
|
+
* **Cheap and total, by contract rather than by implementation.** Every input is
|
|
61
|
+
* an envelope field or an index-side count: it reads **no record bodies** and
|
|
62
|
+
* calls **no embedder**, and the walk over the vault's own state touches the
|
|
63
|
+
* filesystem not at all. That is why it takes no selection, unlike
|
|
64
|
+
* {@link IMemoryStore.list} — the guard there exists because an unnarrowed list
|
|
65
|
+
* reads the vault, and putting one here would decorate a free operation and make
|
|
66
|
+
* that guard mean less.
|
|
67
|
+
*
|
|
68
|
+
* **The index-side counts are the one exception, and it is the caller's own
|
|
69
|
+
* index that spends it.** A persistent index answers `size` / `recordCount` /
|
|
70
|
+
* `fragmentCount` with a query — `SqliteVecVectorIndex` runs a prepared `COUNT`
|
|
71
|
+
* — so on a durable backend this call does I/O and can **fail**. It is bounded
|
|
72
|
+
* (one count per wired index, never per record) and it is why the return is a
|
|
73
|
+
* `Result` rather than a bare value.
|
|
74
|
+
*
|
|
75
|
+
* *If a future addition to the report would require reading a record body, it
|
|
76
|
+
* does not belong on this report.*
|
|
77
|
+
*
|
|
78
|
+
* The counterpart is {@link IMemoryStore.reconcile}: coverage says **how big**
|
|
79
|
+
* the gap is, cheaply; reconcile says **what it was** and closes what it can, at
|
|
80
|
+
* the cost of re-running the embedder. Neither substitutes for the other.
|
|
81
|
+
*
|
|
82
|
+
* **A lane is reported here whenever its *index* is wired**, which is weaker
|
|
83
|
+
* than what reconcile requires (index **and** embedder). That is intended: an
|
|
84
|
+
* index without an embedder still holds vectors and still answers queries, so
|
|
85
|
+
* its coverage is a real number worth reporting. The consequence to expect is a
|
|
86
|
+
* half-wired store that reports a gap `reconcile` will refuse to close, naming
|
|
87
|
+
* the missing embedder.
|
|
88
|
+
*/
|
|
89
|
+
coverage(): Promise<Result<IDerivedStateCoverage>>;
|
|
90
|
+
/**
|
|
91
|
+
* Every entry in the vault — scope and envelope, **no bodies**. Reads no files
|
|
92
|
+
* and requires no selection, because there is nothing to be careful about: it
|
|
93
|
+
* returns what the index already holds.
|
|
94
|
+
*
|
|
95
|
+
* @remarks
|
|
96
|
+
* This is the whole-vault read most callers actually want. Selection, grouping,
|
|
97
|
+
* counting, "which kinds are in here", "what links at this" — all of it is
|
|
98
|
+
* envelope work. Reach for {@link IMemoryStore.list} with
|
|
99
|
+
* {@link scanEveryRecord} only when you genuinely need every body.
|
|
100
|
+
*
|
|
101
|
+
* Synchronous in spirit but `Promise`-returning for consistency with the rest
|
|
102
|
+
* of the store surface.
|
|
103
|
+
*/
|
|
104
|
+
listEntries(): Promise<Result<ReadonlyArray<IIndexedMemoryEntry>>>;
|
|
105
|
+
/**
|
|
106
|
+
* List EVERY record in the vault, each paired with its scope-qualified
|
|
107
|
+
* `(scope, id)` address — the projection {@link IMemoryRecordSource} requires.
|
|
108
|
+
* Unlike {@link IMemoryStore.list | list}, it takes no filter (whole-vault) and
|
|
109
|
+
* returns {@link IScopedMemoryRecord}s so a re-index keys each entry on the same
|
|
110
|
+
* scoped target the incremental embed-on-write path uses. Two records that share
|
|
111
|
+
* a filename stem across scopes appear as distinct entries.
|
|
112
|
+
*/
|
|
113
|
+
listScoped(): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>>;
|
|
114
|
+
/**
|
|
115
|
+
* Adapt this store to the {@link IMemoryRecordSource} seam so it can drive
|
|
116
|
+
* {@link IVectorIndex} rebuilds (e.g. `InMemoryCosineIndex.rebuild`). The
|
|
117
|
+
* returned source's `list()` delegates to {@link IMemoryStore.listScoped},
|
|
118
|
+
* **filtered to the kinds {@link IMemoryStore.embedsKind | embedsKind} reports** —
|
|
119
|
+
* this source exists to feed the record vector index, so a kind excluded from
|
|
120
|
+
* that index is excluded here too, and a reopen does not re-embed records the
|
|
121
|
+
* index will never return. With no
|
|
122
|
+
* {@link IFileTreeMemoryStoreCreateParams.embedKinds | embedKinds} declaration
|
|
123
|
+
* every kind participates and the filter is the identity. `listScoped` itself is
|
|
124
|
+
* **not** filtered and remains the whole-vault surface.
|
|
125
|
+
*
|
|
126
|
+
* The filter also **counts what it drops**, onto
|
|
127
|
+
* {@link IMemoryRecordListing.excluded} — this is the layer where the exclusion
|
|
128
|
+
* decision is made and so the only one that can. The count is always present
|
|
129
|
+
* (empty when nothing was excluded).
|
|
130
|
+
*
|
|
131
|
+
* The store cannot implement {@link IMemoryRecordSource} directly because its
|
|
132
|
+
* `list(filter?)` returns bare records (the ergonomic query surface) while the
|
|
133
|
+
* seam's `list()` returns scope-qualified records.
|
|
134
|
+
*/
|
|
135
|
+
asRecordSource(): IMemoryRecordSource;
|
|
136
|
+
/**
|
|
137
|
+
* The EFFECTIVE {@link DedupScope} for `kind` — the granularity at which a
|
|
138
|
+
* write for this kind deduplicates against the existing vault.
|
|
139
|
+
*
|
|
140
|
+
* @remarks
|
|
141
|
+
* This is a **read accessor over the store's already-injected write policies**,
|
|
142
|
+
* and it is the single place any caller — the store's own write path included —
|
|
143
|
+
* asks what a kind's dedup granularity is. It resolves the full chain the store
|
|
144
|
+
* applies on write: the kind's registered {@link IWritePolicy}, falling back to
|
|
145
|
+
* the store's default policy, then that policy's
|
|
146
|
+
* {@link IWritePolicy.dedupScope | dedupScope}, falling back to
|
|
147
|
+
* {@link DEFAULT_DEDUP_SCOPE}. Note the store's default policy is a
|
|
148
|
+
* {@link KnowledgeLwwPolicy}, which declares `'content'` — so a kind with NO
|
|
149
|
+
* registered policy resolves to `'content'`, not to `DEFAULT_DEDUP_SCOPE`.
|
|
150
|
+
*
|
|
151
|
+
* It exists so a caller that must agree with the store about dedup granularity
|
|
152
|
+
* — notably the ingest orchestrator's stage-4 layer-1 exact match — can read the
|
|
153
|
+
* declaration through this seam instead of being handed a second copy of the
|
|
154
|
+
* policy map. A second declaration site is precisely the defect this accessor
|
|
155
|
+
* was added to remove.
|
|
156
|
+
*
|
|
157
|
+
* Deliberately synchronous, total, and NOT `Result`-returning: it reads
|
|
158
|
+
* constructor-injected configuration, touches no I/O, and cannot fail (every
|
|
159
|
+
* link in the fallback chain has a total default). It exposes only the scope,
|
|
160
|
+
* never the {@link IWritePolicy} itself, so it can never become a back door for
|
|
161
|
+
* invoking admission or merge logic out of band.
|
|
162
|
+
*/
|
|
163
|
+
dedupScopeFor(kind: Kind): DedupScope;
|
|
164
|
+
/**
|
|
165
|
+
* Whether records of `kind` participate in the **record-granular** vector index.
|
|
166
|
+
*
|
|
167
|
+
* @remarks
|
|
168
|
+
* A read accessor over the store's injected {@link IFileTreeMemoryStoreCreateParams.embedKinds | embedKinds}
|
|
169
|
+
* declaration, in the same spirit as {@link IMemoryStore.dedupScopeFor} — one
|
|
170
|
+
* place to ask, so the store's write path and any caller reasoning about index
|
|
171
|
+
* coverage cannot disagree. `true` for every kind when no declaration was made.
|
|
172
|
+
*
|
|
173
|
+
* **This is distinct from a {@link MemoryEmbedder} decline, and the difference is
|
|
174
|
+
* cost.** An embedder that returns `undefined` has already been called: the
|
|
175
|
+
* round trip is paid, and on a locally-hosted model that round trip is the
|
|
176
|
+
* expense. A kind excluded here is never handed to the embedder at all. The
|
|
177
|
+
* decline makes the intent *expressible*; this makes it *free*.
|
|
178
|
+
*
|
|
179
|
+
* Deliberately synchronous, total, and NOT `Result`-returning: it reads
|
|
180
|
+
* constructor-injected configuration, touches no I/O, and cannot fail.
|
|
181
|
+
*/
|
|
182
|
+
embedsKind(kind: Kind): boolean;
|
|
183
|
+
/**
|
|
184
|
+
* Write a record. Validates the body, computes a content hash, deduplicates
|
|
185
|
+
* (scope-wide, before policy), applies the kind's {@link IWritePolicy}, stamps
|
|
186
|
+
* transaction-time metadata (`created` / `updated` / `seq` / `contentHash`),
|
|
187
|
+
* writes the file, and patches the index. Returns the written record — or the
|
|
188
|
+
* existing record unchanged on a dedup no-op.
|
|
189
|
+
*/
|
|
190
|
+
put(record: IMemoryRecord<unknown>): Promise<Result<IMemoryRecord<unknown>>>;
|
|
191
|
+
/**
|
|
192
|
+
* Repair one derived artifact for one {@link Kind} — **targeted and
|
|
193
|
+
* non-destructive**, unlike `IVectorIndex.rebuild`, which resets the index and
|
|
194
|
+
* re-embeds everything.
|
|
195
|
+
*
|
|
196
|
+
* @remarks
|
|
197
|
+
* The counterpart to {@link IMemoryStore.coverage}: coverage says **how big**
|
|
198
|
+
* the gap is and costs nothing; reconcile says **what it was** and closes what
|
|
199
|
+
* it can, at the cost of reading bodies and re-running the embedder. Neither
|
|
200
|
+
* substitutes for the other — in particular, only reconcile can distinguish a
|
|
201
|
+
* *declined* record from a *failed* one, because learning that requires calling
|
|
202
|
+
* the embedder again.
|
|
203
|
+
*
|
|
204
|
+
* **It only touches what is missing.** For the vector lanes it asks
|
|
205
|
+
* `has(target)` per record and skips the ones already held, so a repair after a
|
|
206
|
+
* brief outage costs a handful of embedder calls rather than a whole vault.
|
|
207
|
+
* That check is also the only way to see a record whose vector the index holds
|
|
208
|
+
* but whose envelope lost its `embeddingRef` — which needs a restamp and no
|
|
209
|
+
* embedder call at all, and which an `embeddingRef`-only repair cannot detect.
|
|
210
|
+
*
|
|
211
|
+
* **`artifact` is required and names one lane.** See {@link DerivedArtifact}
|
|
212
|
+
* for why an operation repairing "everything wired" would be the wrong shape.
|
|
213
|
+
*
|
|
214
|
+
* **A vector lane must be wired on BOTH halves — index *and* embedder — or this
|
|
215
|
+
* fails, and it is deliberately stricter than {@link IMemoryStore.coverage}.**
|
|
216
|
+
* The two ask different questions of the same wiring. Coverage asks *what does
|
|
217
|
+
* the index hold*, which an index alone can answer: an index wired without an
|
|
218
|
+
* embedder is a legal store (queries work; writes simply do not embed), so
|
|
219
|
+
* coverage reports that lane rather than pretending it is absent. Reconcile
|
|
220
|
+
* asks to *produce* vectors, which needs the embedder. So a half-wired store
|
|
221
|
+
* legitimately reports a coverage gap it cannot repair, and reconcile names the
|
|
222
|
+
* missing half rather than returning a cheerful success with every record in
|
|
223
|
+
* `failed`.
|
|
224
|
+
*
|
|
225
|
+
* Runs under the store's write lock, like any other mutation.
|
|
226
|
+
*/
|
|
227
|
+
reconcile(kind: Kind, artifact: DerivedArtifact): Promise<Result<ReconcileReport>>;
|
|
228
|
+
/**
|
|
229
|
+
* Delete a record by `(kind, entityId)`. Non-temporal kinds physically delete
|
|
230
|
+
* the file and return the deleted record's {@link MemoryId}. Temporal
|
|
231
|
+
* (versioned) kinds SOFT-delete: the current version is invalidated
|
|
232
|
+
* (`invalid_at` set), history is retained, and the invalidated version's
|
|
233
|
+
* {@link MemoryId} is returned.
|
|
234
|
+
*/
|
|
235
|
+
delete(kind: Kind, entityId: EntityId): Promise<Result<MemoryId>>;
|
|
236
|
+
}
|
|
237
|
+
//# sourceMappingURL=memoryStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoryStore.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/memoryStore.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EACL,UAAU,EACV,QAAQ,EACR,aAAa,EACb,qBAAqB,EACrB,IAAI,EACJ,QAAQ,EACR,cAAc,EACf,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE/D;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAa,SAAQ,qBAAqB;IACzD;;;;;;;;OAQG;IACH,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IAEzF;;OAEG;IACH,OAAO,CAAC,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IAElG;;;;;;;;;;;;;;;;;OAiBG;IACH,IAAI,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAoCG;IACH,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC;IAEnD;;;;;;;;;;;;;OAaG;IACH,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAEnE;;;;;;;OAOG;IACH,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAElE;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,cAAc,IAAI,mBAAmB,CAAC;IAEtC;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,UAAU,CAAC;IAEtC;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC;IAEhC;;;;;;OAMG;IACH,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC;IAEnF;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;CACnE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memoryStore.js","sourceRoot":"","sources":["../../../src/packlets/store/memoryStore.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Result } from '@fgv/ts-utils';\nimport {\n DedupScope,\n EntityId,\n IMemoryRecord,\n IMemoryRecordResolver,\n Kind,\n MemoryId,\n MemoryScopeKey\n} from '../types';\nimport { IIndexedMemoryEntry } from '../index';\nimport { IMemoryRecordSource, IScopedMemoryRecord } from '../vector';\nimport { MemoryListSelection } from './listSelection';\nimport { IDerivedStateCoverage } from './coverage';\nimport { DerivedArtifact, ReconcileReport } from './reconcile';\n\n/**\n * The writable, FileTree-backed, content-hash-deduped memory store.\n *\n * @remarks\n * Extends {@link IMemoryRecordResolver} because the retrievers take a resolver\n * and a store is the obvious one to hand them — `{ index, resolver: store }` is\n * the documented wiring, and it has to type-check for the `IMemoryStore` handle\n * a consumer actually holds, not only for the concrete `FileTreeMemoryStore`.\n * @public\n */\nexport interface IMemoryStore extends IMemoryRecordResolver {\n /**\n * Keyed read by entity id. Resolves `entityId` to a storage address via the\n * registered {@link IIdentityCodec} for `kind`. Returns `undefined` when no\n * record exists. For a versioned (temporal) kind the current version is\n * *selected* from the derived in-memory index — the version history is walked\n * over envelopes, never bodies — and then that one version is read from\n * storage. So the selection costs no file reads and the result costs exactly\n * one, rather than one per version.\n */\n get(kind: Kind, entityId: EntityId): Promise<Result<IMemoryRecord<unknown> | undefined>>;\n\n /**\n * Direct read by `(scope, MemoryId)`. Returns `undefined` when not found.\n */\n getById(scope: MemoryScopeKey, id: MemoryId): Promise<Result<IMemoryRecord<unknown> | undefined>>;\n\n /**\n * List records: select over the derived index, then materialize the survivors.\n *\n * @remarks\n * **The selection is required and must narrow.** Since the index holds\n * envelopes only, every returned record is read from storage — so a call that\n * narrows nothing reads the whole vault, and that has to be a decision rather\n * than a default. Omitting the argument is a compile error; passing one with no\n * `scope` / `kind` / `tag` fails with a message naming {@link scanEveryRecord},\n * which is how a caller says it meant it.\n *\n * The requirement buys **explicitness, not a cost bound** — `{ kind }` on a\n * vault dominated by that kind still materializes most of it. What it prevents\n * is the whole-vault read nobody chose.\n *\n * If you only need to select, use {@link IMemoryStore.listEntries} instead: no\n * selection, no file reads.\n */\n list(selection: MemoryListSelection): Promise<Result<ReadonlyArray<IMemoryRecord<unknown>>>>;\n\n /**\n * How much of the store's **derived state** exists, resolved by {@link Kind}.\n *\n * @remarks\n * Answers *\"is my derived state consistent with my records, and if not by how\n * much?\"* for every artifact the store derives — `rank`, record vectors, and\n * fragment vectors — in one call.\n *\n * **Cheap and total, by contract rather than by implementation.** Every input is\n * an envelope field or an index-side count: it reads **no record bodies** and\n * calls **no embedder**, and the walk over the vault's own state touches the\n * filesystem not at all. That is why it takes no selection, unlike\n * {@link IMemoryStore.list} — the guard there exists because an unnarrowed list\n * reads the vault, and putting one here would decorate a free operation and make\n * that guard mean less.\n *\n * **The index-side counts are the one exception, and it is the caller's own\n * index that spends it.** A persistent index answers `size` / `recordCount` /\n * `fragmentCount` with a query — `SqliteVecVectorIndex` runs a prepared `COUNT`\n * — so on a durable backend this call does I/O and can **fail**. It is bounded\n * (one count per wired index, never per record) and it is why the return is a\n * `Result` rather than a bare value.\n *\n * *If a future addition to the report would require reading a record body, it\n * does not belong on this report.*\n *\n * The counterpart is {@link IMemoryStore.reconcile}: coverage says **how big**\n * the gap is, cheaply; reconcile says **what it was** and closes what it can, at\n * the cost of re-running the embedder. Neither substitutes for the other.\n *\n * **A lane is reported here whenever its *index* is wired**, which is weaker\n * than what reconcile requires (index **and** embedder). That is intended: an\n * index without an embedder still holds vectors and still answers queries, so\n * its coverage is a real number worth reporting. The consequence to expect is a\n * half-wired store that reports a gap `reconcile` will refuse to close, naming\n * the missing embedder.\n */\n coverage(): Promise<Result<IDerivedStateCoverage>>;\n\n /**\n * Every entry in the vault — scope and envelope, **no bodies**. Reads no files\n * and requires no selection, because there is nothing to be careful about: it\n * returns what the index already holds.\n *\n * @remarks\n * This is the whole-vault read most callers actually want. Selection, grouping,\n * counting, \"which kinds are in here\", \"what links at this\" — all of it is\n * envelope work. Reach for {@link IMemoryStore.list} with\n * {@link scanEveryRecord} only when you genuinely need every body.\n *\n * Synchronous in spirit but `Promise`-returning for consistency with the rest\n * of the store surface.\n */\n listEntries(): Promise<Result<ReadonlyArray<IIndexedMemoryEntry>>>;\n\n /**\n * List EVERY record in the vault, each paired with its scope-qualified\n * `(scope, id)` address — the projection {@link IMemoryRecordSource} requires.\n * Unlike {@link IMemoryStore.list | list}, it takes no filter (whole-vault) and\n * returns {@link IScopedMemoryRecord}s so a re-index keys each entry on the same\n * scoped target the incremental embed-on-write path uses. Two records that share\n * a filename stem across scopes appear as distinct entries.\n */\n listScoped(): Promise<Result<ReadonlyArray<IScopedMemoryRecord>>>;\n\n /**\n * Adapt this store to the {@link IMemoryRecordSource} seam so it can drive\n * {@link IVectorIndex} rebuilds (e.g. `InMemoryCosineIndex.rebuild`). The\n * returned source's `list()` delegates to {@link IMemoryStore.listScoped},\n * **filtered to the kinds {@link IMemoryStore.embedsKind | embedsKind} reports** —\n * this source exists to feed the record vector index, so a kind excluded from\n * that index is excluded here too, and a reopen does not re-embed records the\n * index will never return. With no\n * {@link IFileTreeMemoryStoreCreateParams.embedKinds | embedKinds} declaration\n * every kind participates and the filter is the identity. `listScoped` itself is\n * **not** filtered and remains the whole-vault surface.\n *\n * The filter also **counts what it drops**, onto\n * {@link IMemoryRecordListing.excluded} — this is the layer where the exclusion\n * decision is made and so the only one that can. The count is always present\n * (empty when nothing was excluded).\n *\n * The store cannot implement {@link IMemoryRecordSource} directly because its\n * `list(filter?)` returns bare records (the ergonomic query surface) while the\n * seam's `list()` returns scope-qualified records.\n */\n asRecordSource(): IMemoryRecordSource;\n\n /**\n * The EFFECTIVE {@link DedupScope} for `kind` — the granularity at which a\n * write for this kind deduplicates against the existing vault.\n *\n * @remarks\n * This is a **read accessor over the store's already-injected write policies**,\n * and it is the single place any caller — the store's own write path included —\n * asks what a kind's dedup granularity is. It resolves the full chain the store\n * applies on write: the kind's registered {@link IWritePolicy}, falling back to\n * the store's default policy, then that policy's\n * {@link IWritePolicy.dedupScope | dedupScope}, falling back to\n * {@link DEFAULT_DEDUP_SCOPE}. Note the store's default policy is a\n * {@link KnowledgeLwwPolicy}, which declares `'content'` — so a kind with NO\n * registered policy resolves to `'content'`, not to `DEFAULT_DEDUP_SCOPE`.\n *\n * It exists so a caller that must agree with the store about dedup granularity\n * — notably the ingest orchestrator's stage-4 layer-1 exact match — can read the\n * declaration through this seam instead of being handed a second copy of the\n * policy map. A second declaration site is precisely the defect this accessor\n * was added to remove.\n *\n * Deliberately synchronous, total, and NOT `Result`-returning: it reads\n * constructor-injected configuration, touches no I/O, and cannot fail (every\n * link in the fallback chain has a total default). It exposes only the scope,\n * never the {@link IWritePolicy} itself, so it can never become a back door for\n * invoking admission or merge logic out of band.\n */\n dedupScopeFor(kind: Kind): DedupScope;\n\n /**\n * Whether records of `kind` participate in the **record-granular** vector index.\n *\n * @remarks\n * A read accessor over the store's injected {@link IFileTreeMemoryStoreCreateParams.embedKinds | embedKinds}\n * declaration, in the same spirit as {@link IMemoryStore.dedupScopeFor} — one\n * place to ask, so the store's write path and any caller reasoning about index\n * coverage cannot disagree. `true` for every kind when no declaration was made.\n *\n * **This is distinct from a {@link MemoryEmbedder} decline, and the difference is\n * cost.** An embedder that returns `undefined` has already been called: the\n * round trip is paid, and on a locally-hosted model that round trip is the\n * expense. A kind excluded here is never handed to the embedder at all. The\n * decline makes the intent *expressible*; this makes it *free*.\n *\n * Deliberately synchronous, total, and NOT `Result`-returning: it reads\n * constructor-injected configuration, touches no I/O, and cannot fail.\n */\n embedsKind(kind: Kind): boolean;\n\n /**\n * Write a record. Validates the body, computes a content hash, deduplicates\n * (scope-wide, before policy), applies the kind's {@link IWritePolicy}, stamps\n * transaction-time metadata (`created` / `updated` / `seq` / `contentHash`),\n * writes the file, and patches the index. Returns the written record — or the\n * existing record unchanged on a dedup no-op.\n */\n put(record: IMemoryRecord<unknown>): Promise<Result<IMemoryRecord<unknown>>>;\n\n /**\n * Repair one derived artifact for one {@link Kind} — **targeted and\n * non-destructive**, unlike `IVectorIndex.rebuild`, which resets the index and\n * re-embeds everything.\n *\n * @remarks\n * The counterpart to {@link IMemoryStore.coverage}: coverage says **how big**\n * the gap is and costs nothing; reconcile says **what it was** and closes what\n * it can, at the cost of reading bodies and re-running the embedder. Neither\n * substitutes for the other — in particular, only reconcile can distinguish a\n * *declined* record from a *failed* one, because learning that requires calling\n * the embedder again.\n *\n * **It only touches what is missing.** For the vector lanes it asks\n * `has(target)` per record and skips the ones already held, so a repair after a\n * brief outage costs a handful of embedder calls rather than a whole vault.\n * That check is also the only way to see a record whose vector the index holds\n * but whose envelope lost its `embeddingRef` — which needs a restamp and no\n * embedder call at all, and which an `embeddingRef`-only repair cannot detect.\n *\n * **`artifact` is required and names one lane.** See {@link DerivedArtifact}\n * for why an operation repairing \"everything wired\" would be the wrong shape.\n *\n * **A vector lane must be wired on BOTH halves — index *and* embedder — or this\n * fails, and it is deliberately stricter than {@link IMemoryStore.coverage}.**\n * The two ask different questions of the same wiring. Coverage asks *what does\n * the index hold*, which an index alone can answer: an index wired without an\n * embedder is a legal store (queries work; writes simply do not embed), so\n * coverage reports that lane rather than pretending it is absent. Reconcile\n * asks to *produce* vectors, which needs the embedder. So a half-wired store\n * legitimately reports a coverage gap it cannot repair, and reconcile names the\n * missing half rather than returning a cheerful success with every record in\n * `failed`.\n *\n * Runs under the store's write lock, like any other mutation.\n */\n reconcile(kind: Kind, artifact: DerivedArtifact): Promise<Result<ReconcileReport>>;\n\n /**\n * Delete a record by `(kind, entityId)`. Non-temporal kinds physically delete\n * the file and return the deleted record's {@link MemoryId}. Temporal\n * (versioned) kinds SOFT-delete: the current version is invalidated\n * (`invalid_at` set), history is retained, and the invalidated version's\n * {@link MemoryId} is returned.\n */\n delete(kind: Kind, entityId: EntityId): Promise<Result<MemoryId>>;\n}\n"]}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Kind } from '../types';
|
|
2
|
+
import { ISkippedVectorRecord } from '../vector';
|
|
3
|
+
/**
|
|
4
|
+
* Which derived artifact a {@link IMemoryStore.reconcile} call repairs.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* **The operation names its artifact rather than repairing everything wired**,
|
|
8
|
+
* and the reasons are structural rather than stylistic: the record and fragment
|
|
9
|
+
* lanes are independently wirable (so an unnamed repair on a fragment-only store
|
|
10
|
+
* would have to no-op or guess), their units are incommensurable (one vector per
|
|
11
|
+
* record vs. N), and their costs differ by orders of magnitude — a measured case
|
|
12
|
+
* put 68 fragments behind a single 56 KB record. An operation whose cost varies
|
|
13
|
+
* that much along a dimension it does not name is the accidental-expense shape
|
|
14
|
+
* `IMemoryStore.list`'s required selection exists to prevent.
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
export type DerivedArtifact = 'rank' | 'record-vector' | 'fragment-vector';
|
|
18
|
+
/** What every reconcile reports, whatever the artifact. @public */
|
|
19
|
+
export interface IReconcileReportBase {
|
|
20
|
+
/** The kind reconciled — named by the caller, so every count below is scalar. */
|
|
21
|
+
readonly kind: Kind;
|
|
22
|
+
/** Records of this kind considered. The denominator for everything else. */
|
|
23
|
+
readonly examined: number;
|
|
24
|
+
/** Records whose derived artifact was (re)produced by this call. */
|
|
25
|
+
readonly repaired: number;
|
|
26
|
+
/** Records that failed, with the error. A fault, never a decline. */
|
|
27
|
+
readonly failed: ReadonlyArray<ISkippedVectorRecord>;
|
|
28
|
+
}
|
|
29
|
+
/** A `rank` reconcile: re-run the projector and restamp what changed. @public */
|
|
30
|
+
export interface IRankReconcileReport extends IReconcileReportBase {
|
|
31
|
+
readonly artifact: 'rank';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A record-vector reconcile.
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
37
|
+
export interface IVectorReconcileReport extends IReconcileReportBase {
|
|
38
|
+
readonly artifact: 'record-vector';
|
|
39
|
+
/**
|
|
40
|
+
* Records the index already held and which needed **no embedder call** — the
|
|
41
|
+
* whole point of a targeted repair, and the number that shows what it saved
|
|
42
|
+
* against a `rebuild`.
|
|
43
|
+
*/
|
|
44
|
+
readonly alreadyIndexed: number;
|
|
45
|
+
/**
|
|
46
|
+
* Records whose vector the index held but whose envelope had **lost its
|
|
47
|
+
* `embeddingRef`** — repaired by restamping the reference, with no embedder
|
|
48
|
+
* call.
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* This case is invisible to an `embeddingRef`-only check, which is one of the
|
|
52
|
+
* two reasons `IVectorIndex.has` is on the contract: a reference-absent record
|
|
53
|
+
* looks identical to a never-embedded one until you ask the index.
|
|
54
|
+
*/
|
|
55
|
+
readonly restamped: number;
|
|
56
|
+
/** Records the embedder intentionally declined. Not a failure, not a gap. */
|
|
57
|
+
readonly declined: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A fragment-vector reconcile.
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* Note there is no `restamped`: the fragment lane has **no envelope marker**, so
|
|
64
|
+
* there is no reference that can go missing and nothing to restamp. The
|
|
65
|
+
* asymmetry with {@link IVectorReconcileReport} is real rather than an oversight.
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
export interface IFragmentReconcileReport extends IReconcileReportBase {
|
|
69
|
+
readonly artifact: 'fragment-vector';
|
|
70
|
+
/** Records already represented, needing no embedder call. */
|
|
71
|
+
readonly alreadyIndexed: number;
|
|
72
|
+
/** Records whose embedder produced no fragments — this lane's decline. */
|
|
73
|
+
readonly declined: number;
|
|
74
|
+
/** Fragments written by this call — the fan-out `repaired` cannot express. */
|
|
75
|
+
readonly fragments: number;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* What a {@link IMemoryStore.reconcile} established, discriminated by artifact.
|
|
79
|
+
* @public
|
|
80
|
+
*/
|
|
81
|
+
export type ReconcileReport = IRankReconcileReport | IVectorReconcileReport | IFragmentReconcileReport;
|
|
82
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/reconcile.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,eAAe,GAAG,iBAAiB,CAAC;AAE3E,mEAAmE;AACnE,MAAM,WAAW,oBAAoB;IACnC,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC,oBAAoB,CAAC,CAAC;CACtD;AAED,iFAAiF;AACjF,MAAM,WAAW,oBAAqB,SAAQ,oBAAoB;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAuB,SAAQ,oBAAoB;IAClE,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;;;;;;;;;OASG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,wBAAyB,SAAQ,oBAAoB;IACpE,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAC;IACrC,6DAA6D;IAC7D,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,oBAAoB,GAAG,sBAAsB,GAAG,wBAAwB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../src/packlets/store/reconcile.ts"],"names":[],"mappings":";AAAA;;;GAGG","sourcesContent":["/*\n * Copyright (c) 2026 Erik Fortune\n * SPDX-License-Identifier: MIT\n */\n\nimport { Kind } from '../types';\nimport { ISkippedVectorRecord } from '../vector';\n\n/**\n * Which derived artifact a {@link IMemoryStore.reconcile} call repairs.\n *\n * @remarks\n * **The operation names its artifact rather than repairing everything wired**,\n * and the reasons are structural rather than stylistic: the record and fragment\n * lanes are independently wirable (so an unnamed repair on a fragment-only store\n * would have to no-op or guess), their units are incommensurable (one vector per\n * record vs. N), and their costs differ by orders of magnitude — a measured case\n * put 68 fragments behind a single 56 KB record. An operation whose cost varies\n * that much along a dimension it does not name is the accidental-expense shape\n * `IMemoryStore.list`'s required selection exists to prevent.\n * @public\n */\nexport type DerivedArtifact = 'rank' | 'record-vector' | 'fragment-vector';\n\n/** What every reconcile reports, whatever the artifact. @public */\nexport interface IReconcileReportBase {\n /** The kind reconciled — named by the caller, so every count below is scalar. */\n readonly kind: Kind;\n /** Records of this kind considered. The denominator for everything else. */\n readonly examined: number;\n /** Records whose derived artifact was (re)produced by this call. */\n readonly repaired: number;\n /** Records that failed, with the error. A fault, never a decline. */\n readonly failed: ReadonlyArray<ISkippedVectorRecord>;\n}\n\n/** A `rank` reconcile: re-run the projector and restamp what changed. @public */\nexport interface IRankReconcileReport extends IReconcileReportBase {\n readonly artifact: 'rank';\n}\n\n/**\n * A record-vector reconcile.\n * @public\n */\nexport interface IVectorReconcileReport extends IReconcileReportBase {\n readonly artifact: 'record-vector';\n /**\n * Records the index already held and which needed **no embedder call** — the\n * whole point of a targeted repair, and the number that shows what it saved\n * against a `rebuild`.\n */\n readonly alreadyIndexed: number;\n /**\n * Records whose vector the index held but whose envelope had **lost its\n * `embeddingRef`** — repaired by restamping the reference, with no embedder\n * call.\n *\n * @remarks\n * This case is invisible to an `embeddingRef`-only check, which is one of the\n * two reasons `IVectorIndex.has` is on the contract: a reference-absent record\n * looks identical to a never-embedded one until you ask the index.\n */\n readonly restamped: number;\n /** Records the embedder intentionally declined. Not a failure, not a gap. */\n readonly declined: number;\n}\n\n/**\n * A fragment-vector reconcile.\n *\n * @remarks\n * Note there is no `restamped`: the fragment lane has **no envelope marker**, so\n * there is no reference that can go missing and nothing to restamp. The\n * asymmetry with {@link IVectorReconcileReport} is real rather than an oversight.\n * @public\n */\nexport interface IFragmentReconcileReport extends IReconcileReportBase {\n readonly artifact: 'fragment-vector';\n /** Records already represented, needing no embedder call. */\n readonly alreadyIndexed: number;\n /** Records whose embedder produced no fragments — this lane's decline. */\n readonly declined: number;\n /** Fragments written by this call — the fan-out `repaired` cannot express. */\n readonly fragments: number;\n}\n\n/**\n * What a {@link IMemoryStore.reconcile} established, discriminated by artifact.\n * @public\n */\nexport type ReconcileReport = IRankReconcileReport | IVectorReconcileReport | IFragmentReconcileReport;\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Result } from '@fgv/ts-utils';
|
|
2
|
+
import { Kind } from '../types';
|
|
3
|
+
import { IIndexedMemoryEntry } from '../index';
|
|
4
|
+
import { IFragmentVectorIndex, IVectorIndex } from '../vector';
|
|
5
|
+
import { IDerivedStateCoverage } from './coverage';
|
|
6
|
+
/**
|
|
7
|
+
* Everything {@link computeCoverage} needs, supplied by the store.
|
|
8
|
+
*
|
|
9
|
+
* @remarks
|
|
10
|
+
* Taken as inputs rather than reading a store, so this module has no dependency
|
|
11
|
+
* on `FileTreeMemoryStore` and the walk is testable on its own. Package-internal:
|
|
12
|
+
* `IMemoryStore.coverage` is the published surface.
|
|
13
|
+
*/
|
|
14
|
+
export interface IComputeCoverageParams {
|
|
15
|
+
/** The projected index entries — envelopes only, already free of file reads. */
|
|
16
|
+
readonly entries: ReadonlyArray<IIndexedMemoryEntry>;
|
|
17
|
+
/** Whether `kind` has a registered rank projector. */
|
|
18
|
+
readonly hasRankProjector: (kind: Kind) => boolean;
|
|
19
|
+
/** Whether ANY kind has one — distinguishes an unwired lane from an empty one. */
|
|
20
|
+
readonly anyRankProjector: boolean;
|
|
21
|
+
/** Whether `kind` participates in the record-granular vector index. */
|
|
22
|
+
readonly embedsKind: (kind: Kind) => boolean;
|
|
23
|
+
/** The wired record-vector index, or `undefined` when the lane is unwired. */
|
|
24
|
+
readonly vectorIndex: IVectorIndex | undefined;
|
|
25
|
+
/** The wired fragment index, or `undefined` when the lane is unwired. */
|
|
26
|
+
readonly fragmentIndex: IFragmentVectorIndex | undefined;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* One walk over the projected entries, producing every derived-state count.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* **Reads no record bodies and calls no embedder**, and the walk itself touches
|
|
33
|
+
* no filesystem. That is the contract `IMemoryStore.coverage` makes, and this
|
|
34
|
+
* function is where it is kept: every number below comes from an envelope field
|
|
35
|
+
* or an index-side count. The index-side counts are the one place that leaves
|
|
36
|
+
* envelope territory — on a durable backend they run a query, which is why they
|
|
37
|
+
* are captured (see the comment at the `captureResult` below) and why the walk's
|
|
38
|
+
* "no filesystem" claim is scoped to the walk rather than the whole call. A
|
|
39
|
+
* future addition that needs a body does not belong here.
|
|
40
|
+
*
|
|
41
|
+
* Extracted from `fileTreeMemoryStore.ts` because inlining it took that file past
|
|
42
|
+
* the 2000-line `max-lines` cap — the same reason `vectorRecordSource.ts` exists.
|
|
43
|
+
*/
|
|
44
|
+
export declare function computeCoverage(params: IComputeCoverageParams): Result<IDerivedStateCoverage>;
|
|
45
|
+
//# sourceMappingURL=storeCoverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storeCoverage.d.ts","sourceRoot":"","sources":["../../../src/packlets/store/storeCoverage.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAA0B,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAkB,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAqB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAEtE;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,mBAAmB,CAAC,CAAC;IACrD,sDAAsD;IACtD,QAAQ,CAAC,gBAAgB,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;IACnD,kFAAkF;IAClF,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;IAC7C,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,YAAY,GAAG,SAAS,CAAC;IAC/C,yEAAyE;IACzE,QAAQ,CAAC,aAAa,EAAE,oBAAoB,GAAG,SAAS,CAAC;CAC1D;AAkBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAkF7F"}
|