@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
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
/** Loopback REST handlers for private retrieval trace management. */
|
|
2
2
|
|
|
3
|
+
import type { Collection } from "../../config/types";
|
|
3
4
|
import type {
|
|
4
5
|
RetrievalTraceExportRequest,
|
|
5
6
|
RetrievalTraceLabelRequest,
|
|
6
7
|
} from "../../core/retrieval-trace-management";
|
|
7
8
|
import type { StoreError, StorePort, StoreResult } from "../../store/types";
|
|
8
9
|
|
|
10
|
+
import { authorizeCurrentEgress } from "../../core/egress-authorization";
|
|
9
11
|
import { RetrievalTraceManagementService } from "../../core/retrieval-trace-management";
|
|
10
12
|
|
|
11
13
|
const errorStatus = (error: StoreError): number => {
|
|
12
14
|
if (error.code === "NOT_FOUND") return 404;
|
|
13
15
|
if (error.code === "CONSTRAINT_VIOLATION") return 409;
|
|
14
16
|
if (error.code === "INVALID_INPUT") return 400;
|
|
17
|
+
if (error.code === "EGRESS_DENIED") return 403;
|
|
15
18
|
return 500;
|
|
16
19
|
};
|
|
17
20
|
|
|
@@ -135,14 +138,34 @@ export const handleTraceLabel = async (
|
|
|
135
138
|
|
|
136
139
|
export const handleTraceExport = async (
|
|
137
140
|
store: StorePort,
|
|
138
|
-
request: Request
|
|
141
|
+
request: Request,
|
|
142
|
+
collections: readonly Collection[] = []
|
|
139
143
|
): Promise<Response> => {
|
|
140
144
|
const body = await parseJsonObject(request);
|
|
141
145
|
if (body instanceof Response) return body;
|
|
142
146
|
return response(
|
|
143
|
-
await new RetrievalTraceManagementService(store
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
await new RetrievalTraceManagementService(store, {
|
|
148
|
+
authorizeExport: async (lineage) => {
|
|
149
|
+
const hostname = new URL(request.url).hostname;
|
|
150
|
+
const destinationZone = ["localhost", "127.0.0.1", "::1"].includes(
|
|
151
|
+
hostname
|
|
152
|
+
)
|
|
153
|
+
? "local_process"
|
|
154
|
+
: "remote";
|
|
155
|
+
return authorizeCurrentEgress({
|
|
156
|
+
store,
|
|
157
|
+
config: { collections },
|
|
158
|
+
lineage,
|
|
159
|
+
action: "export",
|
|
160
|
+
destinationZone,
|
|
161
|
+
caller: {
|
|
162
|
+
authenticated: true,
|
|
163
|
+
operationAuthorized: true,
|
|
164
|
+
},
|
|
165
|
+
contentClass: "retrieval_trace",
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
}).export(body as unknown as RetrievalTraceExportRequest)
|
|
146
169
|
);
|
|
147
170
|
};
|
|
148
171
|
|
package/src/serve/server.ts
CHANGED
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
handleBrowseTree,
|
|
27
27
|
handleCapabilities,
|
|
28
28
|
handleClearCollectionEmbeddings,
|
|
29
|
+
handleCollectionEgressCheck,
|
|
30
|
+
handleCollectionEgressPolicy,
|
|
29
31
|
handleCollections,
|
|
30
32
|
handleConnectors,
|
|
31
33
|
handleCreateFolder,
|
|
@@ -43,6 +45,11 @@ import {
|
|
|
43
45
|
handleDuplicateDoc,
|
|
44
46
|
handleEmbed,
|
|
45
47
|
handleEmbedStatus,
|
|
48
|
+
handleEgressAuditDelete,
|
|
49
|
+
handleEgressAuditList,
|
|
50
|
+
handleEgressAuditPurge,
|
|
51
|
+
handleEgressAuditShow,
|
|
52
|
+
handleEgressAuditStatus,
|
|
46
53
|
handleHealth,
|
|
47
54
|
handleImportPreview,
|
|
48
55
|
handleInstallConnector,
|
|
@@ -66,6 +73,7 @@ import {
|
|
|
66
73
|
handleTags,
|
|
67
74
|
handleTrashDoc,
|
|
68
75
|
handleUpdateCollection,
|
|
76
|
+
handleUpdateCollectionEgressPolicy,
|
|
69
77
|
handleUpdateDoc,
|
|
70
78
|
handleVerifyConnector,
|
|
71
79
|
} from "./routes/api";
|
|
@@ -304,7 +312,7 @@ export async function startServer(
|
|
|
304
312
|
}
|
|
305
313
|
return withSecurityHeaders(
|
|
306
314
|
await handleResidentRead(runtime as ResidentRuntime, req, () =>
|
|
307
|
-
handleTraceExport(store, req)
|
|
315
|
+
handleTraceExport(store, req, ctxHolder.config.collections)
|
|
308
316
|
),
|
|
309
317
|
isDev
|
|
310
318
|
);
|
|
@@ -398,6 +406,53 @@ export async function startServer(
|
|
|
398
406
|
);
|
|
399
407
|
},
|
|
400
408
|
},
|
|
409
|
+
"/api/egress/check": {
|
|
410
|
+
POST: async (req: Request) =>
|
|
411
|
+
withSecurityHeaders(
|
|
412
|
+
await handleCollectionEgressCheck(ctxHolder, req),
|
|
413
|
+
isDev
|
|
414
|
+
),
|
|
415
|
+
},
|
|
416
|
+
"/api/egress/audits/status": {
|
|
417
|
+
GET: async () =>
|
|
418
|
+
withSecurityHeaders(await handleEgressAuditStatus(store), isDev),
|
|
419
|
+
},
|
|
420
|
+
"/api/egress/audits": {
|
|
421
|
+
GET: async (req: Request) =>
|
|
422
|
+
withSecurityHeaders(await handleEgressAuditList(store, req), isDev),
|
|
423
|
+
DELETE: async (req: Request) => {
|
|
424
|
+
if (!isRequestAllowed(req, port)) {
|
|
425
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
426
|
+
}
|
|
427
|
+
return withSecurityHeaders(
|
|
428
|
+
await handleEgressAuditPurge(store),
|
|
429
|
+
isDev
|
|
430
|
+
);
|
|
431
|
+
},
|
|
432
|
+
},
|
|
433
|
+
"/api/egress/audits/:auditId": {
|
|
434
|
+
GET: async (req: Request) => {
|
|
435
|
+
const auditId = decodeURIComponent(
|
|
436
|
+
new URL(req.url).pathname.split("/").at(-1) ?? ""
|
|
437
|
+
);
|
|
438
|
+
return withSecurityHeaders(
|
|
439
|
+
await handleEgressAuditShow(store, auditId),
|
|
440
|
+
isDev
|
|
441
|
+
);
|
|
442
|
+
},
|
|
443
|
+
DELETE: async (req: Request) => {
|
|
444
|
+
if (!isRequestAllowed(req, port)) {
|
|
445
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
446
|
+
}
|
|
447
|
+
const auditId = decodeURIComponent(
|
|
448
|
+
new URL(req.url).pathname.split("/").at(-1) ?? ""
|
|
449
|
+
);
|
|
450
|
+
return withSecurityHeaders(
|
|
451
|
+
await handleEgressAuditDelete(store, auditId),
|
|
452
|
+
isDev
|
|
453
|
+
);
|
|
454
|
+
},
|
|
455
|
+
},
|
|
401
456
|
"/api/connectors": {
|
|
402
457
|
GET: async (req: Request) =>
|
|
403
458
|
withSecurityHeaders(
|
|
@@ -707,12 +762,42 @@ export async function startServer(
|
|
|
707
762
|
},
|
|
708
763
|
},
|
|
709
764
|
"/api/events": {
|
|
710
|
-
GET: () =>
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
765
|
+
GET: (req: Request) => {
|
|
766
|
+
const residentRuntime = runtime as ResidentRuntime;
|
|
767
|
+
const admitted = residentRuntime.admitRequest(req.signal);
|
|
768
|
+
if (!admitted || !residentRuntime.eventBus) {
|
|
769
|
+
admitted?.finish();
|
|
770
|
+
return withSecurityHeaders(
|
|
771
|
+
Response.json(
|
|
772
|
+
{
|
|
773
|
+
error: {
|
|
774
|
+
code: "UNAVAILABLE",
|
|
775
|
+
message: "Document event stream unavailable",
|
|
776
|
+
},
|
|
777
|
+
},
|
|
778
|
+
{ status: 503 }
|
|
779
|
+
),
|
|
780
|
+
isDev
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
try {
|
|
784
|
+
return withSecurityHeaders(
|
|
785
|
+
residentRuntime.eventBus.createResponse({
|
|
786
|
+
authorizationEpoch:
|
|
787
|
+
admitted.authorizationEpoch ??
|
|
788
|
+
residentRuntime.authorizationEpoch,
|
|
789
|
+
isAuthorizationEpochCurrent: () =>
|
|
790
|
+
admitted.isAuthorizationEpochCurrent?.() ?? true,
|
|
791
|
+
onClose: () => admitted.finish(),
|
|
792
|
+
signal: admitted.signal,
|
|
793
|
+
}),
|
|
794
|
+
isDev
|
|
795
|
+
);
|
|
796
|
+
} catch (error) {
|
|
797
|
+
admitted.finish();
|
|
798
|
+
throw error;
|
|
799
|
+
}
|
|
800
|
+
},
|
|
716
801
|
},
|
|
717
802
|
"/api/tags": {
|
|
718
803
|
GET: async (req: Request) => {
|
|
@@ -893,6 +978,32 @@ export async function startServer(
|
|
|
893
978
|
);
|
|
894
979
|
},
|
|
895
980
|
},
|
|
981
|
+
"/api/collections/:name/egress-policy": {
|
|
982
|
+
GET: (req: Request) => {
|
|
983
|
+
const parts = new URL(req.url).pathname.split("/");
|
|
984
|
+
const name = decodeURIComponent(parts.at(-2) ?? "");
|
|
985
|
+
return withSecurityHeaders(
|
|
986
|
+
handleCollectionEgressPolicy(ctxHolder, name),
|
|
987
|
+
isDev
|
|
988
|
+
);
|
|
989
|
+
},
|
|
990
|
+
PUT: async (req: Request) => {
|
|
991
|
+
if (!isRequestAllowed(req, port)) {
|
|
992
|
+
return withSecurityHeaders(forbiddenResponse(), isDev);
|
|
993
|
+
}
|
|
994
|
+
const parts = new URL(req.url).pathname.split("/");
|
|
995
|
+
const name = decodeURIComponent(parts.at(-2) ?? "");
|
|
996
|
+
return withSecurityHeaders(
|
|
997
|
+
await handleUpdateCollectionEgressPolicy(
|
|
998
|
+
ctxHolder,
|
|
999
|
+
store,
|
|
1000
|
+
name,
|
|
1001
|
+
req
|
|
1002
|
+
),
|
|
1003
|
+
isDev
|
|
1004
|
+
);
|
|
1005
|
+
},
|
|
1006
|
+
},
|
|
896
1007
|
"/api/collections/:name/embeddings/clear": {
|
|
897
1008
|
POST: async (req: Request) => {
|
|
898
1009
|
if (!isRequestAllowed(req, port)) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add fail-closed collection egress policy state.
|
|
3
|
+
*
|
|
4
|
+
* Existing rows become local-only with legacy_default provenance. Config sync
|
|
5
|
+
* can later replace that value with config_default or an explicit user choice
|
|
6
|
+
* without affecting local retrieval/indexing.
|
|
7
|
+
*/
|
|
8
|
+
import type { Migration } from "./runner";
|
|
9
|
+
|
|
10
|
+
export const migration: Migration = {
|
|
11
|
+
version: 23,
|
|
12
|
+
name: "collection_egress_policy",
|
|
13
|
+
|
|
14
|
+
up(db): void {
|
|
15
|
+
const columns = new Set(
|
|
16
|
+
db
|
|
17
|
+
.query<{ name: string }, []>("PRAGMA table_info(collections)")
|
|
18
|
+
.all()
|
|
19
|
+
.map((row) => row.name)
|
|
20
|
+
);
|
|
21
|
+
if (!columns.has("egress_policy")) {
|
|
22
|
+
db.exec(`
|
|
23
|
+
ALTER TABLE collections
|
|
24
|
+
ADD COLUMN egress_policy TEXT NOT NULL DEFAULT 'local_only'
|
|
25
|
+
CHECK (egress_policy IN ('local_only', 'lan', 'remote'))
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
if (!columns.has("egress_policy_source")) {
|
|
29
|
+
db.exec(`
|
|
30
|
+
ALTER TABLE collections
|
|
31
|
+
ADD COLUMN egress_policy_source TEXT NOT NULL DEFAULT 'legacy_default'
|
|
32
|
+
CHECK (
|
|
33
|
+
egress_policy_source IN (
|
|
34
|
+
'explicit',
|
|
35
|
+
'config_default',
|
|
36
|
+
'legacy_default'
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
CHECK (
|
|
40
|
+
egress_policy_source = 'explicit'
|
|
41
|
+
OR egress_policy = 'local_only'
|
|
42
|
+
)
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
down(db): void {
|
|
48
|
+
const columns = new Set(
|
|
49
|
+
db
|
|
50
|
+
.query<{ name: string }, []>("PRAGMA table_info(collections)")
|
|
51
|
+
.all()
|
|
52
|
+
.map((row) => row.name)
|
|
53
|
+
);
|
|
54
|
+
if (columns.has("egress_policy_source")) {
|
|
55
|
+
db.exec("ALTER TABLE collections DROP COLUMN egress_policy_source");
|
|
56
|
+
}
|
|
57
|
+
if (columns.has("egress_policy")) {
|
|
58
|
+
db.exec("ALTER TABLE collections DROP COLUMN egress_policy");
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/** Persist derived egress lineage and bounded redacted audit receipts. */
|
|
2
|
+
|
|
3
|
+
import type { Database } from "bun:sqlite";
|
|
4
|
+
|
|
5
|
+
import type { Migration } from "./runner";
|
|
6
|
+
|
|
7
|
+
const LEGACY_LINEAGE_JSON =
|
|
8
|
+
'{"digest":"87b249b76459c91c172da6be6cbe3f93b61c44869eb196f3470ec36ebb50b8b0","effectivePolicy":"local_only","sources":[{"collection":"legacy","policy":"local_only","source":"legacy_default"}]}';
|
|
9
|
+
const LEGACY_LINEAGE_DIGEST =
|
|
10
|
+
"87b249b76459c91c172da6be6cbe3f93b61c44869eb196f3470ec36ebb50b8b0";
|
|
11
|
+
|
|
12
|
+
export const migration: Migration = {
|
|
13
|
+
version: 24,
|
|
14
|
+
name: "egress_derived_lineage",
|
|
15
|
+
|
|
16
|
+
up(db: Database): void {
|
|
17
|
+
const lineageBytes = new TextEncoder().encode(
|
|
18
|
+
LEGACY_LINEAGE_JSON
|
|
19
|
+
).byteLength;
|
|
20
|
+
db.exec(`
|
|
21
|
+
ALTER TABLE retrieval_traces
|
|
22
|
+
ADD COLUMN effective_egress_policy TEXT NOT NULL DEFAULT 'local_only'
|
|
23
|
+
CHECK (effective_egress_policy IN ('local_only', 'lan', 'remote'));
|
|
24
|
+
ALTER TABLE retrieval_traces
|
|
25
|
+
ADD COLUMN egress_lineage_digest TEXT NOT NULL DEFAULT '${LEGACY_LINEAGE_DIGEST}'
|
|
26
|
+
CHECK (length(egress_lineage_digest) = 64 AND egress_lineage_digest NOT GLOB '*[^0-9a-f]*');
|
|
27
|
+
ALTER TABLE retrieval_traces
|
|
28
|
+
ADD COLUMN egress_lineage_json TEXT NOT NULL DEFAULT '${LEGACY_LINEAGE_JSON}';
|
|
29
|
+
ALTER TABLE retrieval_traces
|
|
30
|
+
ADD COLUMN egress_lineage_bytes INTEGER NOT NULL DEFAULT ${lineageBytes}
|
|
31
|
+
CHECK (egress_lineage_bytes > 0 AND egress_lineage_bytes <= 32768);
|
|
32
|
+
ALTER TABLE retrieval_traces
|
|
33
|
+
ADD COLUMN creation_digest_version INTEGER NOT NULL DEFAULT 0
|
|
34
|
+
CHECK (creation_digest_version IN (0, 1));
|
|
35
|
+
|
|
36
|
+
ALTER TABLE retrieval_trace_exports
|
|
37
|
+
ADD COLUMN effective_egress_policy TEXT NOT NULL DEFAULT 'local_only'
|
|
38
|
+
CHECK (effective_egress_policy IN ('local_only', 'lan', 'remote'));
|
|
39
|
+
ALTER TABLE retrieval_trace_exports
|
|
40
|
+
ADD COLUMN egress_lineage_digest TEXT NOT NULL DEFAULT '${LEGACY_LINEAGE_DIGEST}'
|
|
41
|
+
CHECK (length(egress_lineage_digest) = 64 AND egress_lineage_digest NOT GLOB '*[^0-9a-f]*');
|
|
42
|
+
ALTER TABLE retrieval_trace_exports
|
|
43
|
+
ADD COLUMN egress_lineage_json TEXT NOT NULL DEFAULT '${LEGACY_LINEAGE_JSON}';
|
|
44
|
+
ALTER TABLE retrieval_trace_exports
|
|
45
|
+
ADD COLUMN egress_lineage_bytes INTEGER NOT NULL DEFAULT ${lineageBytes}
|
|
46
|
+
CHECK (egress_lineage_bytes > 0 AND egress_lineage_bytes <= 32768);
|
|
47
|
+
|
|
48
|
+
ALTER TABLE document_changes
|
|
49
|
+
ADD COLUMN effective_egress_policy TEXT NOT NULL DEFAULT 'local_only'
|
|
50
|
+
CHECK (effective_egress_policy IN ('local_only', 'lan', 'remote'));
|
|
51
|
+
ALTER TABLE document_changes
|
|
52
|
+
ADD COLUMN egress_lineage_digest TEXT NOT NULL DEFAULT '${LEGACY_LINEAGE_DIGEST}'
|
|
53
|
+
CHECK (length(egress_lineage_digest) = 64 AND egress_lineage_digest NOT GLOB '*[^0-9a-f]*');
|
|
54
|
+
ALTER TABLE document_changes
|
|
55
|
+
ADD COLUMN egress_lineage_json TEXT NOT NULL DEFAULT '${LEGACY_LINEAGE_JSON}';
|
|
56
|
+
ALTER TABLE document_changes
|
|
57
|
+
ADD COLUMN egress_lineage_bytes INTEGER NOT NULL DEFAULT ${lineageBytes}
|
|
58
|
+
CHECK (egress_lineage_bytes > 0 AND egress_lineage_bytes <= 32768);
|
|
59
|
+
|
|
60
|
+
UPDATE document_changes
|
|
61
|
+
SET byte_size = byte_size + egress_lineage_bytes;
|
|
62
|
+
UPDATE document_change_journal_state
|
|
63
|
+
SET retained_bytes = (
|
|
64
|
+
SELECT COALESCE(SUM(byte_size), 0) FROM document_changes
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE TABLE egress_audit_receipts (
|
|
68
|
+
audit_id TEXT PRIMARY KEY,
|
|
69
|
+
decision TEXT NOT NULL CHECK (decision IN ('allow', 'deny')),
|
|
70
|
+
action TEXT NOT NULL
|
|
71
|
+
CHECK (action IN ('retrieve', 'serve', 'publish', 'remote_inference', 'export', 'clip_write')),
|
|
72
|
+
destination_zone TEXT NOT NULL
|
|
73
|
+
CHECK (destination_zone IN ('local_process', 'loopback', 'lan', 'remote')),
|
|
74
|
+
content_class TEXT NOT NULL
|
|
75
|
+
CHECK (content_class IN ('source', 'snippet', 'metadata', 'attachment', 'embedding', 'capsule', 'audit_log', 'retrieval_trace')),
|
|
76
|
+
effective_policy TEXT NOT NULL
|
|
77
|
+
CHECK (effective_policy IN ('local_only', 'lan', 'remote')),
|
|
78
|
+
reason_code TEXT NOT NULL,
|
|
79
|
+
lineage_digest TEXT NOT NULL
|
|
80
|
+
CHECK (length(lineage_digest) = 64 AND lineage_digest NOT GLOB '*[^0-9a-f]*'),
|
|
81
|
+
created_at_ms INTEGER NOT NULL CHECK (created_at_ms >= 0),
|
|
82
|
+
expires_at_ms INTEGER NOT NULL CHECK (expires_at_ms > created_at_ms),
|
|
83
|
+
byte_size INTEGER NOT NULL CHECK (byte_size > 0 AND byte_size <= 4096),
|
|
84
|
+
CHECK (length(audit_id) BETWEEN 1 AND 128),
|
|
85
|
+
CHECK (length(reason_code) BETWEEN 1 AND 64)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
CREATE INDEX idx_egress_audit_retention
|
|
89
|
+
ON egress_audit_receipts(expires_at_ms, created_at_ms, audit_id);
|
|
90
|
+
`);
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
down(db: Database): void {
|
|
94
|
+
db.exec(`
|
|
95
|
+
DROP TABLE egress_audit_receipts;
|
|
96
|
+
|
|
97
|
+
UPDATE document_changes
|
|
98
|
+
SET byte_size = byte_size - egress_lineage_bytes;
|
|
99
|
+
UPDATE document_change_journal_state
|
|
100
|
+
SET retained_bytes = (
|
|
101
|
+
SELECT COALESCE(SUM(byte_size), 0) FROM document_changes
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
ALTER TABLE document_changes DROP COLUMN egress_lineage_bytes;
|
|
105
|
+
ALTER TABLE document_changes DROP COLUMN egress_lineage_json;
|
|
106
|
+
ALTER TABLE document_changes DROP COLUMN egress_lineage_digest;
|
|
107
|
+
ALTER TABLE document_changes DROP COLUMN effective_egress_policy;
|
|
108
|
+
|
|
109
|
+
ALTER TABLE retrieval_trace_exports DROP COLUMN egress_lineage_bytes;
|
|
110
|
+
ALTER TABLE retrieval_trace_exports DROP COLUMN egress_lineage_json;
|
|
111
|
+
ALTER TABLE retrieval_trace_exports DROP COLUMN egress_lineage_digest;
|
|
112
|
+
ALTER TABLE retrieval_trace_exports DROP COLUMN effective_egress_policy;
|
|
113
|
+
|
|
114
|
+
ALTER TABLE retrieval_traces DROP COLUMN egress_lineage_bytes;
|
|
115
|
+
ALTER TABLE retrieval_traces DROP COLUMN egress_lineage_json;
|
|
116
|
+
ALTER TABLE retrieval_traces DROP COLUMN egress_lineage_digest;
|
|
117
|
+
ALTER TABLE retrieval_traces DROP COLUMN effective_egress_policy;
|
|
118
|
+
ALTER TABLE retrieval_traces DROP COLUMN creation_digest_version;
|
|
119
|
+
`);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add a durable monotonic collection egress policy revision.
|
|
3
|
+
*
|
|
4
|
+
* Legacy rows start at zero. Canonical config mutations advance the revision
|
|
5
|
+
* whenever the effective policy or its provenance changes.
|
|
6
|
+
*/
|
|
7
|
+
import type { Migration } from "./runner";
|
|
8
|
+
|
|
9
|
+
export const migration: Migration = {
|
|
10
|
+
version: 25,
|
|
11
|
+
name: "collection_egress_policy_revision",
|
|
12
|
+
|
|
13
|
+
up(db): void {
|
|
14
|
+
const columns = new Set(
|
|
15
|
+
db
|
|
16
|
+
.query<{ name: string }, []>("PRAGMA table_info(collections)")
|
|
17
|
+
.all()
|
|
18
|
+
.map((row) => row.name)
|
|
19
|
+
);
|
|
20
|
+
if (!columns.has("egress_policy_revision")) {
|
|
21
|
+
db.exec(`
|
|
22
|
+
ALTER TABLE collections
|
|
23
|
+
ADD COLUMN egress_policy_revision INTEGER NOT NULL DEFAULT 0
|
|
24
|
+
CHECK (egress_policy_revision >= 0)
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
down(db): void {
|
|
30
|
+
const columns = new Set(
|
|
31
|
+
db
|
|
32
|
+
.query<{ name: string }, []>("PRAGMA table_info(collections)")
|
|
33
|
+
.all()
|
|
34
|
+
.map((row) => row.name)
|
|
35
|
+
);
|
|
36
|
+
if (columns.has("egress_policy_revision")) {
|
|
37
|
+
db.exec("ALTER TABLE collections DROP COLUMN egress_policy_revision");
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -36,6 +36,9 @@ import { migration as m019 } from "./019-saved-capsule-registration-generation";
|
|
|
36
36
|
import { migration as m020 } from "./020-browser-clipper-security";
|
|
37
37
|
import { migration as m021 } from "./021-multi-context-identity";
|
|
38
38
|
import { migration as m022 } from "./022-record-export-lineage";
|
|
39
|
+
import { migration as m023 } from "./023-collection-egress-policy";
|
|
40
|
+
import { migration as m024 } from "./024-egress-derived-lineage";
|
|
41
|
+
import { migration as m025 } from "./025-collection-egress-policy-revision";
|
|
39
42
|
|
|
40
43
|
/** All migrations in order */
|
|
41
44
|
export const migrations = [
|
|
@@ -61,4 +64,7 @@ export const migrations = [
|
|
|
61
64
|
m020,
|
|
62
65
|
m021,
|
|
63
66
|
m022,
|
|
67
|
+
m023,
|
|
68
|
+
m024,
|
|
69
|
+
m025,
|
|
64
70
|
];
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
RetrievalTraceRunInput,
|
|
11
11
|
} from "./types";
|
|
12
12
|
|
|
13
|
+
import { egressLineageSchema } from "../core/egress-provenance";
|
|
13
14
|
const MAX_ID_LENGTH = 128;
|
|
14
15
|
const MAX_IDEMPOTENCY_KEY_LENGTH = 256;
|
|
15
16
|
const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
@@ -134,6 +135,7 @@ const traceInputBaseSchema = z
|
|
|
134
135
|
goalDigest: sha256Schema.nullable(),
|
|
135
136
|
goalShape: queryShapeSchema,
|
|
136
137
|
filters: traceJsonObjectSchema,
|
|
138
|
+
egressLineage: egressLineageSchema,
|
|
137
139
|
fingerprints: z
|
|
138
140
|
.object({
|
|
139
141
|
pipeline: sha256Schema,
|
|
@@ -362,6 +364,19 @@ export const hashRetrievalTraceCreation = (
|
|
|
362
364
|
return hashTraceCanonical(immutable);
|
|
363
365
|
};
|
|
364
366
|
|
|
367
|
+
/** Exact pre-v24 creation projection, used only for marked migrated rows. */
|
|
368
|
+
export const hashLegacyRetrievalTraceCreation = (
|
|
369
|
+
trace: RetrievalTraceInput
|
|
370
|
+
): string => {
|
|
371
|
+
const {
|
|
372
|
+
status: _status,
|
|
373
|
+
updatedAtMs: _updatedAtMs,
|
|
374
|
+
egressLineage: _egressLineage,
|
|
375
|
+
...immutable
|
|
376
|
+
} = trace;
|
|
377
|
+
return hashTraceCanonical(immutable);
|
|
378
|
+
};
|
|
379
|
+
|
|
365
380
|
export const parseRetrievalTraceInput = (
|
|
366
381
|
input: RetrievalTraceInput
|
|
367
382
|
): RetrievalTraceInput => retrievalTraceInputSchema.parse(input);
|