@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.
Files changed (195) hide show
  1. package/README.md +46 -20
  2. package/assets/skill/SKILL.md +59 -1
  3. package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.zip +0 -0
  4. package/browser-extension/artifacts/gno-browser-clipper-v1.29.0.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 +2 -2
  10. package/spec/cli.md +82 -5
  11. package/spec/db/schema.sql +31 -0
  12. package/spec/mcp.md +71 -0
  13. package/spec/output-schemas/ask.schema.json +156 -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 +183 -3
  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/get.schema.json +94 -0
  22. package/spec/output-schemas/mcp-http-error.schema.json +113 -2
  23. package/spec/output-schemas/mcp-job-status.schema.json +28 -0
  24. package/spec/output-schemas/multi-get.schema.json +128 -0
  25. package/spec/output-schemas/publish-artifact.schema.json +32 -0
  26. package/spec/output-schemas/record-import.schema.json +223 -0
  27. package/spec/output-schemas/retrieval-trace-common.schema.json +31 -0
  28. package/spec/output-schemas/retrieval-trace-export.schema.json +3 -0
  29. package/spec/output-schemas/retrieval-trace-qrels.schema.json +3 -0
  30. package/spec/output-schemas/retrieval-trace-summary.schema.json +4 -0
  31. package/spec/output-schemas/search-result.schema.json +94 -0
  32. package/spec/output-schemas/search-results.schema.json +155 -0
  33. package/spec/project-profile.schema.json +66 -0
  34. package/src/app/context-format.ts +1 -1
  35. package/src/app/context-runtime-contract.ts +16 -7
  36. package/src/cli/commands/ask.ts +7 -0
  37. package/src/cli/commands/collection/index.ts +5 -0
  38. package/src/cli/commands/collection/policy.ts +126 -0
  39. package/src/cli/commands/context-build.ts +5 -0
  40. package/src/cli/commands/daemon.ts +45 -3
  41. package/src/cli/commands/egress-audit.ts +68 -0
  42. package/src/cli/commands/embed.ts +2 -0
  43. package/src/cli/commands/get.ts +12 -2
  44. package/src/cli/commands/index-cmd.ts +13 -0
  45. package/src/cli/commands/multi-get.ts +9 -2
  46. package/src/cli/commands/query.ts +12 -0
  47. package/src/cli/commands/replay.ts +13 -3
  48. package/src/cli/commands/shared.ts +28 -0
  49. package/src/cli/commands/update.ts +5 -0
  50. package/src/cli/commands/vsearch.ts +3 -1
  51. package/src/cli/program.ts +127 -0
  52. package/src/config/index.ts +9 -0
  53. package/src/config/project-profile.ts +2 -0
  54. package/src/config/types.ts +66 -0
  55. package/src/converters/adapters/browser-export/adapter.ts +199 -0
  56. package/src/converters/adapters/browser-export/formats.ts +358 -0
  57. package/src/converters/adapters/email/adapter.ts +429 -0
  58. package/src/converters/adapters/email/html.ts +162 -0
  59. package/src/converters/adapters/email/mime.ts +454 -0
  60. package/src/converters/adapters/email/parameters.ts +77 -0
  61. package/src/converters/adapters/ical/adapter.ts +475 -0
  62. package/src/converters/adapters/ical/recurrence.ts +186 -0
  63. package/src/converters/adapters/jsonl/adapter.ts +238 -0
  64. package/src/converters/adapters/jsonl/config.ts +105 -0
  65. package/src/converters/adapters/shared/html-text.ts +165 -0
  66. package/src/converters/adapters/shared/record-utils.ts +79 -0
  67. package/src/converters/adapters/shared/utf8-lines.ts +141 -0
  68. package/src/converters/adapters/transcript/adapter.ts +295 -0
  69. package/src/converters/adapters/transcript/json.ts +184 -0
  70. package/src/converters/adapters/transcript/model.ts +171 -0
  71. package/src/converters/adapters/transcript/text.ts +58 -0
  72. package/src/converters/adapters/transcript/timed.ts +152 -0
  73. package/src/converters/index.ts +11 -1
  74. package/src/converters/mime.ts +8 -0
  75. package/src/converters/pipeline.ts +15 -1
  76. package/src/converters/registry.ts +37 -1
  77. package/src/converters/types.ts +136 -0
  78. package/src/core/browser-clip.ts +16 -1
  79. package/src/core/collection-egress-policy-projection.ts +28 -0
  80. package/src/core/collection-egress-policy-service.ts +443 -0
  81. package/src/core/collection-egress-policy-validation.ts +242 -0
  82. package/src/core/config-mutation.ts +31 -19
  83. package/src/core/context-capsule-schema.ts +198 -1
  84. package/src/core/context-capsule-validation.ts +4 -4
  85. package/src/core/context-capsule-verification.ts +5 -1
  86. package/src/core/context-capsule.ts +177 -112
  87. package/src/core/context-evidence.ts +93 -15
  88. package/src/core/destination-classifier.ts +402 -0
  89. package/src/core/document-capabilities.ts +12 -0
  90. package/src/core/egress-audit.ts +239 -0
  91. package/src/core/egress-authorization.ts +50 -0
  92. package/src/core/egress-enforcement.ts +264 -0
  93. package/src/core/egress-policy.ts +440 -0
  94. package/src/core/egress-provenance.ts +336 -0
  95. package/src/core/job-manager.ts +37 -0
  96. package/src/core/network-boundary-inventory.ts +261 -0
  97. package/src/core/project-profile-apply-state.ts +5 -0
  98. package/src/core/project-profile.ts +4 -0
  99. package/src/core/record-metadata.ts +49 -0
  100. package/src/core/retrieval-qrels.ts +6 -0
  101. package/src/core/retrieval-replay.ts +18 -4
  102. package/src/core/retrieval-trace-export.ts +23 -4
  103. package/src/core/retrieval-trace-management-helpers.ts +1 -0
  104. package/src/core/retrieval-trace-management-types.ts +3 -0
  105. package/src/core/retrieval-trace-management.ts +9 -1
  106. package/src/core/retrieval-trace-session.ts +94 -0
  107. package/src/core/retrieval-trace.ts +22 -0
  108. package/src/ingestion/record-adapter-canonical.ts +433 -0
  109. package/src/ingestion/record-adapter.ts +448 -0
  110. package/src/ingestion/record-container.ts +704 -0
  111. package/src/ingestion/record-path.ts +20 -0
  112. package/src/ingestion/record-sync.ts +70 -0
  113. package/src/ingestion/sync.ts +243 -36
  114. package/src/ingestion/types.ts +71 -1
  115. package/src/ingestion/walker.ts +31 -11
  116. package/src/llm/errors.ts +10 -0
  117. package/src/llm/http-inference.ts +175 -0
  118. package/src/llm/http-policy.ts +537 -0
  119. package/src/llm/httpEmbedding.ts +47 -28
  120. package/src/llm/httpGeneration.ts +31 -18
  121. package/src/llm/httpRerank.ts +30 -18
  122. package/src/llm/index.ts +1 -0
  123. package/src/llm/nodeLlamaCpp/adapter.ts +55 -20
  124. package/src/llm/pinned-http-connection.ts +177 -0
  125. package/src/mcp/context.ts +53 -1
  126. package/src/mcp/http-egress.ts +220 -0
  127. package/src/mcp/http-security.ts +32 -21
  128. package/src/mcp/http-session.ts +13 -2
  129. package/src/mcp/http-transport.ts +96 -6
  130. package/src/mcp/sync-egress.ts +24 -0
  131. package/src/mcp/tools/add-collection.ts +4 -0
  132. package/src/mcp/tools/ask.ts +1 -1
  133. package/src/mcp/tools/context.ts +9 -2
  134. package/src/mcp/tools/egress.ts +247 -0
  135. package/src/mcp/tools/embed.ts +1 -0
  136. package/src/mcp/tools/get.ts +10 -2
  137. package/src/mcp/tools/index-cmd.ts +7 -0
  138. package/src/mcp/tools/index.ts +98 -0
  139. package/src/mcp/tools/multi-get.ts +9 -2
  140. package/src/mcp/tools/query.ts +10 -0
  141. package/src/mcp/tools/remove-collection.ts +4 -0
  142. package/src/mcp/tools/sync.ts +6 -0
  143. package/src/mcp/tools/trace.ts +7 -1
  144. package/src/mcp/tools/vsearch.ts +1 -0
  145. package/src/mcp/tools/workspace-write.ts +2 -0
  146. package/src/pipeline/egress-lineage.ts +124 -0
  147. package/src/pipeline/filters.ts +1 -1
  148. package/src/pipeline/graph-retrieval.ts +7 -4
  149. package/src/pipeline/hybrid.ts +21 -4
  150. package/src/pipeline/result-context.ts +12 -4
  151. package/src/pipeline/search.ts +23 -4
  152. package/src/pipeline/types.ts +6 -0
  153. package/src/pipeline/vsearch.ts +67 -28
  154. package/src/publish/artifact.ts +31 -3
  155. package/src/publish/export-service.ts +25 -0
  156. package/src/sdk/client.ts +119 -2
  157. package/src/sdk/documents.ts +13 -5
  158. package/src/sdk/embed.ts +1 -0
  159. package/src/sdk/types.ts +33 -0
  160. package/src/serve/browse-tree.ts +4 -2
  161. package/src/serve/clipper-capture.ts +5 -1
  162. package/src/serve/closed-json.ts +61 -0
  163. package/src/serve/config-sync.ts +4 -1
  164. package/src/serve/context.ts +1 -0
  165. package/src/serve/doc-events.ts +110 -39
  166. package/src/serve/public/components/CollectionEgressCheckPanel.tsx +411 -0
  167. package/src/serve/public/components/CollectionEgressPolicyEditor.tsx +78 -0
  168. package/src/serve/public/components/CollectionModelDialog.tsx +93 -9
  169. package/src/serve/public/components/EgressAuditPanel.tsx +278 -0
  170. package/src/serve/public/globals.built.css +1 -1
  171. package/src/serve/public/pages/Collections.tsx +3 -0
  172. package/src/serve/resident-request.ts +80 -3
  173. package/src/serve/resident-runtime.ts +63 -1
  174. package/src/serve/routes/api.ts +237 -65
  175. package/src/serve/routes/clipper.ts +64 -1
  176. package/src/serve/routes/mcp.ts +6 -0
  177. package/src/serve/routes/traces.ts +27 -4
  178. package/src/serve/server.ts +118 -7
  179. package/src/store/migrations/022-record-export-lineage.ts +42 -0
  180. package/src/store/migrations/023-collection-egress-policy.ts +61 -0
  181. package/src/store/migrations/024-egress-derived-lineage.ts +121 -0
  182. package/src/store/migrations/025-collection-egress-policy-revision.ts +40 -0
  183. package/src/store/migrations/index.ts +8 -0
  184. package/src/store/retrieval-trace-codec.ts +15 -0
  185. package/src/store/sqlite/adapter.ts +284 -14
  186. package/src/store/sqlite/change-journal-store.ts +41 -2
  187. package/src/store/sqlite/egress-audit-store.ts +485 -0
  188. package/src/store/sqlite/retrieval-trace-management-store.ts +47 -6
  189. package/src/store/sqlite/retrieval-trace-retention.ts +2 -0
  190. package/src/store/sqlite/retrieval-trace-rows.ts +8 -1
  191. package/src/store/sqlite/retrieval-trace-store.ts +104 -5
  192. package/src/store/types.ts +166 -1
  193. package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip +0 -0
  194. package/browser-extension/artifacts/gno-browser-clipper-v1.27.1.zip.sha256 +0 -1
  195. package/browser-extension/dist/chunk-vn5f663b.js +0 -50
