@gmickel/gno 1.28.0 → 1.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -20
- package/assets/skill/SKILL.md +33 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.29.1.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.29.1.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 +1 -1
- package/spec/cli.md +41 -1
- package/spec/db/schema.sql +19 -0
- package/spec/mcp.md +66 -0
- package/spec/output-schemas/ask.schema.json +31 -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 +89 -2
- 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/mcp-http-error.schema.json +113 -2
- package/spec/output-schemas/publish-artifact.schema.json +32 -0
- package/spec/output-schemas/record-import.schema.json +30 -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-results.schema.json +30 -0
- 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/query.ts +12 -0
- package/src/cli/commands/replay.ts +13 -3
- package/src/cli/commands/vsearch.ts +3 -1
- package/src/cli/program.ts +123 -0
- package/src/config/index.ts +9 -0
- package/src/config/types.ts +50 -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 +111 -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 -132
- package/src/core/context-evidence.ts +86 -14
- package/src/core/destination-classifier.ts +402 -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/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.ts +12 -1
- package/src/ingestion/record-container.ts +38 -22
- package/src/ingestion/sync.ts +15 -0
- package/src/ingestion/types.ts +2 -0
- 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/index-cmd.ts +7 -0
- package/src/mcp/tools/index.ts +98 -0
- 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/pipeline/egress-lineage.ts +124 -0
- package/src/pipeline/hybrid.ts +14 -0
- package/src/pipeline/search.ts +12 -0
- package/src/pipeline/types.ts +3 -0
- package/src/pipeline/vsearch.ts +41 -20
- 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/embed.ts +1 -0
- package/src/sdk/types.ts +33 -0
- 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 +174 -0
- 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/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 +6 -0
- package/src/store/retrieval-trace-codec.ts +15 -0
- package/src/store/sqlite/adapter.ts +170 -7
- 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 +126 -1
- package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.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/hybrid.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
sortByFinalScoreStable,
|
|
33
33
|
} from "./content-type-boost";
|
|
34
34
|
import { formatQueryForEmbedding } from "./contextual";
|
|
35
|
+
import { attachSearchResultEgressLineage } from "./egress-lineage";
|
|
35
36
|
import { expandQuery } from "./expansion";
|
|
36
37
|
import {
|
|
37
38
|
buildExplainResults,
|
|
@@ -1169,6 +1170,19 @@ export async function searchHybrid(
|
|
|
1169
1170
|
}
|
|
1170
1171
|
: undefined;
|
|
1171
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
|
+
}
|
|
1172
1186
|
|
|
1173
1187
|
const output: SearchResults = {
|
|
1174
1188
|
results: finalResults,
|
package/src/pipeline/search.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
hasAuxiliaryRanking,
|
|
25
25
|
sortByFinalScoreStable,
|
|
26
26
|
} from "./content-type-boost";
|
|
27
|
+
import { attachSearchResultEgressLineage } from "./egress-lineage";
|
|
27
28
|
import { matchesExcludedChunks, matchesExcludedText } from "./exclude";
|
|
28
29
|
import { selectBestChunkForSteering } from "./intent";
|
|
29
30
|
import { hasProjectAffinity } from "./project-affinity";
|
|
@@ -433,6 +434,17 @@ export async function searchBm25(
|
|
|
433
434
|
if (metadata) metadata.retrievalRank = index + 1;
|
|
434
435
|
}
|
|
435
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
|
+
}
|
|
436
448
|
|
|
437
449
|
const output: SearchResults = {
|
|
438
450
|
results: finalResults,
|
package/src/pipeline/types.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
ContextCapsuleV1,
|
|
11
11
|
ContextCapsuleVerification,
|
|
12
12
|
} from "../core/context-capsule";
|
|
13
|
+
import type { EgressLineage } from "../core/egress-provenance";
|
|
13
14
|
import type { RecordEvidenceMetadata } from "../core/record-metadata";
|
|
14
15
|
import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
|
|
15
16
|
import type { StoreResult } from "../store/types";
|
|
@@ -89,6 +90,8 @@ export interface SearchResult {
|
|
|
89
90
|
conversion?: SearchResultConversion;
|
|
90
91
|
/** Bounded logical-record provenance for file/export adapters. */
|
|
91
92
|
record?: RecordEvidenceMetadata;
|
|
93
|
+
/** Complete, most-restrictive collection ownership for this derived result. */
|
|
94
|
+
egressLineage?: EgressLineage;
|
|
92
95
|
[SEARCH_RESULT_PLANNER_METADATA]?: SearchResultPlannerMetadata;
|
|
93
96
|
}
|
|
94
97
|
|
package/src/pipeline/vsearch.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
sortByFinalScoreStable,
|
|
23
23
|
} from "./content-type-boost";
|
|
24
24
|
import { formatQueryForEmbedding } from "./contextual";
|
|
25
|
+
import { attachSearchResultEgressLineage } from "./egress-lineage";
|
|
25
26
|
import { matchesExcludedChunks, matchesExcludedText } from "./exclude";
|
|
26
27
|
import { selectBestChunkForSteering } from "./intent";
|
|
27
28
|
import { hasProjectAffinity } from "./project-affinity";
|
|
@@ -143,17 +144,18 @@ export async function searchVectorWithEmbedding(
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
// Cache docs to avoid N+1 queries (filtered by collection and tags)
|
|
146
|
-
const docsByMirrorHash
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
});
|
|
157
159
|
|
|
158
160
|
// Pre-fetch all chunks in one batch query (eliminates N+1)
|
|
159
161
|
const chunksMapResult = await store.getChunksBatch(uniqueHashes);
|
|
@@ -423,6 +425,18 @@ export async function searchVectorWithEmbedding(
|
|
|
423
425
|
if (metadata) metadata.retrievalRank = index + 1;
|
|
424
426
|
}
|
|
425
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
|
+
}
|
|
426
440
|
|
|
427
441
|
const output: SearchResults = {
|
|
428
442
|
results: finalResults,
|
|
@@ -560,28 +574,35 @@ function matchesCategoryFilter(
|
|
|
560
574
|
async function buildDocumentMap(
|
|
561
575
|
store: StorePort,
|
|
562
576
|
options: DocumentMapOptions = {}
|
|
563
|
-
): Promise<
|
|
564
|
-
|
|
577
|
+
): Promise<{
|
|
578
|
+
documents: Map<string, DocumentInfo[]>;
|
|
579
|
+
ownershipDocuments: DocumentRow[];
|
|
580
|
+
}> {
|
|
581
|
+
const documents = new Map<string, DocumentInfo[]>();
|
|
565
582
|
|
|
566
583
|
if (options.mirrorHashes && options.mirrorHashes.length === 0) {
|
|
567
|
-
return
|
|
584
|
+
return { documents, ownershipDocuments: [] };
|
|
568
585
|
}
|
|
569
586
|
|
|
570
587
|
const docs = options.mirrorHashes
|
|
571
588
|
? await store.getDocumentsByMirrorHashes(options.mirrorHashes, {
|
|
572
|
-
collection: options.collection,
|
|
573
589
|
activeOnly: true,
|
|
574
590
|
})
|
|
575
591
|
: await store.listDocuments(options.collection);
|
|
576
592
|
if (!docs.ok) {
|
|
577
|
-
return
|
|
593
|
+
return { documents, ownershipDocuments: [] };
|
|
578
594
|
}
|
|
579
595
|
|
|
580
596
|
// Filter docs with mirrorHash.
|
|
581
597
|
// listDocuments path still needs explicit active filter.
|
|
582
|
-
const
|
|
598
|
+
const ownershipDocuments = options.mirrorHashes
|
|
583
599
|
? docs.value.filter((d) => d.mirrorHash)
|
|
584
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;
|
|
585
606
|
const temporalRange: TemporalRange = {
|
|
586
607
|
since: options.since,
|
|
587
608
|
until: options.until,
|
|
@@ -674,10 +695,10 @@ async function buildDocumentMap(
|
|
|
674
695
|
recordAnchors: doc.recordAnchors,
|
|
675
696
|
recordAdapterFingerprint: doc.recordAdapterFingerprint,
|
|
676
697
|
};
|
|
677
|
-
const matchingDocuments =
|
|
698
|
+
const matchingDocuments = documents.get(doc.mirrorHash!) ?? [];
|
|
678
699
|
matchingDocuments.push(documentInfo);
|
|
679
|
-
|
|
700
|
+
documents.set(doc.mirrorHash!, matchingDocuments);
|
|
680
701
|
}
|
|
681
702
|
|
|
682
|
-
return
|
|
703
|
+
return { documents, ownershipDocuments };
|
|
683
704
|
}
|
package/src/publish/artifact.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* @module src/publish/artifact
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type { EgressLineage } from "../core/egress-provenance";
|
|
7
8
|
import type { DocumentRow } from "../store/types";
|
|
8
9
|
|
|
9
10
|
import { deriveDocid } from "../app/constants";
|
|
@@ -11,6 +12,10 @@ import {
|
|
|
11
12
|
contextCapsuleEvidenceIdentity,
|
|
12
13
|
sha256Text,
|
|
13
14
|
} from "../core/context-capsule-validation";
|
|
15
|
+
import {
|
|
16
|
+
egressLineageSchema,
|
|
17
|
+
legacyLocalOnlyEgressLineage,
|
|
18
|
+
} from "../core/egress-provenance";
|
|
14
19
|
import { stripFrontmatter } from "../ingestion/frontmatter";
|
|
15
20
|
import {
|
|
16
21
|
MAX_PUBLISH_SLUG_LENGTH,
|
|
@@ -71,6 +76,7 @@ export interface PublicPublishDocument {
|
|
|
71
76
|
export interface PublicPublishManifest {
|
|
72
77
|
capabilities: typeof PUBLIC_PUBLISH_CAPABILITIES;
|
|
73
78
|
documents: PublicPublishDocument[];
|
|
79
|
+
egressLineage: EgressLineage;
|
|
74
80
|
generatedAt: string;
|
|
75
81
|
projectionRevision: string;
|
|
76
82
|
schemaVersion: typeof PUBLIC_PUBLISH_MANIFEST_SCHEMA_VERSION;
|
|
@@ -116,6 +122,7 @@ export interface EncryptedPublishArtifactSpace {
|
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
export interface PublishArtifactV1 {
|
|
125
|
+
egressLineage: EgressLineage;
|
|
119
126
|
exportedAt: string;
|
|
120
127
|
source: string;
|
|
121
128
|
spaces: PublishArtifactSpace[];
|
|
@@ -123,6 +130,7 @@ export interface PublishArtifactV1 {
|
|
|
123
130
|
}
|
|
124
131
|
|
|
125
132
|
export interface PublishArtifactV2 {
|
|
133
|
+
egressLineage: EgressLineage;
|
|
126
134
|
exportedAt: string;
|
|
127
135
|
source: string;
|
|
128
136
|
spaces: EncryptedPublishArtifactSpace[];
|
|
@@ -279,6 +287,7 @@ const buildPublicPublishDocument = (
|
|
|
279
287
|
};
|
|
280
288
|
|
|
281
289
|
export const buildPublicPublishManifest = (input: {
|
|
290
|
+
egressLineage?: EgressLineage;
|
|
282
291
|
exportedAt: string;
|
|
283
292
|
homeNoteSlug?: string;
|
|
284
293
|
notes: PublishArtifactNote[];
|
|
@@ -288,7 +297,11 @@ export const buildPublicPublishManifest = (input: {
|
|
|
288
297
|
title: string;
|
|
289
298
|
visibility: "public";
|
|
290
299
|
}): PublicPublishManifest => {
|
|
291
|
-
const
|
|
300
|
+
const { egressLineage: providedLineage, ...spaceInput } = input;
|
|
301
|
+
const validated = validateAndProjectPublishSpaceInput(spaceInput);
|
|
302
|
+
const egressLineage = egressLineageSchema.parse(
|
|
303
|
+
providedLineage ?? legacyLocalOnlyEgressLineage("legacy")
|
|
304
|
+
);
|
|
292
305
|
if (validated.visibility !== "public") {
|
|
293
306
|
throw new Error("Public publish manifests require public visibility");
|
|
294
307
|
}
|
|
@@ -304,6 +317,7 @@ export const buildPublicPublishManifest = (input: {
|
|
|
304
317
|
const revisionProjection = {
|
|
305
318
|
capabilities: PUBLIC_PUBLISH_CAPABILITIES,
|
|
306
319
|
documents,
|
|
320
|
+
egressLineage,
|
|
307
321
|
homeNoteSlug: validated.homeNoteSlug ?? null,
|
|
308
322
|
notes: validated.notes
|
|
309
323
|
.map((note) => ({
|
|
@@ -324,6 +338,7 @@ export const buildPublicPublishManifest = (input: {
|
|
|
324
338
|
return {
|
|
325
339
|
capabilities: { ...PUBLIC_PUBLISH_CAPABILITIES },
|
|
326
340
|
documents,
|
|
341
|
+
egressLineage,
|
|
327
342
|
generatedAt: exportedAt,
|
|
328
343
|
projectionRevision: sha256Text(canonicalJson(revisionProjection)),
|
|
329
344
|
schemaVersion: PUBLIC_PUBLISH_MANIFEST_SCHEMA_VERSION,
|
|
@@ -332,6 +347,7 @@ export const buildPublicPublishManifest = (input: {
|
|
|
332
347
|
};
|
|
333
348
|
|
|
334
349
|
export const buildPublishArtifact = (input: {
|
|
350
|
+
egressLineage?: EgressLineage;
|
|
335
351
|
homeNoteSlug?: string;
|
|
336
352
|
notes: PublishArtifactNote[];
|
|
337
353
|
routeSlug: string;
|
|
@@ -340,7 +356,11 @@ export const buildPublishArtifact = (input: {
|
|
|
340
356
|
title: string;
|
|
341
357
|
visibility: Exclude<PublishVisibility, "encrypted">;
|
|
342
358
|
}): PublishArtifactV1 => {
|
|
343
|
-
const
|
|
359
|
+
const { egressLineage: providedLineage, ...spaceInput } = input;
|
|
360
|
+
const validated = validateAndProjectPublishSpaceInput(spaceInput);
|
|
361
|
+
const egressLineage = egressLineageSchema.parse(
|
|
362
|
+
providedLineage ?? legacyLocalOnlyEgressLineage("legacy")
|
|
363
|
+
);
|
|
344
364
|
const exportedAt = new Date().toISOString();
|
|
345
365
|
const base = {
|
|
346
366
|
...(validated.homeNoteSlug === undefined
|
|
@@ -357,6 +377,7 @@ export const buildPublishArtifact = (input: {
|
|
|
357
377
|
? {
|
|
358
378
|
...base,
|
|
359
379
|
manifest: buildPublicPublishManifest({
|
|
380
|
+
egressLineage,
|
|
360
381
|
exportedAt,
|
|
361
382
|
homeNoteSlug: validated.homeNoteSlug,
|
|
362
383
|
notes: validated.notes,
|
|
@@ -374,6 +395,7 @@ export const buildPublishArtifact = (input: {
|
|
|
374
395
|
};
|
|
375
396
|
|
|
376
397
|
return {
|
|
398
|
+
egressLineage,
|
|
377
399
|
exportedAt,
|
|
378
400
|
source: validated.routeSlug,
|
|
379
401
|
spaces: [space],
|
|
@@ -382,14 +404,20 @@ export const buildPublishArtifact = (input: {
|
|
|
382
404
|
};
|
|
383
405
|
|
|
384
406
|
export const buildEncryptedPublishArtifact = (input: {
|
|
407
|
+
egressLineage?: EgressLineage;
|
|
385
408
|
encryptedPayload: EncryptedArtifactPayload;
|
|
386
409
|
routeSlug: string;
|
|
387
410
|
secretToken: string;
|
|
388
411
|
sourceType: "note" | "collection";
|
|
389
412
|
}): PublishArtifactV2 => {
|
|
390
|
-
const
|
|
413
|
+
const { egressLineage: providedLineage, ...encryptedInput } = input;
|
|
414
|
+
const validated = validateAndProjectEncryptedPublishInput(encryptedInput);
|
|
415
|
+
const egressLineage = egressLineageSchema.parse(
|
|
416
|
+
providedLineage ?? legacyLocalOnlyEgressLineage("legacy")
|
|
417
|
+
);
|
|
391
418
|
const exportedAt = requirePublishDateTime(new Date().toISOString());
|
|
392
419
|
return {
|
|
420
|
+
egressLineage,
|
|
393
421
|
exportedAt,
|
|
394
422
|
source: validated.routeSlug,
|
|
395
423
|
spaces: [
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { Collection } from "../config/types";
|
|
8
8
|
import type { DocumentRow, StorePort, TagRow } from "../store/types";
|
|
9
9
|
|
|
10
|
+
import { enforceCollectionEgressWithAudit } from "../core/egress-enforcement";
|
|
10
11
|
import { parseRef } from "../core/ref-parser";
|
|
11
12
|
import { parseFrontmatter, stripFrontmatter } from "../ingestion/frontmatter";
|
|
12
13
|
import { getContentBatch } from "../store/content-batch";
|
|
@@ -217,6 +218,15 @@ async function exportCollectionArtifact(
|
|
|
217
218
|
target,
|
|
218
219
|
]);
|
|
219
220
|
const visibility = resolveVisibility(options.visibility);
|
|
221
|
+
const { lineage } = await enforceCollectionEgressWithAudit({
|
|
222
|
+
collections,
|
|
223
|
+
collectionNames: [collection.name],
|
|
224
|
+
action: "export",
|
|
225
|
+
destinationZone: "local_process",
|
|
226
|
+
caller: { authenticated: true, operationAuthorized: true },
|
|
227
|
+
contentClass: "source",
|
|
228
|
+
store,
|
|
229
|
+
});
|
|
220
230
|
|
|
221
231
|
if (visibility === "encrypted") {
|
|
222
232
|
if (!options.encryptionPassphrase) {
|
|
@@ -237,6 +247,7 @@ async function exportCollectionArtifact(
|
|
|
237
247
|
});
|
|
238
248
|
|
|
239
249
|
return buildEncryptedPublishArtifact({
|
|
250
|
+
egressLineage: lineage,
|
|
240
251
|
encryptedPayload: encrypted.encryptedPayload,
|
|
241
252
|
routeSlug,
|
|
242
253
|
secretToken: encrypted.secretToken,
|
|
@@ -245,6 +256,7 @@ async function exportCollectionArtifact(
|
|
|
245
256
|
}
|
|
246
257
|
|
|
247
258
|
return buildPublishArtifact({
|
|
259
|
+
egressLineage: lineage,
|
|
248
260
|
homeNoteSlug: chooseHomeNoteSlug(notes),
|
|
249
261
|
notes,
|
|
250
262
|
routeSlug,
|
|
@@ -257,6 +269,7 @@ async function exportCollectionArtifact(
|
|
|
257
269
|
|
|
258
270
|
async function exportDocumentArtifact(
|
|
259
271
|
store: StorePort,
|
|
272
|
+
collections: Collection[],
|
|
260
273
|
target: string,
|
|
261
274
|
options: PublishExportCoreOptions,
|
|
262
275
|
warnings: SanitizeWarning[]
|
|
@@ -283,6 +296,15 @@ async function exportDocumentArtifact(
|
|
|
283
296
|
const slug = deriveExportedSlug(doc);
|
|
284
297
|
const visibility = resolveVisibility(options.visibility);
|
|
285
298
|
const routeSlug = derivePublishSlug([options.routeSlug ?? "", slug, target]);
|
|
299
|
+
const { lineage } = await enforceCollectionEgressWithAudit({
|
|
300
|
+
collections,
|
|
301
|
+
collectionNames: [doc.collection],
|
|
302
|
+
action: "export",
|
|
303
|
+
destinationZone: "local_process",
|
|
304
|
+
caller: { authenticated: true, operationAuthorized: true },
|
|
305
|
+
contentClass: "source",
|
|
306
|
+
store,
|
|
307
|
+
});
|
|
286
308
|
|
|
287
309
|
if (visibility === "encrypted") {
|
|
288
310
|
if (!options.encryptionPassphrase) {
|
|
@@ -310,6 +332,7 @@ async function exportDocumentArtifact(
|
|
|
310
332
|
});
|
|
311
333
|
|
|
312
334
|
return buildEncryptedPublishArtifact({
|
|
335
|
+
egressLineage: lineage,
|
|
313
336
|
encryptedPayload: encrypted.encryptedPayload,
|
|
314
337
|
routeSlug,
|
|
315
338
|
secretToken: encrypted.secretToken,
|
|
@@ -318,6 +341,7 @@ async function exportDocumentArtifact(
|
|
|
318
341
|
}
|
|
319
342
|
|
|
320
343
|
return buildPublishArtifact({
|
|
344
|
+
egressLineage: lineage,
|
|
321
345
|
notes: [
|
|
322
346
|
{
|
|
323
347
|
markdown,
|
|
@@ -357,6 +381,7 @@ export async function exportPublishArtifact(input: {
|
|
|
357
381
|
)) ??
|
|
358
382
|
(await exportDocumentArtifact(
|
|
359
383
|
input.store,
|
|
384
|
+
input.collections,
|
|
360
385
|
input.target,
|
|
361
386
|
input.options,
|
|
362
387
|
warnings
|