@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
@@ -6,6 +6,35 @@
6
6
  "type": "object",
7
7
  "required": ["results", "meta"],
8
8
  "$defs": {
9
+ "egressLineage": {
10
+ "type": "object",
11
+ "additionalProperties": false,
12
+ "required": ["effectivePolicy", "digest", "sources"],
13
+ "properties": {
14
+ "effectivePolicy": { "enum": ["local_only", "lan", "remote"] },
15
+ "digest": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
16
+ "sources": {
17
+ "type": "array",
18
+ "minItems": 1,
19
+ "maxItems": 128,
20
+ "items": {
21
+ "type": "object",
22
+ "additionalProperties": false,
23
+ "required": ["collection", "policy", "source"],
24
+ "properties": {
25
+ "collection": {
26
+ "type": "string",
27
+ "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
28
+ },
29
+ "policy": { "enum": ["local_only", "lan", "remote"] },
30
+ "source": {
31
+ "enum": ["explicit", "config_default", "legacy_default"]
32
+ }
33
+ }
34
+ }
35
+ }
36
+ }
37
+ },
9
38
  "searchResult": {
10
39
  "type": "object",
11
40
  "required": ["docid", "score", "uri", "snippet", "source"],
@@ -54,10 +83,136 @@
54
83
  "type": "string",
55
84
  "description": "Resolved user-configured retrieval guidance, ordered from global to collection to matching path prefixes"
56
85
  },
86
+ "egressLineage": { "$ref": "#/$defs/egressLineage" },
57
87
  "snippet": {
58
88
  "type": "string",
59
89
  "description": "Matching text snippet from document mirror"
60
90
  },
91
+ "record": {
92
+ "type": "object",
93
+ "additionalProperties": false,
94
+ "description": "Bounded logical-record provenance for a file/export source",
95
+ "required": ["recordKey", "sourceLocator", "anchors", "adapter"],
96
+ "properties": {
97
+ "recordKey": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
98
+ "sourceLocator": {
99
+ "type": "string",
100
+ "minLength": 1,
101
+ "maxLength": 512
102
+ },
103
+ "anchors": {
104
+ "type": "array",
105
+ "maxItems": 256,
106
+ "items": {
107
+ "type": "object",
108
+ "additionalProperties": false,
109
+ "required": ["kind", "value"],
110
+ "properties": {
111
+ "kind": {
112
+ "enum": [
113
+ "line",
114
+ "cue",
115
+ "timestamp",
116
+ "message",
117
+ "event",
118
+ "record"
119
+ ]
120
+ },
121
+ "value": {
122
+ "type": "string",
123
+ "minLength": 1,
124
+ "maxLength": 512
125
+ },
126
+ "endValue": {
127
+ "type": "string",
128
+ "minLength": 1,
129
+ "maxLength": 512
130
+ }
131
+ }
132
+ }
133
+ },
134
+ "adapter": {
135
+ "type": "object",
136
+ "additionalProperties": false,
137
+ "required": ["id", "version", "fingerprint"],
138
+ "properties": {
139
+ "id": { "type": "string", "minLength": 1, "maxLength": 128 },
140
+ "version": {
141
+ "type": "string",
142
+ "minLength": 1,
143
+ "maxLength": 64
144
+ },
145
+ "fingerprint": { "type": "string", "pattern": "^[a-f0-9]{64}$" }
146
+ }
147
+ },
148
+ "author": { "type": "string", "maxLength": 2048 },
149
+ "participants": {
150
+ "type": "array",
151
+ "maxItems": 256,
152
+ "items": { "type": "string", "minLength": 1, "maxLength": 2048 }
153
+ },
154
+ "categories": {
155
+ "type": "array",
156
+ "maxItems": 256,
157
+ "items": { "type": "string", "minLength": 1, "maxLength": 512 }
158
+ },
159
+ "dateFields": {
160
+ "type": "object",
161
+ "maxProperties": 256,
162
+ "propertyNames": { "type": "string", "maxLength": 128 },
163
+ "additionalProperties": {
164
+ "type": "string",
165
+ "minLength": 1,
166
+ "maxLength": 256
167
+ }
168
+ },
169
+ "messageId": { "type": "string", "maxLength": 2048 },
170
+ "inReplyTo": { "type": "string", "maxLength": 2048 },
171
+ "references": {
172
+ "type": "array",
173
+ "maxItems": 256,
174
+ "items": {
175
+ "type": "string",
176
+ "minLength": 1,
177
+ "maxLength": 2048
178
+ }
179
+ },
180
+ "threadId": { "type": "string", "maxLength": 2048 },
181
+ "eventId": { "type": "string", "maxLength": 2048 },
182
+ "sessionId": { "type": "string", "maxLength": 2048 },
183
+ "attachments": {
184
+ "type": "array",
185
+ "maxItems": 256,
186
+ "items": {
187
+ "type": "object",
188
+ "additionalProperties": false,
189
+ "required": ["name"],
190
+ "properties": {
191
+ "name": {
192
+ "type": "string",
193
+ "minLength": 1,
194
+ "maxLength": 512
195
+ },
196
+ "mime": {
197
+ "type": "string",
198
+ "minLength": 1,
199
+ "maxLength": 256
200
+ },
201
+ "bytes": {
202
+ "type": "integer",
203
+ "minimum": 0,
204
+ "maximum": 9007199254740991
205
+ },
206
+ "disposition": { "enum": ["inline", "attachment"] },
207
+ "sha256": {
208
+ "type": "string",
209
+ "pattern": "^[a-f0-9]{64}$"
210
+ }
211
+ }
212
+ }
213
+ }
214
+ }
215
+ },
61
216
  "snippetRange": {
62
217
  "type": "object",
63
218
  "description": "Line range of snippet in mirror content",
@@ -186,6 +186,72 @@
186
186
  "modelPreset": {
187
187
  "type": "string",
188
188
  "pattern": "^[a-z0-9][a-z0-9_-]{0,63}$"
189
+ },
190
+ "recordAdapters": {
191
+ "$ref": "#/definitions/recordAdapters"
192
+ }
193
+ }
194
+ },
195
+ "jsonPointer": {
196
+ "type": "string",
197
+ "maxLength": 512,
198
+ "pattern": "^(?!.*(?:^|/)(?:__proto__|constructor|prototype)(?:/|$))(?:/(?:[^~/]|~[01])*){0,32}$"
199
+ },
200
+ "fieldSelector": {
201
+ "oneOf": [
202
+ { "$ref": "#/definitions/jsonPointer" },
203
+ {
204
+ "type": "array",
205
+ "minItems": 1,
206
+ "maxItems": 8,
207
+ "items": { "$ref": "#/definitions/jsonPointer" }
208
+ }
209
+ ]
210
+ },
211
+ "jsonlFieldMapping": {
212
+ "type": "object",
213
+ "additionalProperties": false,
214
+ "properties": {
215
+ "id": { "$ref": "#/definitions/fieldSelector" },
216
+ "body": { "$ref": "#/definitions/fieldSelector" },
217
+ "title": { "$ref": "#/definitions/fieldSelector" },
218
+ "author": { "$ref": "#/definitions/fieldSelector" },
219
+ "participants": { "$ref": "#/definitions/fieldSelector" },
220
+ "categories": { "$ref": "#/definitions/fieldSelector" },
221
+ "sessionId": { "$ref": "#/definitions/fieldSelector" },
222
+ "threadId": { "$ref": "#/definitions/fieldSelector" },
223
+ "dateFields": {
224
+ "type": "object",
225
+ "maxProperties": 16,
226
+ "propertyNames": {
227
+ "pattern": "^[a-z][a-z0-9_-]{0,63}$"
228
+ },
229
+ "additionalProperties": {
230
+ "$ref": "#/definitions/fieldSelector"
231
+ }
232
+ }
233
+ }
234
+ },
235
+ "recordAdapters": {
236
+ "type": "object",
237
+ "additionalProperties": false,
238
+ "properties": {
239
+ "jsonl": {
240
+ "type": "object",
241
+ "additionalProperties": false,
242
+ "properties": {
243
+ "fieldMapping": {
244
+ "$ref": "#/definitions/jsonlFieldMapping"
245
+ }
246
+ }
247
+ },
248
+ "transcript": {
249
+ "type": "object",
250
+ "additionalProperties": false,
251
+ "required": ["format"],
252
+ "properties": {
253
+ "format": { "enum": ["json", "srt", "text", "vtt"] }
254
+ }
189
255
  }
190
256
  }
191
257
  },
