@gmickel/gno 1.27.1 → 1.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -20
- package/assets/skill/SKILL.md +59 -1
- package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip.sha256 +1 -0
- package/browser-extension/dist/chunk-b2zm0jjd.js +50 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/browser-extension/dist/preview.html +1 -1
- package/browser-extension/dist/service-worker.js +6 -6
- package/package.json +2 -2
- package/spec/cli.md +82 -5
- package/spec/db/schema.sql +31 -0
- package/spec/mcp.md +71 -0
- package/spec/output-schemas/ask.schema.json +156 -1
- package/spec/output-schemas/browser-clip-preview.schema.json +30 -0
- package/spec/output-schemas/collection-egress-check.schema.json +91 -0
- package/spec/output-schemas/collection-egress-policy-set.schema.json +56 -0
- package/spec/output-schemas/collection-egress-policy.schema.json +41 -0
- package/spec/output-schemas/context-capsule-v1.schema.json +183 -3
- package/spec/output-schemas/context-capsule-verification.schema.json +1 -1
- package/spec/output-schemas/egress-audit-management.schema.json +88 -0
- package/spec/output-schemas/get.schema.json +94 -0
- package/spec/output-schemas/mcp-http-error.schema.json +113 -2
- package/spec/output-schemas/mcp-job-status.schema.json +28 -0
- package/spec/output-schemas/multi-get.schema.json +128 -0
- package/spec/output-schemas/publish-artifact.schema.json +32 -0
- package/spec/output-schemas/record-import.schema.json +223 -0
- package/spec/output-schemas/retrieval-trace-common.schema.json +31 -0
- package/spec/output-schemas/retrieval-trace-export.schema.json +3 -0
- package/spec/output-schemas/retrieval-trace-qrels.schema.json +3 -0
- package/spec/output-schemas/retrieval-trace-summary.schema.json +4 -0
- package/spec/output-schemas/search-result.schema.json +94 -0
- package/spec/output-schemas/search-results.schema.json +155 -0
- package/spec/project-profile.schema.json +66 -0
- package/src/app/context-format.ts +1 -1
- package/src/app/context-runtime-contract.ts +16 -7
- package/src/cli/commands/ask.ts +7 -0
- package/src/cli/commands/collection/index.ts +5 -0
- package/src/cli/commands/collection/policy.ts +126 -0
- package/src/cli/commands/context-build.ts +5 -0
- package/src/cli/commands/daemon.ts +45 -3
- package/src/cli/commands/egress-audit.ts +68 -0
- package/src/cli/commands/embed.ts +2 -0
- package/src/cli/commands/get.ts +12 -2
- package/src/cli/commands/index-cmd.ts +13 -0
- package/src/cli/commands/multi-get.ts +9 -2
- package/src/cli/commands/query.ts +12 -0
- package/src/cli/commands/replay.ts +13 -3
- package/src/cli/commands/shared.ts +28 -0
- package/src/cli/commands/update.ts +5 -0
- package/src/cli/commands/vsearch.ts +3 -1
- package/src/cli/program.ts +127 -0
- package/src/config/index.ts +9 -0
- package/src/config/project-profile.ts +2 -0
- package/src/config/types.ts +66 -0
- package/src/converters/adapters/browser-export/adapter.ts +199 -0
- package/src/converters/adapters/browser-export/formats.ts +358 -0
- package/src/converters/adapters/email/adapter.ts +429 -0
- package/src/converters/adapters/email/html.ts +162 -0
- package/src/converters/adapters/email/mime.ts +454 -0
- package/src/converters/adapters/email/parameters.ts +77 -0
- package/src/converters/adapters/ical/adapter.ts +475 -0
- package/src/converters/adapters/ical/recurrence.ts +186 -0
- package/src/converters/adapters/jsonl/adapter.ts +238 -0
- package/src/converters/adapters/jsonl/config.ts +105 -0
- package/src/converters/adapters/shared/html-text.ts +165 -0
- package/src/converters/adapters/shared/record-utils.ts +79 -0
- package/src/converters/adapters/shared/utf8-lines.ts +141 -0
- package/src/converters/adapters/transcript/adapter.ts +295 -0
- package/src/converters/adapters/transcript/json.ts +184 -0
- package/src/converters/adapters/transcript/model.ts +171 -0
- package/src/converters/adapters/transcript/text.ts +58 -0
- package/src/converters/adapters/transcript/timed.ts +152 -0
- package/src/converters/index.ts +11 -1
- package/src/converters/mime.ts +8 -0
- package/src/converters/pipeline.ts +15 -1
- package/src/converters/registry.ts +37 -1
- package/src/converters/types.ts +136 -0
- package/src/core/browser-clip.ts +16 -1
- package/src/core/collection-egress-policy-projection.ts +28 -0
- package/src/core/collection-egress-policy-service.ts +443 -0
- package/src/core/collection-egress-policy-validation.ts +242 -0
- package/src/core/config-mutation.ts +31 -19
- package/src/core/context-capsule-schema.ts +198 -1
- package/src/core/context-capsule-validation.ts +4 -4
- package/src/core/context-capsule-verification.ts +5 -1
- package/src/core/context-capsule.ts +177 -112
- package/src/core/context-evidence.ts +93 -15
- package/src/core/destination-classifier.ts +402 -0
- package/src/core/document-capabilities.ts +12 -0
- package/src/core/egress-audit.ts +239 -0
- package/src/core/egress-authorization.ts +50 -0
- package/src/core/egress-enforcement.ts +264 -0
- package/src/core/egress-policy.ts +440 -0
- package/src/core/egress-provenance.ts +336 -0
- package/src/core/job-manager.ts +37 -0
- package/src/core/network-boundary-inventory.ts +261 -0
- package/src/core/project-profile-apply-state.ts +5 -0
- package/src/core/project-profile.ts +4 -0
- package/src/core/record-metadata.ts +49 -0
- package/src/core/retrieval-qrels.ts +6 -0
- package/src/core/retrieval-replay.ts +18 -4
- package/src/core/retrieval-trace-export.ts +23 -4
- package/src/core/retrieval-trace-management-helpers.ts +1 -0
- package/src/core/retrieval-trace-management-types.ts +3 -0
- package/src/core/retrieval-trace-management.ts +9 -1
- package/src/core/retrieval-trace-session.ts +94 -0
- package/src/core/retrieval-trace.ts +22 -0
- package/src/ingestion/record-adapter-canonical.ts +433 -0
- package/src/ingestion/record-adapter.ts +448 -0
- package/src/ingestion/record-container.ts +704 -0
- package/src/ingestion/record-path.ts +20 -0
- package/src/ingestion/record-sync.ts +70 -0
- package/src/ingestion/sync.ts +243 -36
- package/src/ingestion/types.ts +71 -1
- package/src/ingestion/walker.ts +31 -11
- package/src/llm/errors.ts +10 -0
- package/src/llm/http-inference.ts +175 -0
- package/src/llm/http-policy.ts +537 -0
- package/src/llm/httpEmbedding.ts +47 -28
- package/src/llm/httpGeneration.ts +31 -18
- package/src/llm/httpRerank.ts +30 -18
- package/src/llm/index.ts +1 -0
- package/src/llm/nodeLlamaCpp/adapter.ts +55 -20
- package/src/llm/pinned-http-connection.ts +177 -0
- package/src/mcp/context.ts +53 -1
- package/src/mcp/http-egress.ts +220 -0
- package/src/mcp/http-security.ts +32 -21
- package/src/mcp/http-session.ts +13 -2
- package/src/mcp/http-transport.ts +96 -6
- package/src/mcp/sync-egress.ts +24 -0
- package/src/mcp/tools/add-collection.ts +4 -0
- package/src/mcp/tools/ask.ts +1 -1
- package/src/mcp/tools/context.ts +9 -2
- package/src/mcp/tools/egress.ts +247 -0
- package/src/mcp/tools/embed.ts +1 -0
- package/src/mcp/tools/get.ts +10 -2
- package/src/mcp/tools/index-cmd.ts +7 -0
- package/src/mcp/tools/index.ts +98 -0
- package/src/mcp/tools/multi-get.ts +9 -2
- package/src/mcp/tools/query.ts +10 -0
- package/src/mcp/tools/remove-collection.ts +4 -0
- package/src/mcp/tools/sync.ts +6 -0
- package/src/mcp/tools/trace.ts +7 -1
- package/src/mcp/tools/vsearch.ts +1 -0
- package/src/mcp/tools/workspace-write.ts +2 -0
- package/src/pipeline/egress-lineage.ts +124 -0
- package/src/pipeline/filters.ts +1 -1
- package/src/pipeline/graph-retrieval.ts +7 -4
- package/src/pipeline/hybrid.ts +21 -4
- package/src/pipeline/result-context.ts +12 -4
- package/src/pipeline/search.ts +23 -4
- package/src/pipeline/types.ts +6 -0
- package/src/pipeline/vsearch.ts +67 -28
- package/src/publish/artifact.ts +31 -3
- package/src/publish/export-service.ts +25 -0
- package/src/sdk/client.ts +119 -2
- package/src/sdk/documents.ts +13 -5
- package/src/sdk/embed.ts +1 -0
- package/src/sdk/types.ts +33 -0
- package/src/serve/browse-tree.ts +4 -2
- package/src/serve/clipper-capture.ts +5 -1
- package/src/serve/closed-json.ts +61 -0
- package/src/serve/config-sync.ts +4 -1
- package/src/serve/context.ts +1 -0
- package/src/serve/doc-events.ts +110 -39
- package/src/serve/public/components/CollectionEgressCheckPanel.tsx +411 -0
- package/src/serve/public/components/CollectionEgressPolicyEditor.tsx +78 -0
- package/src/serve/public/components/CollectionModelDialog.tsx +93 -9
- package/src/serve/public/components/EgressAuditPanel.tsx +278 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Collections.tsx +3 -0
- package/src/serve/resident-request.ts +80 -3
- package/src/serve/resident-runtime.ts +63 -1
- package/src/serve/routes/api.ts +237 -65
- package/src/serve/routes/clipper.ts +64 -1
- package/src/serve/routes/mcp.ts +6 -0
- package/src/serve/routes/traces.ts +27 -4
- package/src/serve/server.ts +118 -7
- package/src/store/migrations/022-record-export-lineage.ts +42 -0
- package/src/store/migrations/023-collection-egress-policy.ts +61 -0
- package/src/store/migrations/024-egress-derived-lineage.ts +121 -0
- package/src/store/migrations/025-collection-egress-policy-revision.ts +40 -0
- package/src/store/migrations/index.ts +8 -0
- package/src/store/retrieval-trace-codec.ts +15 -0
- package/src/store/sqlite/adapter.ts +284 -14
- package/src/store/sqlite/change-journal-store.ts +41 -2
- package/src/store/sqlite/egress-audit-store.ts +485 -0
- package/src/store/sqlite/retrieval-trace-management-store.ts +47 -6
- package/src/store/sqlite/retrieval-trace-retention.ts +2 -0
- package/src/store/sqlite/retrieval-trace-rows.ts +8 -1
- package/src/store/sqlite/retrieval-trace-store.ts +104 -5
- package/src/store/types.ts +166 -1
- package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip.sha256 +0 -1
- package/browser-extension/dist/chunk-vn5f663b.js +0 -50
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/** Attach complete collection policy ownership to observable search results. */
|
|
2
|
+
|
|
3
|
+
import type { EgressLineage } from "../core/egress-provenance";
|
|
4
|
+
import type {
|
|
5
|
+
CollectionRow,
|
|
6
|
+
DocumentRow,
|
|
7
|
+
StorePort,
|
|
8
|
+
StoreResult,
|
|
9
|
+
} from "../store/types";
|
|
10
|
+
import type { SearchResult } from "./types";
|
|
11
|
+
|
|
12
|
+
import { parseUri } from "../app/constants";
|
|
13
|
+
import { createEgressLineage } from "../core/egress-provenance";
|
|
14
|
+
import { err, ok } from "../store/types";
|
|
15
|
+
|
|
16
|
+
type EgressLineageStore = Pick<
|
|
17
|
+
StorePort,
|
|
18
|
+
"getCollections" | "getDocumentsByMirrorHashes"
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
export const attachSearchResultEgressLineage = async (
|
|
22
|
+
store: EgressLineageStore,
|
|
23
|
+
results: SearchResult[],
|
|
24
|
+
options: {
|
|
25
|
+
ownershipHashes?: readonly string[];
|
|
26
|
+
ownershipDocuments?: readonly Pick<
|
|
27
|
+
DocumentRow,
|
|
28
|
+
"collection" | "mirrorHash"
|
|
29
|
+
>[];
|
|
30
|
+
collections?: readonly CollectionRow[];
|
|
31
|
+
} = {}
|
|
32
|
+
): Promise<StoreResult<void>> => {
|
|
33
|
+
if (results.length === 0) return ok(undefined);
|
|
34
|
+
const mirrorHashes = [
|
|
35
|
+
...new Set(
|
|
36
|
+
options.ownershipHashes ??
|
|
37
|
+
results
|
|
38
|
+
.map((result) => result.conversion?.mirrorHash)
|
|
39
|
+
.filter((hash): hash is string => Boolean(hash))
|
|
40
|
+
),
|
|
41
|
+
];
|
|
42
|
+
let ownershipDocuments = options.ownershipDocuments;
|
|
43
|
+
if (ownershipDocuments === undefined) {
|
|
44
|
+
if (typeof store.getDocumentsByMirrorHashes !== "function") {
|
|
45
|
+
ownershipDocuments = [];
|
|
46
|
+
} else {
|
|
47
|
+
const documentsResult = await store.getDocumentsByMirrorHashes(
|
|
48
|
+
mirrorHashes,
|
|
49
|
+
{
|
|
50
|
+
activeOnly: true,
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
if (!documentsResult.ok) return documentsResult;
|
|
54
|
+
ownershipDocuments = documentsResult.value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
let collectionRows = options.collections;
|
|
58
|
+
if (collectionRows === undefined) {
|
|
59
|
+
const collectionsResult = await store.getCollections();
|
|
60
|
+
if (!collectionsResult.ok) return collectionsResult;
|
|
61
|
+
collectionRows = collectionsResult.value;
|
|
62
|
+
}
|
|
63
|
+
const policyByCollection = new Map(
|
|
64
|
+
collectionRows.map((collection) => [
|
|
65
|
+
collection.name,
|
|
66
|
+
{
|
|
67
|
+
collection: collection.name,
|
|
68
|
+
policy: collection.egressPolicy,
|
|
69
|
+
source: collection.egressPolicySource,
|
|
70
|
+
},
|
|
71
|
+
])
|
|
72
|
+
);
|
|
73
|
+
const lineageByMirrorHash = new Map<string, EgressLineage>();
|
|
74
|
+
for (const mirrorHash of mirrorHashes) {
|
|
75
|
+
const ownerCollections = [
|
|
76
|
+
...new Set([
|
|
77
|
+
...ownershipDocuments
|
|
78
|
+
.filter((document) => document.mirrorHash === mirrorHash)
|
|
79
|
+
.map((owner) => owner.collection),
|
|
80
|
+
...results
|
|
81
|
+
.filter((result) => result.conversion?.mirrorHash === mirrorHash)
|
|
82
|
+
.map((result) => parseUri(result.uri)?.collection)
|
|
83
|
+
.filter((collection): collection is string => Boolean(collection)),
|
|
84
|
+
]),
|
|
85
|
+
];
|
|
86
|
+
const policies = ownerCollections.map((collection) =>
|
|
87
|
+
policyByCollection.get(collection)
|
|
88
|
+
);
|
|
89
|
+
if (policies.some((policy) => policy === undefined)) {
|
|
90
|
+
return err(
|
|
91
|
+
"INVALID_INPUT",
|
|
92
|
+
"Search result policy lineage references an unknown collection"
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
if (policies.length === 0) continue;
|
|
96
|
+
try {
|
|
97
|
+
lineageByMirrorHash.set(
|
|
98
|
+
mirrorHash,
|
|
99
|
+
createEgressLineage(
|
|
100
|
+
policies as NonNullable<(typeof policies)[number]>[]
|
|
101
|
+
)
|
|
102
|
+
);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return err(
|
|
105
|
+
"INVALID_INPUT",
|
|
106
|
+
"Search result policy lineage is incomplete",
|
|
107
|
+
error
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (const result of results) {
|
|
112
|
+
const lineage = result.conversion?.mirrorHash
|
|
113
|
+
? lineageByMirrorHash.get(result.conversion.mirrorHash)
|
|
114
|
+
: undefined;
|
|
115
|
+
if (!lineage) {
|
|
116
|
+
return err(
|
|
117
|
+
"INVALID_INPUT",
|
|
118
|
+
"Search result policy lineage could not be resolved"
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
result.egressLineage = lineage;
|
|
122
|
+
}
|
|
123
|
+
return ok(undefined);
|
|
124
|
+
};
|
package/src/pipeline/filters.ts
CHANGED
|
@@ -42,6 +42,9 @@ const EMPTY_EDGE_CONFIDENCE: Record<GraphEdgeConfidence, number> = {
|
|
|
42
42
|
similarity: 0,
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
+
const sourceRelPath = (doc: DocumentRow): string =>
|
|
46
|
+
doc.recordSourcePath ?? doc.relPath;
|
|
47
|
+
|
|
45
48
|
const confidenceWeight = (confidence: GraphEdgeConfidence): number => {
|
|
46
49
|
switch (confidence) {
|
|
47
50
|
case "explicit":
|
|
@@ -253,8 +256,8 @@ export async function expandGraphCandidates(
|
|
|
253
256
|
}
|
|
254
257
|
if (
|
|
255
258
|
options.relPathPrefix !== undefined &&
|
|
256
|
-
doc
|
|
257
|
-
!doc.
|
|
259
|
+
sourceRelPath(doc) !== options.relPathPrefix &&
|
|
260
|
+
!sourceRelPath(doc).startsWith(`${options.relPathPrefix}/`)
|
|
258
261
|
) {
|
|
259
262
|
continue;
|
|
260
263
|
}
|
|
@@ -337,8 +340,8 @@ export async function expandGraphCandidates(
|
|
|
337
340
|
(doc) =>
|
|
338
341
|
doc.mirrorHash &&
|
|
339
342
|
(options.relPathPrefix === undefined ||
|
|
340
|
-
doc
|
|
341
|
-
doc.
|
|
343
|
+
sourceRelPath(doc) === options.relPathPrefix ||
|
|
344
|
+
sourceRelPath(doc).startsWith(`${options.relPathPrefix}/`)) &&
|
|
342
345
|
matchesDocumentFilters(doc, options)
|
|
343
346
|
);
|
|
344
347
|
const docs = await filterDocsByTags(store, metadataFilteredDocs, options);
|
package/src/pipeline/hybrid.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
} from "./types";
|
|
22
22
|
|
|
23
23
|
import { normalizeContentTypes } from "../config/content-types";
|
|
24
|
+
import { projectRecordEvidenceMetadata } from "../core/record-metadata";
|
|
24
25
|
import { embedTextsWithRecovery } from "../embed/batch";
|
|
25
26
|
import { err, ok } from "../store/types";
|
|
26
27
|
import { createChunkLookup } from "./chunk-lookup";
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
sortByFinalScoreStable,
|
|
32
33
|
} from "./content-type-boost";
|
|
33
34
|
import { formatQueryForEmbedding } from "./contextual";
|
|
35
|
+
import { attachSearchResultEgressLineage } from "./egress-lineage";
|
|
34
36
|
import { expandQuery } from "./expansion";
|
|
35
37
|
import {
|
|
36
38
|
buildExplainResults,
|
|
@@ -840,10 +842,11 @@ export async function searchHybrid(
|
|
|
840
842
|
};
|
|
841
843
|
const matchesMetadataFilters = (doc: DocumentRow): boolean => {
|
|
842
844
|
const relPathPrefix = options.retrievalScope?.relPathPrefix;
|
|
845
|
+
const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
|
|
843
846
|
if (
|
|
844
847
|
relPathPrefix !== undefined &&
|
|
845
|
-
|
|
846
|
-
!
|
|
848
|
+
sourceRelPath !== relPathPrefix &&
|
|
849
|
+
!sourceRelPath.startsWith(`${relPathPrefix}/`)
|
|
847
850
|
) {
|
|
848
851
|
return false;
|
|
849
852
|
}
|
|
@@ -1052,9 +1055,9 @@ export async function searchHybrid(
|
|
|
1052
1055
|
snippetLanguage: chunk.language ?? undefined,
|
|
1053
1056
|
snippetRange,
|
|
1054
1057
|
source: {
|
|
1055
|
-
relPath: doc.relPath,
|
|
1058
|
+
relPath: doc.recordSourcePath ?? doc.relPath,
|
|
1056
1059
|
absPath: collectionPath
|
|
1057
|
-
? `${collectionPath}/${doc.relPath}`
|
|
1060
|
+
? `${collectionPath}/${doc.recordSourcePath ?? doc.relPath}`
|
|
1058
1061
|
: undefined,
|
|
1059
1062
|
mime: doc.sourceMime,
|
|
1060
1063
|
ext: doc.sourceExt,
|
|
@@ -1068,6 +1071,7 @@ export async function searchHybrid(
|
|
|
1068
1071
|
converterId: doc.converterId ?? undefined,
|
|
1069
1072
|
converterVersion: doc.converterVersion ?? undefined,
|
|
1070
1073
|
},
|
|
1074
|
+
record: projectRecordEvidenceMetadata(doc),
|
|
1071
1075
|
};
|
|
1072
1076
|
const auxiliaryBaseScore =
|
|
1073
1077
|
auxiliaryBaseScores.get(`${candidate.mirrorHash}:${candidate.seq}`) ??
|
|
@@ -1166,6 +1170,19 @@ export async function searchHybrid(
|
|
|
1166
1170
|
}
|
|
1167
1171
|
: undefined;
|
|
1168
1172
|
await attachSearchResultContexts(store, finalResults);
|
|
1173
|
+
const lineageResult = await attachSearchResultEgressLineage(
|
|
1174
|
+
store,
|
|
1175
|
+
finalResults,
|
|
1176
|
+
{
|
|
1177
|
+
ownershipHashes: [...neededHashes],
|
|
1178
|
+
ownershipDocuments:
|
|
1179
|
+
options.collection === undefined ? documents : undefined,
|
|
1180
|
+
collections: collectionsResult.ok ? collectionsResult.value : undefined,
|
|
1181
|
+
}
|
|
1182
|
+
);
|
|
1183
|
+
if (!lineageResult.ok) {
|
|
1184
|
+
return err("QUERY_FAILED", lineageResult.error.message);
|
|
1185
|
+
}
|
|
1169
1186
|
|
|
1170
1187
|
const output: SearchResults = {
|
|
1171
1188
|
results: finalResults,
|
|
@@ -16,10 +16,18 @@ export async function attachSearchResultContexts(
|
|
|
16
16
|
results: SearchResult[]
|
|
17
17
|
): Promise<void> {
|
|
18
18
|
const validResults = results
|
|
19
|
-
.map((result) =>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
.map((result) => {
|
|
20
|
+
const uriIdentity = contextIdentityFromUri(result.uri);
|
|
21
|
+
return {
|
|
22
|
+
identity: uriIdentity
|
|
23
|
+
? {
|
|
24
|
+
collection: uriIdentity.collection,
|
|
25
|
+
relPath: result.source.relPath,
|
|
26
|
+
}
|
|
27
|
+
: null,
|
|
28
|
+
result,
|
|
29
|
+
};
|
|
30
|
+
})
|
|
23
31
|
.filter(
|
|
24
32
|
(
|
|
25
33
|
entry
|
package/src/pipeline/search.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
SearchResults,
|
|
16
16
|
} from "./types";
|
|
17
17
|
|
|
18
|
+
import { projectRecordEvidenceMetadata } from "../core/record-metadata";
|
|
18
19
|
import { getContentBatch } from "../store/content-batch";
|
|
19
20
|
import { err, ok } from "../store/types";
|
|
20
21
|
import { createChunkLookup } from "./chunk-lookup";
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
hasAuxiliaryRanking,
|
|
24
25
|
sortByFinalScoreStable,
|
|
25
26
|
} from "./content-type-boost";
|
|
27
|
+
import { attachSearchResultEgressLineage } from "./egress-lineage";
|
|
26
28
|
import { matchesExcludedChunks, matchesExcludedText } from "./exclude";
|
|
27
29
|
import { selectBestChunkForSteering } from "./intent";
|
|
28
30
|
import { hasProjectAffinity } from "./project-affinity";
|
|
@@ -85,8 +87,9 @@ interface BuildResultContext {
|
|
|
85
87
|
/** Build SearchResult from FtsResult and related data */
|
|
86
88
|
function buildSearchResult(ctx: BuildResultContext): SearchResult {
|
|
87
89
|
const { fts, chunk, collectionPath, options, fullContent } = ctx;
|
|
90
|
+
const sourceRelPath = fts.recordSourcePath ?? fts.relPath ?? "";
|
|
88
91
|
const source: SearchResultSource = {
|
|
89
|
-
relPath:
|
|
92
|
+
relPath: sourceRelPath,
|
|
90
93
|
// Use actual source metadata with fallback to markdown defaults
|
|
91
94
|
mime: fts.sourceMime ?? "text/markdown",
|
|
92
95
|
ext: fts.sourceExt ?? ".md",
|
|
@@ -97,8 +100,8 @@ function buildSearchResult(ctx: BuildResultContext): SearchResult {
|
|
|
97
100
|
};
|
|
98
101
|
|
|
99
102
|
// Add absPath if we have collection path (cross-platform safe)
|
|
100
|
-
if (collectionPath &&
|
|
101
|
-
source.absPath = pathJoin(collectionPath,
|
|
103
|
+
if (collectionPath && sourceRelPath) {
|
|
104
|
+
source.absPath = pathJoin(collectionPath, sourceRelPath);
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
// Determine snippet content and range
|
|
@@ -134,6 +137,7 @@ function buildSearchResult(ctx: BuildResultContext): SearchResult {
|
|
|
134
137
|
snippetRange,
|
|
135
138
|
source,
|
|
136
139
|
conversion: fts.mirrorHash ? { mirrorHash: fts.mirrorHash } : undefined,
|
|
140
|
+
record: projectRecordEvidenceMetadata(fts),
|
|
137
141
|
};
|
|
138
142
|
if (!(chunk && fts.mirrorHash)) return result;
|
|
139
143
|
return attachSearchResultPlannerMetadata(result, {
|
|
@@ -287,7 +291,11 @@ export async function searchBm25(
|
|
|
287
291
|
|
|
288
292
|
const excluded =
|
|
289
293
|
matchesExcludedText(
|
|
290
|
-
[
|
|
294
|
+
[
|
|
295
|
+
fts.title ?? "",
|
|
296
|
+
fts.recordSourcePath ?? fts.relPath ?? "",
|
|
297
|
+
fts.snippet ?? "",
|
|
298
|
+
],
|
|
291
299
|
options.exclude
|
|
292
300
|
) ||
|
|
293
301
|
matchesExcludedChunks(
|
|
@@ -426,6 +434,17 @@ export async function searchBm25(
|
|
|
426
434
|
if (metadata) metadata.retrievalRank = index + 1;
|
|
427
435
|
}
|
|
428
436
|
await attachSearchResultContexts(store, finalResults);
|
|
437
|
+
const lineageResult = await attachSearchResultEgressLineage(
|
|
438
|
+
store,
|
|
439
|
+
finalResults,
|
|
440
|
+
{
|
|
441
|
+
ownershipHashes: uniqueHashes,
|
|
442
|
+
collections: collectionsResult.ok ? collectionsResult.value : undefined,
|
|
443
|
+
}
|
|
444
|
+
);
|
|
445
|
+
if (!lineageResult.ok) {
|
|
446
|
+
return err("QUERY_FAILED", lineageResult.error.message);
|
|
447
|
+
}
|
|
429
448
|
|
|
430
449
|
const output: SearchResults = {
|
|
431
450
|
results: finalResults,
|
package/src/pipeline/types.ts
CHANGED
|
@@ -10,6 +10,8 @@ import type {
|
|
|
10
10
|
ContextCapsuleV1,
|
|
11
11
|
ContextCapsuleVerification,
|
|
12
12
|
} from "../core/context-capsule";
|
|
13
|
+
import type { EgressLineage } from "../core/egress-provenance";
|
|
14
|
+
import type { RecordEvidenceMetadata } from "../core/record-metadata";
|
|
13
15
|
import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
|
|
14
16
|
import type { StoreResult } from "../store/types";
|
|
15
17
|
import type { ClaimVerificationResult } from "./claim-verification";
|
|
@@ -86,6 +88,10 @@ export interface SearchResult {
|
|
|
86
88
|
context?: string;
|
|
87
89
|
source: SearchResultSource;
|
|
88
90
|
conversion?: SearchResultConversion;
|
|
91
|
+
/** Bounded logical-record provenance for file/export adapters. */
|
|
92
|
+
record?: RecordEvidenceMetadata;
|
|
93
|
+
/** Complete, most-restrictive collection ownership for this derived result. */
|
|
94
|
+
egressLineage?: EgressLineage;
|
|
89
95
|
[SEARCH_RESULT_PLANNER_METADATA]?: SearchResultPlannerMetadata;
|
|
90
96
|
}
|
|
91
97
|
|
package/src/pipeline/vsearch.ts
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Config } from "../config/types";
|
|
9
9
|
import type { EmbeddingPort } from "../llm/types";
|
|
10
|
-
import type { StorePort } from "../store/types";
|
|
10
|
+
import type { DocumentRow, StorePort } from "../store/types";
|
|
11
11
|
import type { VectorIndexPort } from "../store/vector/types";
|
|
12
12
|
import type { SearchOptions, SearchResult, SearchResults } from "./types";
|
|
13
13
|
|
|
14
14
|
import { normalizeContentTypes } from "../config/content-types";
|
|
15
|
+
import { projectRecordEvidenceMetadata } from "../core/record-metadata";
|
|
15
16
|
import { getContentBatch } from "../store/content-batch";
|
|
16
17
|
import { err, ok } from "../store/types";
|
|
17
18
|
import { createChunkLookup } from "./chunk-lookup";
|
|
@@ -21,6 +22,7 @@ import {
|
|
|
21
22
|
sortByFinalScoreStable,
|
|
22
23
|
} from "./content-type-boost";
|
|
23
24
|
import { formatQueryForEmbedding } from "./contextual";
|
|
25
|
+
import { attachSearchResultEgressLineage } from "./egress-lineage";
|
|
24
26
|
import { matchesExcludedChunks, matchesExcludedText } from "./exclude";
|
|
25
27
|
import { selectBestChunkForSteering } from "./intent";
|
|
26
28
|
import { hasProjectAffinity } from "./project-affinity";
|
|
@@ -142,17 +144,18 @@ export async function searchVectorWithEmbedding(
|
|
|
142
144
|
}
|
|
143
145
|
|
|
144
146
|
// Cache docs to avoid N+1 queries (filtered by collection and tags)
|
|
145
|
-
const docsByMirrorHash
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
147
|
+
const { documents: docsByMirrorHash, ownershipDocuments } =
|
|
148
|
+
await buildDocumentMap(store, {
|
|
149
|
+
collection: options.collection,
|
|
150
|
+
relPathPrefix: options.retrievalScope?.relPathPrefix,
|
|
151
|
+
tagsAll: options.tagsAll,
|
|
152
|
+
tagsAny: options.tagsAny,
|
|
153
|
+
since: temporalRange.since,
|
|
154
|
+
until: temporalRange.until,
|
|
155
|
+
categories: options.categories,
|
|
156
|
+
author: options.author,
|
|
157
|
+
mirrorHashes: uniqueHashes,
|
|
158
|
+
});
|
|
156
159
|
|
|
157
160
|
// Pre-fetch all chunks in one batch query (eliminates N+1)
|
|
158
161
|
const chunksMapResult = await store.getChunksBatch(uniqueHashes);
|
|
@@ -213,11 +216,12 @@ export async function searchVectorWithEmbedding(
|
|
|
213
216
|
const docs = auxiliaryRankingActive ? matchingDocs : [matchingDocs.at(-1)!];
|
|
214
217
|
for (const doc of docs) {
|
|
215
218
|
const collectionPath = collectionPaths.get(doc.collection);
|
|
219
|
+
const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
|
|
216
220
|
const excluded =
|
|
217
221
|
matchesExcludedText(
|
|
218
222
|
[
|
|
219
223
|
doc.title ?? "",
|
|
220
|
-
|
|
224
|
+
sourceRelPath,
|
|
221
225
|
doc.author ?? "",
|
|
222
226
|
doc.contentType ?? "",
|
|
223
227
|
...(doc.categories ?? []),
|
|
@@ -248,9 +252,9 @@ export async function searchVectorWithEmbedding(
|
|
|
248
252
|
endLine: chunk.endLine,
|
|
249
253
|
},
|
|
250
254
|
source: {
|
|
251
|
-
relPath:
|
|
255
|
+
relPath: sourceRelPath,
|
|
252
256
|
absPath: collectionPath
|
|
253
|
-
? `${collectionPath}/${
|
|
257
|
+
? `${collectionPath}/${sourceRelPath}`
|
|
254
258
|
: undefined,
|
|
255
259
|
mime: doc.sourceMime,
|
|
256
260
|
ext: doc.sourceExt,
|
|
@@ -266,6 +270,7 @@ export async function searchVectorWithEmbedding(
|
|
|
266
270
|
converterVersion: doc.converterVersion ?? undefined,
|
|
267
271
|
}
|
|
268
272
|
: undefined,
|
|
273
|
+
record: projectRecordEvidenceMetadata(doc),
|
|
269
274
|
},
|
|
270
275
|
doc.collection,
|
|
271
276
|
contentTypeRules,
|
|
@@ -332,6 +337,7 @@ export async function searchVectorWithEmbedding(
|
|
|
332
337
|
: undefined;
|
|
333
338
|
|
|
334
339
|
const collectionPath = collectionPaths.get(doc.collection);
|
|
340
|
+
const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
|
|
335
341
|
|
|
336
342
|
const result = applyContentTypeBoost(
|
|
337
343
|
{
|
|
@@ -349,9 +355,9 @@ export async function searchVectorWithEmbedding(
|
|
|
349
355
|
? undefined
|
|
350
356
|
: { startLine: chunk.startLine, endLine: chunk.endLine },
|
|
351
357
|
source: {
|
|
352
|
-
relPath:
|
|
358
|
+
relPath: sourceRelPath,
|
|
353
359
|
absPath: collectionPath
|
|
354
|
-
? `${collectionPath}/${
|
|
360
|
+
? `${collectionPath}/${sourceRelPath}`
|
|
355
361
|
: undefined,
|
|
356
362
|
mime: doc.sourceMime,
|
|
357
363
|
ext: doc.sourceExt,
|
|
@@ -367,6 +373,7 @@ export async function searchVectorWithEmbedding(
|
|
|
367
373
|
converterVersion: doc.converterVersion ?? undefined,
|
|
368
374
|
}
|
|
369
375
|
: undefined,
|
|
376
|
+
record: projectRecordEvidenceMetadata(doc),
|
|
370
377
|
},
|
|
371
378
|
doc.collection,
|
|
372
379
|
contentTypeRules,
|
|
@@ -418,6 +425,18 @@ export async function searchVectorWithEmbedding(
|
|
|
418
425
|
if (metadata) metadata.retrievalRank = index + 1;
|
|
419
426
|
}
|
|
420
427
|
await attachSearchResultContexts(store, finalResults);
|
|
428
|
+
const lineageResult = await attachSearchResultEgressLineage(
|
|
429
|
+
store,
|
|
430
|
+
finalResults,
|
|
431
|
+
{
|
|
432
|
+
ownershipHashes: uniqueHashes,
|
|
433
|
+
ownershipDocuments,
|
|
434
|
+
collections: collectionsResult.ok ? collectionsResult.value : undefined,
|
|
435
|
+
}
|
|
436
|
+
);
|
|
437
|
+
if (!lineageResult.ok) {
|
|
438
|
+
return err("QUERY_FAILED", lineageResult.error.message);
|
|
439
|
+
}
|
|
421
440
|
|
|
422
441
|
const output: SearchResults = {
|
|
423
442
|
results: finalResults,
|
|
@@ -514,6 +533,12 @@ interface DocumentInfo {
|
|
|
514
533
|
mirrorHash: string | null;
|
|
515
534
|
converterId: string | null;
|
|
516
535
|
converterVersion: string | null;
|
|
536
|
+
recordKey: DocumentRow["recordKey"];
|
|
537
|
+
recordSourcePath: DocumentRow["recordSourcePath"];
|
|
538
|
+
recordSourceLocator: DocumentRow["recordSourceLocator"];
|
|
539
|
+
recordMetadata: DocumentRow["recordMetadata"];
|
|
540
|
+
recordAnchors: DocumentRow["recordAnchors"];
|
|
541
|
+
recordAdapterFingerprint: DocumentRow["recordAdapterFingerprint"];
|
|
517
542
|
}
|
|
518
543
|
|
|
519
544
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -549,28 +574,35 @@ function matchesCategoryFilter(
|
|
|
549
574
|
async function buildDocumentMap(
|
|
550
575
|
store: StorePort,
|
|
551
576
|
options: DocumentMapOptions = {}
|
|
552
|
-
): Promise<
|
|
553
|
-
|
|
577
|
+
): Promise<{
|
|
578
|
+
documents: Map<string, DocumentInfo[]>;
|
|
579
|
+
ownershipDocuments: DocumentRow[];
|
|
580
|
+
}> {
|
|
581
|
+
const documents = new Map<string, DocumentInfo[]>();
|
|
554
582
|
|
|
555
583
|
if (options.mirrorHashes && options.mirrorHashes.length === 0) {
|
|
556
|
-
return
|
|
584
|
+
return { documents, ownershipDocuments: [] };
|
|
557
585
|
}
|
|
558
586
|
|
|
559
587
|
const docs = options.mirrorHashes
|
|
560
588
|
? await store.getDocumentsByMirrorHashes(options.mirrorHashes, {
|
|
561
|
-
collection: options.collection,
|
|
562
589
|
activeOnly: true,
|
|
563
590
|
})
|
|
564
591
|
: await store.listDocuments(options.collection);
|
|
565
592
|
if (!docs.ok) {
|
|
566
|
-
return
|
|
593
|
+
return { documents, ownershipDocuments: [] };
|
|
567
594
|
}
|
|
568
595
|
|
|
569
596
|
// Filter docs with mirrorHash.
|
|
570
597
|
// listDocuments path still needs explicit active filter.
|
|
571
|
-
const
|
|
598
|
+
const ownershipDocuments = options.mirrorHashes
|
|
572
599
|
? docs.value.filter((d) => d.mirrorHash)
|
|
573
600
|
: docs.value.filter((d) => d.mirrorHash && d.active);
|
|
601
|
+
const activeDocs = options.collection
|
|
602
|
+
? ownershipDocuments.filter(
|
|
603
|
+
(document) => document.collection === options.collection
|
|
604
|
+
)
|
|
605
|
+
: ownershipDocuments;
|
|
574
606
|
const temporalRange: TemporalRange = {
|
|
575
607
|
since: options.since,
|
|
576
608
|
until: options.until,
|
|
@@ -611,10 +643,11 @@ async function buildDocumentMap(
|
|
|
611
643
|
}
|
|
612
644
|
|
|
613
645
|
for (const doc of activeDocs) {
|
|
646
|
+
const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
|
|
614
647
|
if (
|
|
615
648
|
options.relPathPrefix !== undefined &&
|
|
616
|
-
|
|
617
|
-
!
|
|
649
|
+
sourceRelPath !== options.relPathPrefix &&
|
|
650
|
+
!sourceRelPath.startsWith(`${options.relPathPrefix}/`)
|
|
618
651
|
) {
|
|
619
652
|
continue;
|
|
620
653
|
}
|
|
@@ -655,11 +688,17 @@ async function buildDocumentMap(
|
|
|
655
688
|
mirrorHash: doc.mirrorHash,
|
|
656
689
|
converterId: doc.converterId,
|
|
657
690
|
converterVersion: doc.converterVersion,
|
|
691
|
+
recordKey: doc.recordKey,
|
|
692
|
+
recordSourcePath: doc.recordSourcePath,
|
|
693
|
+
recordSourceLocator: doc.recordSourceLocator,
|
|
694
|
+
recordMetadata: doc.recordMetadata,
|
|
695
|
+
recordAnchors: doc.recordAnchors,
|
|
696
|
+
recordAdapterFingerprint: doc.recordAdapterFingerprint,
|
|
658
697
|
};
|
|
659
|
-
const matchingDocuments =
|
|
698
|
+
const matchingDocuments = documents.get(doc.mirrorHash!) ?? [];
|
|
660
699
|
matchingDocuments.push(documentInfo);
|
|
661
|
-
|
|
700
|
+
documents.set(doc.mirrorHash!, matchingDocuments);
|
|
662
701
|
}
|
|
663
702
|
|
|
664
|
-
return
|
|
703
|
+
return { documents, ownershipDocuments };
|
|
665
704
|
}
|