@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.
Files changed (143) hide show
  1. package/README.md +46 -20
  2. package/assets/skill/SKILL.md +33 -0
  3. package/browser-extension/artifacts/gno-browser-clipper-v1.29.1.zip +0 -0
  4. package/browser-extension/artifacts/gno-browser-clipper-v1.29.1.zip.sha256 +1 -0
  5. package/browser-extension/dist/chunk-b2zm0jjd.js +50 -0
  6. package/browser-extension/dist/manifest.json +1 -1
  7. package/browser-extension/dist/preview.html +1 -1
  8. package/browser-extension/dist/service-worker.js +6 -6
  9. package/package.json +1 -1
  10. package/spec/cli.md +41 -1
  11. package/spec/db/schema.sql +19 -0
  12. package/spec/mcp.md +66 -0
  13. package/spec/output-schemas/ask.schema.json +31 -1
  14. package/spec/output-schemas/browser-clip-preview.schema.json +30 -0
  15. package/spec/output-schemas/collection-egress-check.schema.json +91 -0
  16. package/spec/output-schemas/collection-egress-policy-set.schema.json +56 -0
  17. package/spec/output-schemas/collection-egress-policy.schema.json +41 -0
  18. package/spec/output-schemas/context-capsule-v1.schema.json +89 -2
  19. package/spec/output-schemas/context-capsule-verification.schema.json +1 -1
  20. package/spec/output-schemas/egress-audit-management.schema.json +88 -0
  21. package/spec/output-schemas/mcp-http-error.schema.json +113 -2
  22. package/spec/output-schemas/publish-artifact.schema.json +32 -0
  23. package/spec/output-schemas/record-import.schema.json +30 -0
  24. package/spec/output-schemas/retrieval-trace-common.schema.json +31 -0
  25. package/spec/output-schemas/retrieval-trace-export.schema.json +3 -0
  26. package/spec/output-schemas/retrieval-trace-qrels.schema.json +3 -0
  27. package/spec/output-schemas/retrieval-trace-summary.schema.json +4 -0
  28. package/spec/output-schemas/search-results.schema.json +30 -0
  29. package/src/app/context-runtime-contract.ts +16 -7
  30. package/src/cli/commands/ask.ts +7 -0
  31. package/src/cli/commands/collection/index.ts +5 -0
  32. package/src/cli/commands/collection/policy.ts +126 -0
  33. package/src/cli/commands/context-build.ts +5 -0
  34. package/src/cli/commands/daemon.ts +45 -3
  35. package/src/cli/commands/egress-audit.ts +68 -0
  36. package/src/cli/commands/embed.ts +2 -0
  37. package/src/cli/commands/query.ts +12 -0
  38. package/src/cli/commands/replay.ts +13 -3
  39. package/src/cli/commands/vsearch.ts +3 -1
  40. package/src/cli/program.ts +123 -0
  41. package/src/config/index.ts +9 -0
  42. package/src/config/types.ts +50 -0
  43. package/src/core/browser-clip.ts +16 -1
  44. package/src/core/collection-egress-policy-projection.ts +28 -0
  45. package/src/core/collection-egress-policy-service.ts +443 -0
  46. package/src/core/collection-egress-policy-validation.ts +242 -0
  47. package/src/core/config-mutation.ts +31 -19
  48. package/src/core/context-capsule-schema.ts +111 -1
  49. package/src/core/context-capsule-validation.ts +4 -4
  50. package/src/core/context-capsule-verification.ts +5 -1
  51. package/src/core/context-capsule.ts +177 -132
  52. package/src/core/context-evidence.ts +86 -14
  53. package/src/core/destination-classifier.ts +402 -0
  54. package/src/core/egress-audit.ts +239 -0
  55. package/src/core/egress-authorization.ts +50 -0
  56. package/src/core/egress-enforcement.ts +264 -0
  57. package/src/core/egress-policy.ts +440 -0
  58. package/src/core/egress-provenance.ts +336 -0
  59. package/src/core/job-manager.ts +37 -0
  60. package/src/core/network-boundary-inventory.ts +261 -0
  61. package/src/core/retrieval-qrels.ts +6 -0
  62. package/src/core/retrieval-replay.ts +18 -4
  63. package/src/core/retrieval-trace-export.ts +23 -4
  64. package/src/core/retrieval-trace-management-helpers.ts +1 -0
  65. package/src/core/retrieval-trace-management-types.ts +3 -0
  66. package/src/core/retrieval-trace-management.ts +9 -1
  67. package/src/core/retrieval-trace-session.ts +94 -0
  68. package/src/core/retrieval-trace.ts +22 -0
  69. package/src/ingestion/record-adapter.ts +12 -1
  70. package/src/ingestion/record-container.ts +38 -22
  71. package/src/ingestion/sync.ts +15 -0
  72. package/src/ingestion/types.ts +2 -0
  73. package/src/llm/errors.ts +10 -0
  74. package/src/llm/http-inference.ts +175 -0
  75. package/src/llm/http-policy.ts +537 -0
  76. package/src/llm/httpEmbedding.ts +47 -28
  77. package/src/llm/httpGeneration.ts +31 -18
  78. package/src/llm/httpRerank.ts +30 -18
  79. package/src/llm/index.ts +1 -0
  80. package/src/llm/nodeLlamaCpp/adapter.ts +55 -20
  81. package/src/llm/pinned-http-connection.ts +177 -0
  82. package/src/mcp/context.ts +53 -1
  83. package/src/mcp/http-egress.ts +220 -0
  84. package/src/mcp/http-security.ts +32 -21
  85. package/src/mcp/http-session.ts +13 -2
  86. package/src/mcp/http-transport.ts +96 -6
  87. package/src/mcp/sync-egress.ts +24 -0
  88. package/src/mcp/tools/add-collection.ts +4 -0
  89. package/src/mcp/tools/ask.ts +1 -1
  90. package/src/mcp/tools/context.ts +9 -2
  91. package/src/mcp/tools/egress.ts +247 -0
  92. package/src/mcp/tools/embed.ts +1 -0
  93. package/src/mcp/tools/index-cmd.ts +7 -0
  94. package/src/mcp/tools/index.ts +98 -0
  95. package/src/mcp/tools/query.ts +10 -0
  96. package/src/mcp/tools/remove-collection.ts +4 -0
  97. package/src/mcp/tools/sync.ts +6 -0
  98. package/src/mcp/tools/trace.ts +7 -1
  99. package/src/mcp/tools/vsearch.ts +1 -0
  100. package/src/pipeline/egress-lineage.ts +124 -0
  101. package/src/pipeline/hybrid.ts +14 -0
  102. package/src/pipeline/search.ts +12 -0
  103. package/src/pipeline/types.ts +3 -0
  104. package/src/pipeline/vsearch.ts +41 -20
  105. package/src/publish/artifact.ts +31 -3
  106. package/src/publish/export-service.ts +25 -0
  107. package/src/sdk/client.ts +119 -2
  108. package/src/sdk/embed.ts +1 -0
  109. package/src/sdk/types.ts +33 -0
  110. package/src/serve/clipper-capture.ts +5 -1
  111. package/src/serve/closed-json.ts +61 -0
  112. package/src/serve/config-sync.ts +4 -1
  113. package/src/serve/context.ts +1 -0
  114. package/src/serve/doc-events.ts +110 -39
  115. package/src/serve/public/components/CollectionEgressCheckPanel.tsx +411 -0
  116. package/src/serve/public/components/CollectionEgressPolicyEditor.tsx +78 -0
  117. package/src/serve/public/components/CollectionModelDialog.tsx +93 -9
  118. package/src/serve/public/components/EgressAuditPanel.tsx +278 -0
  119. package/src/serve/public/globals.built.css +1 -1
  120. package/src/serve/public/pages/Collections.tsx +3 -0
  121. package/src/serve/resident-request.ts +80 -3
  122. package/src/serve/resident-runtime.ts +63 -1
  123. package/src/serve/routes/api.ts +174 -0
  124. package/src/serve/routes/clipper.ts +64 -1
  125. package/src/serve/routes/mcp.ts +6 -0
  126. package/src/serve/routes/traces.ts +27 -4
  127. package/src/serve/server.ts +118 -7
  128. package/src/store/migrations/023-collection-egress-policy.ts +61 -0
  129. package/src/store/migrations/024-egress-derived-lineage.ts +121 -0
  130. package/src/store/migrations/025-collection-egress-policy-revision.ts +40 -0
  131. package/src/store/migrations/index.ts +6 -0
  132. package/src/store/retrieval-trace-codec.ts +15 -0
  133. package/src/store/sqlite/adapter.ts +170 -7
  134. package/src/store/sqlite/change-journal-store.ts +41 -2
  135. package/src/store/sqlite/egress-audit-store.ts +485 -0
  136. package/src/store/sqlite/retrieval-trace-management-store.ts +47 -6
  137. package/src/store/sqlite/retrieval-trace-retention.ts +2 -0
  138. package/src/store/sqlite/retrieval-trace-rows.ts +8 -1
  139. package/src/store/sqlite/retrieval-trace-store.ts +104 -5
  140. package/src/store/types.ts +126 -1
  141. package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip +0 -0
  142. package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip.sha256 +0 -1
  143. package/browser-extension/dist/chunk-vn5f663b.js +0 -50
