@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
@@ -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 validated = validateAndProjectPublishSpaceInput(input);
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 validated = validateAndProjectPublishSpaceInput(input);
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 validated = validateAndProjectEncryptedPublishInput(input);
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
package/src/sdk/client.ts CHANGED
@@ -79,6 +79,11 @@ import {
79
79
  planCapture,
80
80
  } from "../core/capture";
81
81
  import { writeCapturePlanFile } from "../core/capture-write";
82
+ import { projectCollectionEgressPolicy } from "../core/collection-egress-policy-projection";
83
+ import { CollectionEgressPolicyService } from "../core/collection-egress-policy-service";
84
+ import { applyConfigChange } from "../core/config-mutation";
85
+ import { EgressAuditService } from "../core/egress-audit";
86
+ import { authorizeCurrentEgress } from "../core/egress-authorization";
82
87
  import {
83
88
  atomicWrite,
84
89
  copyFilePath,
@@ -270,7 +275,7 @@ async function resolveClientState(
270
275
  }
271
276
 
272
277
  class GnoClientImpl implements GnoClient {
273
- readonly config: Config;
278
+ config: Config;
274
279
  readonly dbPath: string;
275
280
  readonly configPath: string | null;
276
281
  readonly configSource: "file" | "inline";
@@ -331,6 +336,9 @@ class GnoClientImpl implements GnoClient {
331
336
  rerankModel?: string;
332
337
  }): Promise<RuntimePorts> {
333
338
  this.assertOpen();
339
+ const egressCollections = options.collection
340
+ ? [options.collection]
341
+ : ("all" as const);
334
342
 
335
343
  let embedPort: EmbeddingPort | null = null;
336
344
  let expandPort: GenerationPort | null = null;
@@ -347,6 +355,7 @@ class GnoClientImpl implements GnoClient {
347
355
  options.collection
348
356
  ),
349
357
  {
358
+ egressCollections,
350
359
  policy: this.downloadPolicy,
351
360
  }
352
361
  );
@@ -391,6 +400,7 @@ class GnoClientImpl implements GnoClient {
391
400
  options.collection
392
401
  ),
393
402
  {
403
+ egressCollections,
394
404
  policy: this.downloadPolicy,
395
405
  }
396
406
  );
@@ -415,6 +425,7 @@ class GnoClientImpl implements GnoClient {
415
425
  options.collection
416
426
  ),
417
427
  {
428
+ egressCollections,
418
429
  policy: this.downloadPolicy,
419
430
  }
420
431
  );
@@ -442,6 +453,7 @@ class GnoClientImpl implements GnoClient {
442
453
  options.collection
443
454
  ),
444
455
  {
456
+ egressCollections,
445
457
  policy: this.downloadPolicy,
446
458
  }
447
459
  );
@@ -1230,7 +1242,19 @@ class GnoClientImpl implements GnoClient {
1230
1242
  ) {
1231
1243
  this.assertOpen();
1232
1244
  return unwrapTraceStore(
1233
- await new RetrievalTraceManagementService(this.store).export(input)
1245
+ await new RetrievalTraceManagementService(this.store, {
1246
+ authorizeExport: async (lineage) => {
1247
+ return authorizeCurrentEgress({
1248
+ store: this.store,
1249
+ config: this.config,
1250
+ lineage,
1251
+ action: "export",
1252
+ destinationZone: "local_process",
1253
+ caller: { authenticated: true, operationAuthorized: true },
1254
+ contentClass: "retrieval_trace",
1255
+ });
1256
+ },
1257
+ }).export(input)
1234
1258
  );
1235
1259
  }
1236
1260
 
@@ -1248,6 +1272,99 @@ class GnoClientImpl implements GnoClient {
1248
1272
  );
1249
1273
  }
1250
1274
 