@@ -80,7 +80,7 @@ const evidenceBlock = (
80
80
  item: ContextCapsuleV1["evidence"][number]
81
81
  ): string[] => {
82
82
  const metadata = [
83
- `{"title":${json(item.title)},"heading":${json(item.heading)}}`,
83
+ `{"title":${json(item.title)},"heading":${json(item.heading)},"record":${json(item.record ?? null)}}`,
84
84
  "configuredContexts:",
85
85
  ...configuredContextLines(capsule, item.contextIds).map((line) =>
86
86
  line.startsWith(" ") ? line.slice(4) : line
@@ -3,7 +3,7 @@
3
3
  import type { ContextCanonicalProjection } from "../core/context-budget";
4
4
  import type { ContextCapsuleV1 } from "../core/context-capsule";
5
5
  import type { ContextCapabilityState } from "../core/context-capsule-retrieval-schema";
6
- import type { ContextCapsulePayloadV1 } from "../core/context-capsule-schema";
6
+ import type { ContextCapsulePayloadV1_1 } from "../core/context-capsule-schema";
7
7
  import type { ContextCanonicalPlanDraft } from "../core/context-compiler";
8
8
  import type { ContextEvidenceValue } from "../core/context-evidence";
9
9
  import type { NormalizedContextBuildInput } from "./context-runtime-input";
@@ -19,6 +19,7 @@ import {
19
19
  toContextCapsuleEvidence,
20
20
  } from "../core/context-evidence";
21
21
  import { canonicalVerifierJson } from "../core/context-verifier-canonical";
22
+ import { mergeEgressLineages } from "../core/egress-provenance";
22
23
  import { resolveModelUri } from "../llm/registry";
23
24
 
24
25
  const fingerprint = (value: unknown): string =>
@@ -104,13 +105,13 @@ const capsuleCapabilities = (
104
105
  graphExpansion: states.graphExpansion.outcome === "used",
105
106
  exactTokenCount: exactTokens,
106
107
  configuredContext,
107
- egressPolicy: false,
108
+ egressPolicy: true,
108
109
  });
109
110
 
110
111
  const capsuleFallbacks = (
111
112
  capabilities: ReturnType<typeof capsuleCapabilities>,
112
113
  states: ReturnType<typeof capsuleCapabilityStates>
113
- ): ContextCapsulePayloadV1["fallbacks"] => [
114
+ ): ContextCapsulePayloadV1_1["fallbacks"] => [
114
115
  ...(states.semanticSearch.outcome === "unavailable"
115
116
  ? [
116
117
  {
@@ -143,12 +144,15 @@ const capsuleFallbacks = (
143
144
  capability: "token_count" as const,
144
145
  },
145
146
  ]),
146
- { code: "egress_policy_unavailable", capability: "egress_policy" },
147
147
  ];
148
148
 
149
149
  export const projectContextCapsule = (
150
150
  draft: ContextCanonicalPlanDraft<ContextEvidenceValue>,
151
- snapshots: { indexFingerprint: string; contextFingerprint: string },
151
+ snapshots: {
152
+ indexFingerprint: string;
153
+ contextFingerprint: string;
154
+ egressLineage: ContextCapsulePayloadV1_1["egressLineage"];
155
+ },
152
156
  input: NormalizedContextBuildInput,
153
157
  deps: ContextCapsuleRuntimeDeps
154
158
  ): ContextCanonicalProjection<ContextCapsuleV1> | null => {
@@ -156,6 +160,10 @@ export const projectContextCapsule = (
156
160
  const evidence = draft.selection.selected.map((candidate, index) =>
157
161
  toContextCapsuleEvidence(candidate, index + 1)
158
162
  );
163
+ const egressLineage = mergeEgressLineages([
164
+ snapshots.egressLineage,
165
+ ...evidence.map((item) => item.egressLineage),
166
+ ]);
159
167
  const evidenceIdsByFacet = new Map<string, string[]>();
160
168
  for (const item of evidence) {
161
169
  for (const facet of item.facets) {
@@ -173,7 +181,7 @@ export const projectContextCapsule = (
173
181
  draft.configuredContexts.length > 0
174
182
  );
175
183
  const base = {
176
- schemaVersion: "1.0" as const,
184
+ schemaVersion: "1.1" as const,
177
185
  coordinateSpace: "canonical_mirror" as const,
178
186
  goal: draft.goal,
179
187
  query: draft.query,
@@ -213,7 +221,7 @@ export const projectContextCapsule = (
213
221
  },
214
222
  capabilities,
215
223
  };
216
- const payload: ContextCapsulePayloadV1 = {
224
+ const payload: ContextCapsulePayloadV1_1 = {
217
225
  ...base,
218
226
  budget: {
219
227
  authority: "canonical_json",
@@ -246,6 +254,7 @@ export const projectContextCapsule = (
246
254
  instructionBoundary: "hard_delimited",
247
255
  configuredContexts: draft.configuredContexts,
248
256
  },
257
+ egressLineage,
249
258
  evidence,
250
259
  coverage: {
251
260
  complete: draft.selection.coverage.unresolvedFacets.length === 0,
@@ -165,6 +165,9 @@ export async function ask(
165
165
  }
166
166
  traceSession = traceStart.value ?? undefined;
167
167
  const llm = new LlmAdapter(config);
168
+ const egressCollections = options.collection
169
+ ? [options.collection]
170
+ : ("all" as const);
168
171
 
169
172
  // Resolve download policy from env/flags
170
173
  const policy = resolveDownloadPolicy(process.env, {
@@ -179,6 +182,7 @@ export async function ask(
179
182
 
180
183
  // Create embedding port
181
184
  const embedResult = await llm.createEmbeddingPort(embedUri, {
185
+ egressCollections,
182
186
  policy,
183
187
  onProgress: downloadProgress
184
188
  ? (progress) => downloadProgress("embed", progress)
@@ -191,6 +195,7 @@ export async function ask(
191
195
  // Create expansion port when expansion is enabled.
192
196
  if (expandUri) {
193
197
  const genResult = await llm.createExpansionPort(expandUri, {
198
+ egressCollections,
194
199
  policy,
195
200
  onProgress: downloadProgress
196
201
  ? (progress) => downloadProgress("expand", progress)
@@ -204,6 +209,7 @@ export async function ask(
204
209
  // Create answer generation port when answers are requested.
205
210
  if (answerUri) {
206
211
  const genResult = await llm.createGenerationPort(answerUri, {
212
+ egressCollections,
207
213
  policy,
208
214
  onProgress: downloadProgress
209
215
  ? (progress) => downloadProgress("gen", progress)
@@ -217,6 +223,7 @@ export async function ask(
217
223
  // Create rerank port (unless --fast or --no-rerank)
218
224
  if (rerankUri) {
219
225
  const rerankResult = await llm.createRerankPort(rerankUri, {
226
+ egressCollections,
220
227
  policy,
221
228
  onProgress: downloadProgress
222
229
  ? (progress) => downloadProgress("rerank", progress)
@@ -7,5 +7,10 @@
7
7
  export { collectionAdd } from "./add";
8
8
  export { collectionClearEmbeddings } from "./clear-embeddings";
9
9
  export { collectionList } from "./list";
10
+ export {
11
+ collectionPolicyCheck,
12
+ collectionPolicyGet,
13
+ collectionPolicySet,
14
+ } from "./policy";
10
15
  export { collectionRemove } from "./remove";
11
16
  export { collectionRename } from "./rename";
@@ -0,0 +1,126 @@
1
+ /** Collection egress policy management commands. */
2
+
3
+ import type { EgressPolicy } from "../../../config/types";
4
+ import type {
5
+ EgressAction,
6
+ EgressContentClass,
7
+ EgressDestinationZone,
8
+ } from "../../../core/egress-policy";
9
+
10
+ import { loadConfig } from "../../../config";
11
+ import { projectCollectionEgressPolicy } from "../../../core/collection-egress-policy-projection";
12
+ import { CollectionEgressPolicyService } from "../../../core/collection-egress-policy-service";
13
+ import { applyConfigChange } from "../../../core/config-mutation";
14
+ import { CliError } from "../../errors";
15
+ import { initStore } from "../shared";
16
+
17
+ interface PolicyOptions {
18
+ configPath?: string;
19
+ indexName?: string;
20
+ }
21
+
22
+ const writeJson = (value: unknown): void => {
23
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
24
+ };
25
+
26
+ export const collectionPolicyGet = async (
27
+ name: string,
28
+ options: PolicyOptions = {}
29
+ ): Promise<void> => {
30
+ const loaded = await loadConfig(options.configPath);
31
+ if (!loaded.ok) throw new CliError("RUNTIME", loaded.error.message);
32
+ const state = new CollectionEgressPolicyService({
33
+ getConfig: () => loaded.value,
34
+ }).get(name);
35
+ if (!state.ok) throw new CliError("VALIDATION", state.error);
36
+ writeJson(state.value);
37
+ };
38
+
39
+ export const collectionPolicySet = async (
40
+ name: string,
41
+ policy: EgressPolicy,
42
+ options: PolicyOptions & { confirmRelaxation?: string } = {}
43
+ ): Promise<void> => {
44
+ const initialized = await initStore({
45
+ configPath: options.configPath,
46
+ indexName: options.indexName,
47
+ });
48
+ if (!initialized.ok) throw new CliError("RUNTIME", initialized.error);
49
+ let config = initialized.config;
50
+ try {
51
+ const currentResult = new CollectionEgressPolicyService({
52
+ getConfig: () => config,
53
+ }).get(name);
54
+ const service = new CollectionEgressPolicyService({
55
+ getConfig: () => config,
56
+ mutateConfig: async (mutate) => {
57
+ const result = await applyConfigChange(
58
+ {
59
+ store: initialized.store,
60
+ configPath: initialized.actualConfigPath,
61
+ onConfigUpdated: (next) => {
62
+ config = next;
63
+ },
64
+ projectStore: (store, next) =>
65
+ projectCollectionEgressPolicy(store, next, name),
66
+ },
67
+ mutate
68
+ );
69
+ return result;
70
+ },
71
+ });
72
+ const result = await service.set({
73
+ collection: name,
74
+ policy,
75
+ confirmation:
76
+ currentResult.ok && options.confirmRelaxation
77
+ ? {
78
+ collection: currentResult.value.collection,
79
+ currentPolicy: currentResult.value.effectivePolicy,
80
+ currentRevision: Number(options.confirmRelaxation),
81
+ targetPolicy: policy,
82
+ acknowledged: true,
83
+ }
84
+ : undefined,
85
+ });
86
+ if (!result.ok) {
87
+ throw new CliError("VALIDATION", result.error, {
88
+ details: { policyCode: result.code },
89
+ });
90
+ }
91
+ writeJson(result.value);
92
+ } finally {
93
+ await initialized.store.close();
94
+ }
95
+ };
96
+
97
+ export const collectionPolicyCheck = async (
98
+ input: {
99
+ collections?: string[];
100
+ action: EgressAction;
101
+ destinationZone: EgressDestinationZone;
102
+ contentClass: EgressContentClass;
103
+ authenticated: boolean;
104
+ authorized: boolean;
105
+ partial?: boolean;
106
+ },
107
+ options: PolicyOptions = {}
108
+ ): Promise<void> => {
109
+ const loaded = await loadConfig(options.configPath);
110
+ if (!loaded.ok) throw new CliError("RUNTIME", loaded.error.message);
111
+ const result = new CollectionEgressPolicyService({
112
+ getConfig: () => loaded.value,
113
+ }).explain({
114
+ collections: input.collections,
115
+ action: input.action,
116
+ destinationZone: input.destinationZone,
117
+ caller: {
118
+ authenticated: input.authenticated,
119
+ operationAuthorized: input.authorized,
120
+ },
121
+ contentClass: input.contentClass,
122
+ partialResults: input.partial ? "explicit" : "deny",
123
+ });
124
+ if (!result.ok) throw new CliError("VALIDATION", result.error);
125
+ writeJson(result.value);
126
+ };
@@ -149,7 +149,11 @@ export const contextBuild = async (
149
149
  const progress = showProgress
150
150
  ? createThrottledProgressRenderer(createProgressRenderer())
151
151
  : undefined;
152
+ const egressCollections = options.collections?.length
153
+ ? options.collections
154
+ : ("all" as const);
152
155
  const embedResult = await llm.createEmbeddingPort(embedUri, {
156
+ egressCollections,
153
157
  policy,
154
158
  onProgress: progress ? (value) => progress("embed", value) : undefined,
155
159
  });
@@ -165,6 +169,7 @@ export const contextBuild = async (
165
169
  }
166
170
  }
167
171
  const rerankResult = await llm.createRerankPort(rerankUri, {
172
+ egressCollections,
168
173
  policy,
169
174
  onProgress: progress ? (value) => progress("rerank", value) : undefined,
170
175
  });
@@ -3,6 +3,11 @@ import type { HttpGatewayOverrides } from "../../mcp/http-security";
3
3
  import type { BackgroundRuntimeResult } from "../../serve/background-runtime";
4
4
  import type { ResidentRuntime } from "../../serve/resident-runtime";
5
5
 
6
+ import {
7
+ enforceCollectionEgress,
8
+ EGRESS_DENIED_MESSAGE,
9
+ EgressDeniedError,
10
+ } from "../../core/egress-enforcement";
6
11
  import {
7
12
  DEFAULT_HTTP_GATEWAY_PORT,
8
13
  isHttpGatewayLoopbackBind,
@@ -42,6 +47,40 @@ function formatCollectionSyncSummary(result: CollectionSyncResult): string {
42
47
  return `${result.collection}: ${result.filesAdded} added, ${result.filesUpdated} updated, ${result.filesUnchanged} unchanged, ${result.filesErrored} errors`;
43
48
  }
44
49
 
50
+ export const authorizeDaemonStatus = async (
51
+ runtime: ResidentRuntime,
52
+ gateway: Awaited<ReturnType<typeof createMcpHttpGateway>>,
53
+ request: Request,
54
+ server: Parameters<typeof gateway.route>[1]
55
+ ): Promise<Response> => {
56
+ const authorization = await gateway.security.authorize(request, server);
57
+ if (!authorization.ok) return authorization.response;
58
+ try {
59
+ enforceCollectionEgress({
60
+ collections: runtime.config.collections,
61
+ action: "serve",
62
+ destinationZone: authorization.value.peerClassification.zone,
63
+ caller: {
64
+ authenticated: authorization.value.authenticated,
65
+ operationAuthorized: true,
66
+ },
67
+ contentClass: "metadata",
68
+ });
69
+ return handleResidentStatus(() => runtime.getStatus());
70
+ } catch (error) {
71
+ if (!(error instanceof EgressDeniedError)) throw error;
72
+ return Response.json(
73
+ {
74
+ error: "EGRESS_DENIED",
75
+ message: EGRESS_DENIED_MESSAGE,
76
+ reason: error.decision.reason,
77
+ audit: error.decision.audit,
78
+ },
79
+ { status: 403 }
80
+ );
81
+ }
82
+ };
83
+
45
84
  export function handleDaemonAppStatus(
46
85
  runtime: ResidentRuntime,
47
86
  host: string
@@ -161,9 +200,12 @@ export async function daemon(
161
200
  ),
162
201
  },
163
202
  "/api/resident/status": {
164
- GET: () =>
165
- handleResidentStatus(() =>
166
- (runtime as ResidentRuntime).getStatus()
203
+ GET: (request, peerServer) =>
204
+ authorizeDaemonStatus(
205
+ runtime as ResidentRuntime,
206
+ gateway as Awaited<ReturnType<typeof createMcpHttpGateway>>,
207
+ request,
208
+ peerServer
167
209
  ),
168
210
  },
169
211
  },
@@ -0,0 +1,68 @@
1
+ /** Local, content-free egress audit management commands. */
2
+
3
+ import { EgressAuditService } from "../../core/egress-audit";
4
+ import { CliError } from "../errors";
5
+ import { initStore } from "./shared";
6
+
7
+ interface AuditOptions {
8
+ configPath?: string;
9
+ indexName?: string;
10
+ }
11
+
12
+ const run = async (
13
+ options: AuditOptions,
14
+ operation: (service: EgressAuditService) => Promise<unknown>
15
+ ): Promise<void> => {
16
+ const initialized = await initStore({
17
+ configPath: options.configPath,
18
+ indexName: options.indexName,
19
+ syncConfig: false,
20
+ allowEmptyCollections: true,
21
+ });
22
+ if (!initialized.ok) throw new CliError("RUNTIME", initialized.error);
23
+ try {
24
+ const result = await operation(new EgressAuditService(initialized.store));
25
+ if (
26
+ typeof result === "object" &&
27
+ result !== null &&
28
+ "ok" in result &&
29
+ !(result as { ok: boolean }).ok
30
+ ) {
31
+ const failure = result as {
32
+ ok: false;
33
+ error: { code: string; message: string };
34
+ };
35
+ throw new CliError("RUNTIME", failure.error.message, {
36
+ details: { auditCode: failure.error.code },
37
+ });
38
+ }
39
+ const value =
40
+ typeof result === "object" && result !== null && "value" in result
41
+ ? (result as { value: unknown }).value
42
+ : result;
43
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
44
+ } finally {
45
+ await initialized.store.close();
46
+ }
47
+ };
48
+
49
+ export const egressAuditList = (
50
+ input: { limit?: number; cursor?: string },
51
+ options: AuditOptions = {}
52
+ ): Promise<void> => run(options, (service) => service.list(input));
53
+
54
+ export const egressAuditShow = (
55
+ auditId: string,
56
+ options: AuditOptions = {}
57
+ ): Promise<void> => run(options, (service) => service.show(auditId));
58
+
59
+ export const egressAuditStatus = (options: AuditOptions = {}): Promise<void> =>
60
+ run(options, (service) => service.status());
61
+
62
+ export const egressAuditDelete = (
63
+ auditId: string,
64
+ options: AuditOptions = {}
65
+ ): Promise<void> => run(options, (service) => service.delete(auditId));
66
+
67
+ export const egressAuditPurge = (options: AuditOptions = {}): Promise<void> =>
68
+ run(options, (service) => service.purge());