@@ -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
 
@@ -15,6 +15,10 @@ import {
15
15
  type DocumentCapabilities,
16
16
  } from "../../core/document-capabilities";
17
17
  import { resolveEffectiveIndex } from "../../core/indexed-reference";
18
+ import {
19
+ projectRecordEvidenceMetadata,
20
+ type RecordEvidenceMetadata,
21
+ } from "../../core/record-metadata";
18
22
  import { parseRef } from "../../core/ref-parser";
19
23
  import {
20
24
  attachRetrievalTraceMetadata,
@@ -54,6 +58,7 @@ interface GetResponse {
54
58
  converterVersion?: string;
55
59
  mirrorHash?: string;
56
60
  };
61
+ record?: RecordEvidenceMetadata;
57
62
  capabilities: DocumentCapabilities;
58
63
  }
59
64
 
@@ -210,13 +215,14 @@ export function handleGet(
210
215
 
211
216
  // Build absPath
212
217
  const uriParsed = parseUri(doc.uri);
218
+ const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
213
219
  let absPath: string | undefined;
214
220
  if (uriParsed) {
215
221
  const collection = ctx.collections.find(
216
222
  (c) => c.name === uriParsed.collection
217
223
  );
218
224
  if (collection) {
219
- absPath = pathJoin(collection.path, doc.relPath);
225
+ absPath = pathJoin(collection.path, sourceRelPath);
220
226
  }
221
227
  }
222
228
 
@@ -230,7 +236,7 @@ export function handleGet(
230
236
  language: doc.languageHint ?? undefined,
231
237
  source: {
232
238
  absPath,
233
- relPath: doc.relPath,
239
+ relPath: sourceRelPath,
234
240
  mime: doc.sourceMime,
235
241
  ext: doc.sourceExt,
236
242
  modifiedAt: doc.sourceMtime,
@@ -244,10 +250,12 @@ export function handleGet(
244
250
  mirrorHash: doc.mirrorHash,
245
251
  }
246
252
  : undefined,
253
+ record: projectRecordEvidenceMetadata(doc),
247
254
  capabilities: getDocumentCapabilities({
248
255
  sourceExt: doc.sourceExt,
249
256
  sourceMime: doc.sourceMime,
250
257
  contentAvailable: doc.mirrorHash !== null,
258
+ recordKey: doc.recordKey,
251
259
  }),
252
260
  };
253
261
 
@@ -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.",
@@ -11,6 +11,10 @@ import type { ToolContext } from "../server";
11
11
 
12
12
  import { decorateUriForIndex, parseUri } from "../../app/constants";
13
13
  import { resolveEffectiveIndex } from "../../core/indexed-reference";
14
+ import {
15
+ projectRecordEvidenceMetadata,
16
+ type RecordEvidenceMetadata,
17
+ } from "../../core/record-metadata";
14
18
  import { parseRef } from "../../core/ref-parser";
15
19
  import { openScopedIndexStore } from "../../store/sqlite/scoped-index";
16
20
  import { runTool, type ToolResult } from "./index";
@@ -37,6 +41,7 @@ interface DocumentResult {
37
41
  modifiedAt?: string;
38
42
  sizeBytes?: number;
39
43
  };
44
+ record?: RecordEvidenceMetadata;
40
45
  }
41
46
 
42
47
  interface MultiGetResponse {
@@ -234,13 +239,14 @@ export function handleMultiGet(
234
239
 
235
240
  // Build absPath
236
241
  const uriParsed = parseUri(doc.uri);
242
+ const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
237
243
  let absPath: string | undefined;
238
244
  if (uriParsed) {
239
245
  const collection = ctx.collections.find(
240
246
  (c) => c.name === uriParsed.collection
241
247
  );
242
248
  if (collection) {
243
- absPath = pathJoin(collection.path, doc.relPath);
249
+ absPath = pathJoin(collection.path, sourceRelPath);
244
250
  }
245
251
  }
246
252
 
@@ -253,12 +259,13 @@ export function handleMultiGet(
253
259
  truncated,
254
260
  source: {
255
261
  absPath,
256
- relPath: doc.relPath,
262
+ relPath: sourceRelPath,
257
263
  mime: doc.sourceMime,
258
264
  ext: doc.sourceExt,
259
265
  modifiedAt: doc.sourceMtime,
260
266
  sizeBytes: doc.sourceSize,
261
267
  },
268
+ record: projectRecordEvidenceMetadata(doc),
262
269
  });
263
270
  }
264
271
 
@@ -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
  });
@@ -141,11 +141,13 @@ function ensureEditable(doc: {
141
141
  sourceExt: string;
142
142
  sourceMime: string;
143
143
  mirrorHash: string | null;
144
+ recordKey?: string | null;
144
145
  }) {
145
146
  const capabilities = getDocumentCapabilities({
146
147
  sourceExt: doc.sourceExt,
147
148
  sourceMime: doc.sourceMime,
148
149
  contentAvailable: doc.mirrorHash !== null,
150
+ recordKey: doc.recordKey,
149
151
  });
150
152
  if (!capabilities.editable) {
151
153
  throw new Error(