1275
+ async getCollectionEgressPolicy(collection: string) {
1276
+ this.assertOpen();
1277
+ const state = new CollectionEgressPolicyService({
1278
+ getConfig: () => this.config,
1279
+ }).get(collection);
1280
+ if (!state.ok) {
1281
+ throw sdkError(
1282
+ state.code === "NOT_FOUND" ? "NOT_FOUND" : "VALIDATION",
1283
+ state.error
1284
+ );
1285
+ }
1286
+ return state.value;
1287
+ }
1288
+
1289
+ async setCollectionEgressPolicy(
1290
+ collection: string,
1291
+ policy: import("../config/types").EgressPolicy,
1292
+ confirmation?: import("../core/collection-egress-policy-service").EgressRelaxationConfirmation
1293
+ ) {
1294
+ this.assertOpen();
1295
+ if (!this.configPath) {
1296
+ throw sdkError(
1297
+ "VALIDATION",
1298
+ "Inline-config clients cannot persist collection policy changes"
1299
+ );
1300
+ }
1301
+ const service = new CollectionEgressPolicyService({
1302
+ getConfig: () => this.config,
1303
+ mutateConfig: (mutate) =>
1304
+ applyConfigChange(
1305
+ {
1306
+ store: this.store,
1307
+ configPath: this.configPath ?? undefined,
1308
+ onConfigUpdated: (config) => {
1309
+ this.config = config;
1310
+ },
1311
+ projectStore: (store, config) =>
1312
+ projectCollectionEgressPolicy(store, config, collection),
1313
+ },
1314
+ mutate
1315
+ ),
1316
+ });
1317
+ const result = await service.set({
1318
+ collection,
1319
+ policy,
1320
+ confirmation,
1321
+ });
1322
+ if (!result.ok) throw sdkError("VALIDATION", result.error);
1323
+ return result.value;
1324
+ }
1325
+
1326
+ async checkEgress(
1327
+ input: import("../core/collection-egress-policy-service").CollectionEgressCheckInput
1328
+ ) {
1329
+ this.assertOpen();
1330
+ const result = new CollectionEgressPolicyService({
1331
+ getConfig: () => this.config,
1332
+ }).check(input);
1333
+ if (!result.ok) throw sdkError("VALIDATION", result.error);
1334
+ return result.value;
1335
+ }
1336
+
1337
+ async listEgressAudits(options: { limit?: number; cursor?: string } = {}) {
1338
+ this.assertOpen();
1339
+ return unwrapTraceStore(
1340
+ await new EgressAuditService(this.store).list(options)
1341
+ );
1342
+ }
1343
+
1344
+ async getEgressAudit(auditId: string) {
1345
+ this.assertOpen();
1346
+ return unwrapTraceStore(
1347
+ await new EgressAuditService(this.store).show(auditId)
1348
+ );
1349
+ }
1350
+
1351
+ async getEgressAuditStatus() {
1352
+ this.assertOpen();
1353
+ return unwrapTraceStore(await new EgressAuditService(this.store).status());
1354
+ }
1355
+
1356
+ async deleteEgressAudit(auditId: string) {
1357
+ this.assertOpen();
1358
+ return unwrapTraceStore(
1359
+ await new EgressAuditService(this.store).delete(auditId)
1360
+ );
1361
+ }
1362
+
1363
+ async purgeEgressAudits() {
1364
+ this.assertOpen();
1365
+ return unwrapTraceStore(await new EgressAuditService(this.store).purge());
1366
+ }
1367
+
1251
1368
  async update(options: GnoUpdateOptions = {}): Promise<SyncResult> {
1252
1369
  this.assertOpen();
1253
1370
  const collections = this.getCollections(options.collection);
@@ -23,6 +23,7 @@ import type {
23
23
  } from "./types";
24
24
 
25
25
  import { getDocumentCapabilities } from "../core/document-capabilities";
26
+ import { projectRecordEvidenceMetadata } from "../core/record-metadata";
26
27
  import { isGlobPattern, parseRef, splitRefs } from "../core/ref-parser";
27
28
  import {
28
29
  attachRetrievalTraceMetadata,
@@ -53,9 +54,10 @@ function buildSourceMeta(
53
54
  config: Config
54
55
  ): GetResponse["source"] {
55
56
  const coll = config.collections.find((c) => c.name === doc.collection);
57
+ const relPath = doc.recordSourcePath ?? doc.relPath;
56
58
  return {
57
- absPath: coll ? `${coll.path}/${doc.relPath}` : undefined,
58
- relPath: doc.relPath,
59
+ absPath: coll ? `${coll.path}/${relPath}` : undefined,
60
+ relPath,
59
61
  mime: doc.sourceMime,
60
62
  ext: doc.sourceExt,
61
63
  sizeBytes: doc.sourceSize,
@@ -129,10 +131,12 @@ async function getDocumentByRefUntraced(
129
131
  language: doc.languageHint ?? undefined,
130
132
  source: buildSourceMeta(doc, config),
131
133
  conversion: buildConversionMeta(doc),
134
+ record: projectRecordEvidenceMetadata(doc),
132
135
  capabilities: getDocumentCapabilities({
133
136
  sourceExt: doc.sourceExt,
134
137
  sourceMime: doc.sourceMime,
135
138
  contentAvailable: doc.mirrorHash !== null,
139
+ recordKey: doc.recordKey,
136
140
  }),
137
141
  };
138
142
  }
@@ -156,10 +160,12 @@ async function getDocumentByRefUntraced(
156
160
  language: doc.languageHint ?? undefined,
157
161
  source: buildSourceMeta(doc, config),
158
162
  conversion: buildConversionMeta(doc),
163
+ record: projectRecordEvidenceMetadata(doc),
159
164
  capabilities: getDocumentCapabilities({
160
165
  sourceExt: doc.sourceExt,
161
166
  sourceMime: doc.sourceMime,
162
167
  contentAvailable: doc.mirrorHash !== null,
168
+ recordKey: doc.recordKey,
163
169
  }),
164
170
  };
165
171
  }
@@ -318,6 +324,7 @@ export async function multiGetDocuments(
318
324
  maxBytes
319
325
  );
320
326
  const coll = config.collections.find((c) => c.name === doc.collection);
327
+ const sourceRelPath = doc.recordSourcePath ?? doc.relPath;
321
328
  documents.push({
322
329
  docid: doc.docid,
323
330
  uri: doc.uri,
@@ -326,11 +333,12 @@ export async function multiGetDocuments(
326
333
  truncated: truncated || undefined,
327
334
  totalLines: content.split("\n").length,
328
335
  source: {
329
- absPath: coll ? `${coll.path}/${doc.relPath}` : undefined,
330
- relPath: doc.relPath,
336
+ absPath: coll ? `${coll.path}/${sourceRelPath}` : undefined,
337
+ relPath: sourceRelPath,
331
338
  mime: doc.sourceMime,
332
339
  ext: doc.sourceExt,
333
340
  },
341
+ record: projectRecordEvidenceMetadata(doc),
334
342
  });
335
343
  }
336
344
 
@@ -385,7 +393,7 @@ export async function listDocuments(
385
393
  uri: d.uri,
386
394
  title: d.title ?? undefined,
387
395
  source: {
388
- relPath: d.relPath,
396
+ relPath: d.recordSourcePath ?? d.relPath,
389
397
  mime: d.sourceMime,
390
398
  ext: d.sourceExt,
391
399
  },
package/src/sdk/embed.ts CHANGED
@@ -283,6 +283,7 @@ export async function runEmbed(
283
283
  }
284
284
 
285
285
  const embedResult = await runtime.llm.createEmbeddingPort(modelUri, {
286
+ egressCollections: options.collection ? [options.collection] : "all",
286
287
  policy: runtime.downloadPolicy,
287
288
  });
288
289
  if (!embedResult.ok) {
package/src/sdk/types.ts CHANGED
@@ -11,6 +11,13 @@ import type {
11
11
  import type { ContentTypeBoostStatus } from "../config/content-types";
12
12
  import type { Config } from "../config/types";
13
13
  import type { CaptureInput, CaptureReceipt } from "../core/capture";
14
+ import type {
15
+ CollectionEgressCheckInput,
16
+ CollectionEgressCheckResult,
17
+ CollectionEgressPolicySetResult,
18
+ CollectionEgressPolicyState,
19
+ EgressRelaxationConfirmation,
20
+ } from "../core/collection-egress-policy-service";
14
21
  import type {
15
22
  ContextCapsuleErrorCode,
16
23
  ContextCapsuleV1,
@@ -18,6 +25,13 @@ import type {
18
25
  } from "../core/context-capsule";
19
26
  import type { ContextEvidenceErrorCode } from "../core/context-evidence";
20
27
  import type { ContextVerifierErrorCode } from "../core/context-verifier";
28
+ import type {
29
+ EgressAuditDeleteResult,
30
+ EgressAuditListResult,
31
+ EgressAuditPurgeManagementResult,
32
+ EgressAuditShowResult,
33
+ EgressAuditStatusResult,
34
+ } from "../core/egress-audit";
21
35
  import type {
22
36
  KnowledgeChangesResult,
23
37
  KnowledgeDiffResult,
@@ -300,6 +314,25 @@ export interface GnoClient {
300
314
  ): Promise<RetrievalTraceExportResult>;
301
315
  deleteRetrievalTrace(traceId: string): Promise<RetrievalTraceDeleteResult>;
302
316
  purgeRetrievalTraces(): Promise<RetrievalTraceManagementPurgeResult>;
317
+ getCollectionEgressPolicy(
318
+ collection: string
319
+ ): Promise<CollectionEgressPolicyState>;
320
+ setCollectionEgressPolicy(
321
+ collection: string,
322
+ policy: import("../config/types").EgressPolicy,
323
+ confirmation?: EgressRelaxationConfirmation
324
+ ): Promise<CollectionEgressPolicySetResult>;
325
+ checkEgress(
326
+ input: CollectionEgressCheckInput
327
+ ): Promise<CollectionEgressCheckResult>;
328
+ listEgressAudits(options?: {
329
+ limit?: number;
330
+ cursor?: string;
331
+ }): Promise<EgressAuditListResult>;
332
+ getEgressAudit(auditId: string): Promise<EgressAuditShowResult>;
333
+ getEgressAuditStatus(): Promise<EgressAuditStatusResult>;
334
+ deleteEgressAudit(auditId: string): Promise<EgressAuditDeleteResult>;
335
+ purgeEgressAudits(): Promise<EgressAuditPurgeManagementResult>;
303
336
  update(options?: GnoUpdateOptions): Promise<SyncResult>;
304
337
  embed(options?: GnoEmbedOptions): Promise<GnoEmbedResult>;
305
338
  index(options?: GnoIndexOptions): Promise<GnoIndexResult>;
@@ -15,7 +15,7 @@ export interface BrowseTreeNode {
15
15
  type BrowseCollectionLike = Pick<CollectionRow, "name">;
16
16
  type BrowseDocumentLike = Pick<
17
17
  DocumentRow,
18
- "collection" | "relPath" | "active"
18
+ "collection" | "relPath" | "recordSourcePath" | "active"
19
19
  >;
20
20
  type BrowseFolderLike = {
21
21
  collection: string;
@@ -128,7 +128,9 @@ export function buildBrowseTree(
128
128
 
129
129
  root.documentCount += 1;
130
130
 
131
- const normalizedRelPath = normalizeBrowsePath(doc.relPath);
131
+ const normalizedRelPath = normalizeBrowsePath(
132
+ doc.recordSourcePath ?? doc.relPath
133
+ );
132
134
  const parts = normalizedRelPath.split("/").filter(Boolean);
133
135
  const folderParts = parts.slice(0, -1);
134
136
 
@@ -3,6 +3,7 @@
3
3
  import type { Database } from "bun:sqlite";
4
4
 
5
5
  import type { PreparedBrowserClip } from "../core/browser-clip";
6
+ import type { EgressLineage } from "../core/egress-provenance";
6
7
  import type { SqliteAdapter } from "../store/sqlite/adapter";
7
8
  import type { ClipperIdempotencyReplay } from "../store/sqlite/clipper-store-types";
8
9
  import type { ResidentCaptureContext } from "./capture-service";
@@ -35,6 +36,7 @@ interface ExecuteClipperCaptureInput {
35
36
  context: ResidentCaptureContext;
36
37
  store: SqliteAdapter;
37
38
  pairing: ClipperPairingService;
39
+ egressLineage?: EgressLineage;
38
40
  }
39
41
 
40
42
  const inFlightRequests = new Set<string>();
@@ -153,7 +155,9 @@ export const executeClipperCapture = async (
153
155
  );
154
156
  }
155
157
 
156
- const prepared = prepareBrowserClip(parsed.data.payload);
158
+ const prepared = prepareBrowserClip(parsed.data.payload, {
159
+ egressLineage: input.egressLineage,
160
+ });
157
161
  if (prepared.preview.digest !== parsed.data.previewDigest) {
158
162
  return clipperErrorResponse(
159
163
  "CLIPPER_PREVIEW_MISMATCH",
@@ -0,0 +1,61 @@
1
+ /** Bounded, content-free JSON request parsing for closed REST contracts. */
2
+
3
+ const DEFAULT_MAX_BYTES = 16 * 1024;
4
+
5
+ export type ClosedJsonResult =
6
+ | { ok: true; value: unknown }
7
+ | { ok: false; code: "VALIDATION"; error: string };
8
+
9
+ export const parseClosedJson = async (
10
+ request: Request,
11
+ maxBytes = DEFAULT_MAX_BYTES
12
+ ): Promise<ClosedJsonResult> => {
13
+ try {
14
+ const contentLength = Number(request.headers.get("content-length"));
15
+ if (Number.isFinite(contentLength) && contentLength > maxBytes) {
16
+ return {
17
+ ok: false,
18
+ code: "VALIDATION",
19
+ error: "JSON body exceeds the allowed size",
20
+ };
21
+ }
22
+ const reader = request.body?.getReader();
23
+ if (!reader) {
24
+ return {
25
+ ok: false,
26
+ code: "VALIDATION",
27
+ error: "Invalid JSON body",
28
+ };
29
+ }
30
+ const chunks: Uint8Array[] = [];
31
+ let totalBytes = 0;
32
+ while (true) {
33
+ const chunk = await reader.read();
34
+ if (chunk.done) break;
35
+ totalBytes += chunk.value.byteLength;
36
+ if (totalBytes > maxBytes) {
37
+ await reader.cancel("JSON body exceeds the allowed size");
38
+ return {
39
+ ok: false,
40
+ code: "VALIDATION",
41
+ error: "JSON body exceeds the allowed size",
42
+ };
43
+ }
44
+ chunks.push(chunk.value);
45
+ }
46
+ const bytes = new Uint8Array(totalBytes);
47
+ let offset = 0;
48
+ for (const chunk of chunks) {
49
+ bytes.set(chunk, offset);
50
+ offset += chunk.byteLength;
51
+ }
52
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
53
+ return { ok: true, value: JSON.parse(text) as unknown };
54
+ } catch {
55
+ return {
56
+ ok: false,
57
+ code: "VALIDATION",
58
+ error: "Invalid JSON body",
59
+ };
60
+ }
61
+ };
@@ -11,6 +11,7 @@ import type { ContextHolder } from "./routes/api";
11
11
 
12
12
  import {
13
13
  applyConfigChange as applyConfigChangeCore,
14
+ type ConfigMutationContext,
14
15
  type ApplyConfigResult,
15
16
  type MutationResult,
16
17
  } from "../core/config-mutation";
@@ -67,11 +68,13 @@ export async function applyConfigChangeTyped<T>(
67
68
  ctxHolder: ContextHolder,
68
69
  store: SqliteAdapter,
69
70
  mutate: (config: Config) => Promise<MutationResult<T>> | MutationResult<T>,
70
- configPath?: string
71
+ configPath?: string,
72
+ options: Pick<ConfigMutationContext, "projectStore"> = {}
71
73
  ): Promise<ApplyConfigResult<T>> {
72
74
  const result = await applyConfigChangeCore(
73
75
  {
74
76
  store,
77
+ projectStore: options.projectStore,
75
78
  configPath: configPath ?? ctxHolder.actualConfigPath,
76
79
  onConfigUpdated: (config) => {
77
80
  ctxHolder.config = config;
@@ -115,6 +115,7 @@ export async function createServerContext(
115
115
 
116
116
  // Progress callback updates downloadState for WebUI polling
117
117
  const createPortOptions = (type: ModelType): CreatePortOptions => ({
118
+ egressCollections: "all",
118
119
  policy,
119
120
  onProgress: (progress) => {
120
121
  downloadState.active = true;