@@ -119,7 +119,7 @@ export const disposeContextModelOwners = async (
119
119
 
120
120
  export const createMcpModelPorts = async (
121
121
  context: ToolContext,
122
- collection?: string,
122
+ collections?: readonly string[],
123
123
  factoryOverride?: McpModelPortFactory,
124
124
  options: { generation?: boolean } = {}
125
125
  ): Promise<McpModelPorts> => {
@@ -128,6 +128,10 @@ export const createMcpModelPorts = async (
128
128
  const lease = factory.acquireModelLease?.();
129
129
  const policy = resolveDownloadPolicy(process.env, {});
130
130
  const progress = createNonTtyProgressRenderer();
131
+ const egressCollections = collections?.length
132
+ ? collections
133
+ : ("all" as const);
134
+ const collection = collections?.length === 1 ? collections[0] : undefined;
131
135
  const embedUri = resolveModelUri(
132
136
  context.config,
133
137
  "embed",
@@ -141,6 +145,7 @@ export const createMcpModelPorts = async (
141
145
  let vectorIndex: VectorIndexPort | null = null;
142
146
  try {
143
147
  const embedResult = await factory.createEmbeddingPort(embedUri, {
148
+ egressCollections,
144
149
  policy,
145
150
  onProgress: (value) => progress("embed", value),
146
151
  });
@@ -160,6 +165,7 @@ export const createMcpModelPorts = async (
160
165
  const rerankResult = await factory.createRerankPort(
161
166
  resolveModelUri(context.config, "rerank", undefined, collection),
162
167
  {
168
+ egressCollections,
163
169
  policy,
164
170
  onProgress: (value) => progress("rerank", value),
165
171
  }
@@ -169,6 +175,7 @@ export const createMcpModelPorts = async (
169
175
  const genResult = await factory.createGenerationPort(
170
176
  resolveModelUri(context.config, "gen", undefined, collection),
171
177
  {
178
+ egressCollections,
172
179
  policy,
173
180
  onProgress: (value) => progress("gen", value),
174
181
  }
@@ -269,7 +276,7 @@ export const handleContext = (
269
276
  if (!traceStart.ok) throw new Error(traceStart.error.message);
270
277
  traceSession = traceStart.value ?? undefined;
271
278
  modelPorts = useModels
272
- ? await createMcpModelPorts(context, collection)
279
+ ? await createMcpModelPorts(context, parsed.input.collections)
273
280
  : null;
274
281
  const capsule = await buildContextCapsule(parsed.input, {
275
282
  store: context.store,
@@ -0,0 +1,247 @@
1
+ /** MCP collection egress policy and content-free audit management tools. */
2
+
3
+ import { z } from "zod";
4
+
5
+ import type { ToolContext } from "../server";
6
+
7
+ import { projectCollectionEgressPolicy } from "../../core/collection-egress-policy-projection";
8
+ import { CollectionEgressPolicyService } from "../../core/collection-egress-policy-service";
9
+ import { applyConfigChange } from "../../core/config-mutation";
10
+ import { EgressAuditService } from "../../core/egress-audit";
11
+ import { runTool, type ToolResult } from "./index";
12
+
13
+ const policy = z.enum(["local_only", "lan", "remote"]);
14
+
15
+ export const egressPolicyGetInputSchema = z
16
+ .object({
17
+ collection: z.string().min(1).max(64),
18
+ })
19
+ .strict();
20
+
21
+ export const egressPolicySetInputSchema = z
22
+ .object({
23
+ collection: z.string().min(1).max(64),
24
+ policy,
25
+ confirmation: z
26
+ .object({
27
+ collection: z.string().min(1).max(64),
28
+ currentPolicy: policy,
29
+ currentRevision: z.number().int().nonnegative(),
30
+ targetPolicy: policy,
31
+ acknowledged: z.literal(true),
32
+ })
33
+ .strict()
34
+ .optional(),
35
+ })
36
+ .strict();
37
+
38
+ export const egressCheckInputSchema = z
39
+ .object({
40
+ collections: z.array(z.string().min(1).max(64)).min(1).max(64).optional(),
41
+ action: z.enum([
42
+ "retrieve",
43
+ "serve",
44
+ "publish",
45
+ "remote_inference",
46
+ "export",
47
+ "clip_write",
48
+ ]),
49
+ destinationZone: z.enum(["local_process", "loopback", "lan", "remote"]),
50
+ caller: z
51
+ .object({
52
+ authenticated: z.boolean(),
53
+ operationAuthorized: z.boolean(),
54
+ })
55
+ .strict(),
56
+ contentClass: z.enum([
57
+ "source",
58
+ "snippet",
59
+ "metadata",
60
+ "attachment",
61
+ "embedding",
62
+ "capsule",
63
+ "audit_log",
64
+ "retrieval_trace",
65
+ ]),
66
+ partialResults: z.enum(["deny", "explicit"]).default("deny"),
67
+ })
68
+ .strict();
69
+
70
+ export const egressAuditListInputSchema = z
71
+ .object({
72
+ limit: z.number().int().min(1).max(1000).default(100),
73
+ cursor: z.string().min(1).max(512).optional(),
74
+ })
75
+ .strict();
76
+
77
+ export const egressAuditIdInputSchema = z
78
+ .object({
79
+ auditId: z.string().min(1).max(128),
80
+ })
81
+ .strict();
82
+
83
+ export const egressAuditStatusInputSchema = z.object({}).strict();
84
+
85
+ export const egressAuditPurgeInputSchema = z
86
+ .object({
87
+ confirm: z.literal(true),
88
+ })
89
+ .strict();
90
+
91
+ const json = (value: unknown): string => JSON.stringify(value, null, 2);
92
+
93
+ const unwrap = <T>(
94
+ result:
95
+ | { ok: true; value: T }
96
+ | { ok: false; error: { code: string; message: string } }
97
+ ): T => {
98
+ if (result.ok) return result.value;
99
+ throw new Error(`${result.error.code}: ${result.error.message}`);
100
+ };
101
+
102
+ const requireWrite = (ctx: ToolContext): void => {
103
+ if (!ctx.enableWrite) {
104
+ throw new Error(
105
+ "WRITE_DISABLED: Egress policy and audit mutations require gateway.enableWrite or --mcp-enable-write"
106
+ );
107
+ }
108
+ };
109
+
110
+ export const handleEgressPolicyGet = (
111
+ args: z.infer<typeof egressPolicyGetInputSchema>,
112
+ ctx: ToolContext
113
+ ): Promise<ToolResult> =>
114
+ runTool(
115
+ ctx,
116
+ "gno_egress_policy_get",
117
+ async () => {
118
+ const state = new CollectionEgressPolicyService({
119
+ getConfig: () => ctx.config,
120
+ }).get(args.collection);
121
+ if (!state.ok) throw new Error(`${state.code}: ${state.error}`);
122
+ return state.value;
123
+ },
124
+ json
125
+ );
126
+
127
+ export const handleEgressPolicySet = (
128
+ args: z.infer<typeof egressPolicySetInputSchema>,
129
+ ctx: ToolContext
130
+ ): Promise<ToolResult> =>
131
+ runTool(
132
+ ctx,
133
+ "gno_egress_policy_set",
134
+ async () => {
135
+ requireWrite(ctx);
136
+ const service = new CollectionEgressPolicyService({
137
+ getConfig: () => ctx.config,
138
+ mutateConfig: (mutate) =>
139
+ applyConfigChange(
140
+ {
141
+ store: ctx.store,
142
+ configPath: ctx.actualConfigPath,
143
+ onConfigUpdated: (config) => {
144
+ ctx.config = config;
145
+ },
146
+ projectStore: (store, config) =>
147
+ projectCollectionEgressPolicy(store, config, args.collection),
148
+ },
149
+ mutate
150
+ ),
151
+ onPolicyChanged: async () =>
152
+ (await ctx.invalidateEgressPolicy?.()) ?? {
153
+ policyEpoch: "egress-epoch-standalone",
154
+ queuedJobsInvalidated: 0,
155
+ sessionsInvalidated: 0,
156
+ staleWorkMustRetry: true,
157
+ },
158
+ });
159
+ const result = await service.set(args);
160
+ if (!result.ok) throw new Error(`${result.code}: ${result.error}`);
161
+ const epoch = result.value.invalidation?.policyEpoch;
162
+ if (epoch) ctx.advanceRequestAuthorizationEpoch?.(epoch);
163
+ return result.value;
164
+ },
165
+ json
166
+ );
167
+
168
+ export const handleEgressCheck = (
169
+ args: z.infer<typeof egressCheckInputSchema>,
170
+ ctx: ToolContext
171
+ ): Promise<ToolResult> =>
172
+ runTool(
173
+ ctx,
174
+ "gno_egress_check",
175
+ async () => {
176
+ const result = new CollectionEgressPolicyService({
177
+ getConfig: () => ctx.config,
178
+ }).explain(args);
179
+ if (!result.ok) throw new Error(`${result.code}: ${result.error}`);
180
+ return result.value;
181
+ },
182
+ json
183
+ );
184
+
185
+ export const handleEgressAuditList = (
186
+ args: z.infer<typeof egressAuditListInputSchema>,
187
+ ctx: ToolContext
188
+ ): Promise<ToolResult> =>
189
+ runTool(
190
+ ctx,
191
+ "gno_egress_audit_list",
192
+ async () => unwrap(await new EgressAuditService(ctx.store).list(args)),
193
+ json
194
+ );
195
+
196
+ export const handleEgressAuditShow = (
197
+ args: z.infer<typeof egressAuditIdInputSchema>,
198
+ ctx: ToolContext
199
+ ): Promise<ToolResult> =>
200
+ runTool(
201
+ ctx,
202
+ "gno_egress_audit_show",
203
+ async () =>
204
+ unwrap(await new EgressAuditService(ctx.store).show(args.auditId)),
205
+ json
206
+ );
207
+
208
+ export const handleEgressAuditStatus = (
209
+ _args: z.infer<typeof egressAuditStatusInputSchema>,
210
+ ctx: ToolContext
211
+ ): Promise<ToolResult> =>
212
+ runTool(
213
+ ctx,
214
+ "gno_egress_audit_status",
215
+ async () => unwrap(await new EgressAuditService(ctx.store).status()),
216
+ json
217
+ );
218
+
219
+ export const handleEgressAuditDelete = (
220
+ args: z.infer<typeof egressAuditIdInputSchema>,
221
+ ctx: ToolContext
222
+ ): Promise<ToolResult> =>
223
+ runTool(
224
+ ctx,
225
+ "gno_egress_audit_delete",
226
+ async () => {
227
+ requireWrite(ctx);
228
+ return unwrap(
229
+ await new EgressAuditService(ctx.store).delete(args.auditId)
230
+ );
231
+ },
232
+ json
233
+ );
234
+
235
+ export const handleEgressAuditPurge = (
236
+ _args: z.infer<typeof egressAuditPurgeInputSchema>,
237
+ ctx: ToolContext
238
+ ): Promise<ToolResult> =>
239
+ runTool(
240
+ ctx,
241
+ "gno_egress_audit_purge",
242
+ async () => {
243
+ requireWrite(ctx);
244
+ return unwrap(await new EgressAuditService(ctx.store).purge());
245
+ },
246
+ json
247
+ );
@@ -87,6 +87,7 @@ export function handleEmbed(
87
87
  // Create LLM adapter with offline policy (fail-fast, no download)
88
88
  const llm = new LlmAdapter(ctx.config);
89
89
  const embedResult = await llm.createEmbeddingPort(modelUri, {
90
+ egressCollections: collection ? [collection.name] : "all",
90
91
  policy: { offline: true, allowDownload: false },
91
92
  });
92
93
 
@@ -23,6 +23,7 @@ import {
23
23
  createVectorIndexPort,
24
24
  createVectorStatsPort,
25
25
  } from "../../store/vector";
26
+ import { enforceSyncCommandEgress } from "../sync-egress";
26
27
  import { runTool, type ToolResult } from "./index";
27
28
 
28
29
  interface IndexInput {
@@ -107,6 +108,11 @@ export function handleIndex(
107
108
  },
108
109
  ctx.config
109
110
  );
111
+ enforceSyncCommandEgress(ctx, {
112
+ collectionNames: collections,
113
+ gitPull: options.gitPull,
114
+ runUpdateCmd: options.runUpdateCmd,
115
+ });
110
116
 
111
117
  const modelUri = resolveModelUri(
112
118
  ctx.config,
@@ -144,6 +150,7 @@ export function handleIndex(
144
150
  // Phase 2: Embed
145
151
  const llm = new LlmAdapter(ctx.config);
146
152
  const embedResult = await llm.createEmbeddingPort(modelUri, {
153
+ egressCollections: collections,
147
154
  policy: { offline: true, allowDownload: false },
148
155
  });
149
156
 
@@ -31,6 +31,23 @@ import {
31
31
  } from "./changes";
32
32
  import { handleClearCollectionEmbeddings } from "./clear-collection-embeddings";
33
33
  import { handleContext, handleContextVerify } from "./context";
34
+ import {
35
+ egressAuditIdInputSchema,
36
+ egressAuditListInputSchema,
37
+ egressAuditPurgeInputSchema,
38
+ egressAuditStatusInputSchema,
39
+ egressCheckInputSchema,
40
+ egressPolicyGetInputSchema,
41
+ egressPolicySetInputSchema,
42
+ handleEgressAuditDelete,
43
+ handleEgressAuditList,
44
+ handleEgressAuditPurge,
45
+ handleEgressAuditShow,
46
+ handleEgressAuditStatus,
47
+ handleEgressCheck,
48
+ handleEgressPolicyGet,
49
+ handleEgressPolicySet,
50
+ } from "./egress";
34
51
  import { handleEmbed } from "./embed";
35
52
  import { handleGet } from "./get";
36
53
  import { handleIndex } from "./index-cmd";
@@ -125,6 +142,9 @@ export const MCP_WRITE_TOOL_NAMES = new Set([
125
142
  "gno_trace_export",
126
143
  "gno_trace_delete",
127
144
  "gno_trace_purge",
145
+ "gno_egress_policy_set",
146
+ "gno_egress_audit_delete",
147
+ "gno_egress_audit_purge",
128
148
  ]);
129
149
 
130
150
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1059,6 +1079,54 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
1059
1079
  (args) => handleStatus(args, ctx)
1060
1080
  );
1061
1081
 
1082
+ server.registerTool(
1083
+ "gno_egress_policy_get",
1084
+ {
1085
+ description:
1086
+ "Show one collection's configured and effective egress policy, provenance source, and confirmation-bound version.",
1087
+ inputSchema: egressPolicyGetInputSchema,
1088
+ },
1089
+ (args) => handleEgressPolicyGet(args, ctx)
1090
+ );
1091
+
1092
+ server.registerTool(
1093
+ "gno_egress_check",
1094
+ {
1095
+ description:
1096
+ "Check or explain an exact action, destination, caller, content class, and collection scope without performing the action.",
1097
+ inputSchema: egressCheckInputSchema,
1098
+ },
1099
+ (args) => handleEgressCheck(args, ctx)
1100
+ );
1101
+
1102
+ server.registerTool(
1103
+ "gno_egress_audit_list",
1104
+ {
1105
+ description:
1106
+ "List content-free local egress decisions newest-first through an opaque cursor.",
1107
+ inputSchema: egressAuditListInputSchema,
1108
+ },
1109
+ (args) => handleEgressAuditList(args, ctx)
1110
+ );
1111
+
1112
+ server.registerTool(
1113
+ "gno_egress_audit_show",
1114
+ {
1115
+ description: "Inspect one content-free local egress decision receipt.",
1116
+ inputSchema: egressAuditIdInputSchema,
1117
+ },
1118
+ (args) => handleEgressAuditShow(args, ctx)
1119
+ );
1120
+
1121
+ server.registerTool(
1122
+ "gno_egress_audit_status",
1123
+ {
1124
+ description: "Show local egress audit retention and storage status.",
1125
+ inputSchema: egressAuditStatusInputSchema,
1126
+ },
1127
+ (args) => handleEgressAuditStatus(args, ctx)
1128
+ );
1129
+
1062
1130
  server.tool(
1063
1131
  "gno_changes",
1064
1132
  "List retained metadata-only document changes with opaque cursor pagination and retention disclosure.",
@@ -1151,6 +1219,36 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
1151
1219
  );
1152
1220
 
1153
1221
  if (ctx.enableWrite) {
1222
+ server.registerTool(
1223
+ "gno_egress_policy_set",
1224
+ {
1225
+ description:
1226
+ "Set one collection policy. Relaxation requires confirmation bound to the current policy and version.",
1227
+ inputSchema: egressPolicySetInputSchema,
1228
+ },
1229
+ (args) => handleEgressPolicySet(args, ctx)
1230
+ );
1231
+
1232
+ server.registerTool(
1233
+ "gno_egress_audit_delete",
1234
+ {
1235
+ description:
1236
+ "Delete exactly one local egress audit receipt with physical cleanup status.",
1237
+ inputSchema: egressAuditIdInputSchema,
1238
+ },
1239
+ (args) => handleEgressAuditDelete(args, ctx)
1240
+ );
1241
+
1242
+ server.registerTool(
1243
+ "gno_egress_audit_purge",
1244
+ {
1245
+ description:
1246
+ "Purge local egress audit receipts only. Requires confirm=true.",
1247
+ inputSchema: egressAuditPurgeInputSchema,
1248
+ },
1249
+ (args) => handleEgressAuditPurge(args, ctx)
1250
+ );
1251
+
1154
1252
  server.tool(
1155
1253
  "gno_trace_label",
1156
1254
  "Append an explicit relevant, irrelevant, or missing_expected judgment to a local retrieval trace.",
@@ -279,6 +279,9 @@ export function handleQuery(
279
279
  if (!traceStart.ok) throw new Error(traceStart.error.message);
280
280
  traceSession = traceStart.value ?? undefined;
281
281
  const llm = new LlmAdapter(ctx.config);
282
+ const egressCollections = args.collection
283
+ ? [args.collection]
284
+ : ("all" as const);
282
285
 
283
286
  // Resolve download policy from env (MCP has no CLI flags)
284
287
  const policy = resolveDownloadPolicy(process.env, {});
@@ -288,6 +291,7 @@ export function handleQuery(
288
291
 
289
292
  // Create embedding port (for vector search) - optional
290
293
  const embedResult = await llm.createEmbeddingPort(embedUri, {
294
+ egressCollections,
291
295
  policy,
292
296
  onProgress: (progress) => downloadProgress("embed", progress),
293
297
  });
@@ -298,6 +302,7 @@ export function handleQuery(
298
302
  // Create expansion port - optional
299
303
  if (expandUri) {
300
304
  const genResult = await llm.createExpansionPort(expandUri, {
305
+ egressCollections,
301
306
  policy,
302
307
  onProgress: (progress) => downloadProgress("expand", progress),
303
308
  });
@@ -309,6 +314,7 @@ export function handleQuery(
309
314
  // Create rerank port - optional
310
315
  if (rerankUri) {
311
316
  const rerankResult = await llm.createRerankPort(rerankUri, {
317
+ egressCollections,
312
318
  policy,
313
319
  onProgress: (progress) => downloadProgress("rerank", progress),
314
320
  });
@@ -425,6 +431,7 @@ export function handleQueryDiagnose(
425
431
  const llm = new LlmAdapter(ctx.config);
426
432
  const policy = resolveDownloadPolicy(process.env, {});
427
433
  const downloadProgress = createNonTtyProgressRenderer();
434
+ const egressCollections = collection ? [collection] : ("all" as const);
428
435
 
429
436
  let embedPort: EmbeddingPort | null = null;
430
437
  let expandPort: GenerationPort | null = null;
@@ -456,6 +463,7 @@ export function handleQueryDiagnose(
456
463
 
457
464
  if (!args.fast) {
458
465
  const embedResult = await llm.createEmbeddingPort(embedUri, {
466
+ egressCollections,
459
467
  policy,
460
468
  onProgress: (progress) => downloadProgress("embed", progress),
461
469
  });
@@ -468,6 +476,7 @@ export function handleQueryDiagnose(
468
476
  const genResult = await llm.createExpansionPort(
469
477
  resolveModelUri(ctx.config, "expand", undefined, collection),
470
478
  {
479
+ egressCollections,
471
480
  policy,
472
481
  onProgress: (progress) => downloadProgress("expand", progress),
473
482
  }
@@ -481,6 +490,7 @@ export function handleQueryDiagnose(
481
490
  const rerankResult = await llm.createRerankPort(
482
491
  resolveModelUri(ctx.config, "rerank", undefined, collection),
483
492
  {
493
+ egressCollections,
484
494
  policy,
485
495
  onProgress: (progress) => downloadProgress("rerank", progress),
486
496
  }
@@ -89,6 +89,10 @@ export function handleRemoveCollection(
89
89
  if (!mutationResult.ok) {
90
90
  throw mapConfigError(mutationResult.code, mutationResult.error);
91
91
  }
92
+ const invalidation = await ctx.invalidateEgressPolicy?.();
93
+ if (invalidation) {
94
+ ctx.advanceRequestAuthorizationEpoch?.(invalidation.policyEpoch);
95
+ }
92
96
  ctx.markContentMutation?.();
93
97
  ctx.markIndexMutation?.();
94
98
 
@@ -14,6 +14,7 @@ import { JobError } from "../../core/job-manager";
14
14
  import { recordContentMutation } from "../../core/mutation-generations";
15
15
  import { normalizeCollectionName } from "../../core/validation";
16
16
  import { defaultSyncService, withContentTypeRules } from "../../ingestion";
17
+ import { enforceSyncCommandEgress } from "../sync-egress";
17
18
  import { runTool, type ToolResult } from "./index";
18
19
 
19
20
  interface SyncInput {
@@ -109,6 +110,11 @@ export function handleSync(
109
110
  },
110
111
  ctx.config
111
112
  );
113
+ enforceSyncCommandEgress(ctx, {
114
+ collectionNames: collections,
115
+ gitPull: options.gitPull,
116
+ runUpdateCmd: options.runUpdateCmd,
117
+ });
112
118
 
113
119
  const jobId = await ctx.jobManager.startJobWithLock(
114
120
  "sync",
@@ -106,7 +106,13 @@ const runTraceWrite = <T>(
106
106
  async () => {
107
107
  if (!ctx.enableWrite) return writeDisabled();
108
108
  return withWriteLock(ctx.writeLockPath, async () =>
109
- unwrap(await operation(new RetrievalTraceManagementService(ctx.store)))
109
+ unwrap(
110
+ await operation(
111
+ new RetrievalTraceManagementService(ctx.store, {
112
+ authorizeExport: ctx.authorizeTraceExport,
113
+ })
114
+ )
115
+ )
110
116
  );
111
117
  },
112
118
  jsonText
@@ -180,6 +180,7 @@ export function handleVsearch(
180
180
  // Create LLM adapter for embeddings
181
181
  const llm = new LlmAdapter(ctx.config);
182
182
  const embedResult = await llm.createEmbeddingPort(modelUri, {
183
+ egressCollections: args.collection ? [args.collection] : "all",
183
184
  policy,
184
185
  onProgress: (progress) => downloadProgress("embed", progress),
185
186
  });