@gmickel/gno 1.28.0 → 1.29.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +46 -20
  2. package/assets/skill/SKILL.md +33 -0
  3. package/browser-extension/artifacts/gno-browser-clipper-v1.29.1.zip +0 -0
  4. package/browser-extension/artifacts/gno-browser-clipper-v1.29.1.zip.sha256 +1 -0
  5. package/browser-extension/dist/chunk-b2zm0jjd.js +50 -0
  6. package/browser-extension/dist/manifest.json +1 -1
  7. package/browser-extension/dist/preview.html +1 -1
  8. package/browser-extension/dist/service-worker.js +6 -6
  9. package/package.json +1 -1
  10. package/spec/cli.md +41 -1
  11. package/spec/db/schema.sql +19 -0
  12. package/spec/mcp.md +66 -0
  13. package/spec/output-schemas/ask.schema.json +31 -1
  14. package/spec/output-schemas/browser-clip-preview.schema.json +30 -0
  15. package/spec/output-schemas/collection-egress-check.schema.json +91 -0
  16. package/spec/output-schemas/collection-egress-policy-set.schema.json +56 -0
  17. package/spec/output-schemas/collection-egress-policy.schema.json +41 -0
  18. package/spec/output-schemas/context-capsule-v1.schema.json +89 -2
  19. package/spec/output-schemas/context-capsule-verification.schema.json +1 -1
  20. package/spec/output-schemas/egress-audit-management.schema.json +88 -0
  21. package/spec/output-schemas/mcp-http-error.schema.json +113 -2
  22. package/spec/output-schemas/publish-artifact.schema.json +32 -0
  23. package/spec/output-schemas/record-import.schema.json +30 -0
  24. package/spec/output-schemas/retrieval-trace-common.schema.json +31 -0
  25. package/spec/output-schemas/retrieval-trace-export.schema.json +3 -0
  26. package/spec/output-schemas/retrieval-trace-qrels.schema.json +3 -0
  27. package/spec/output-schemas/retrieval-trace-summary.schema.json +4 -0
  28. package/spec/output-schemas/search-results.schema.json +30 -0
  29. package/src/app/context-runtime-contract.ts +16 -7
  30. package/src/cli/commands/ask.ts +7 -0
  31. package/src/cli/commands/collection/index.ts +5 -0
  32. package/src/cli/commands/collection/policy.ts +126 -0
  33. package/src/cli/commands/context-build.ts +5 -0
  34. package/src/cli/commands/daemon.ts +45 -3
  35. package/src/cli/commands/egress-audit.ts +68 -0
  36. package/src/cli/commands/embed.ts +2 -0
  37. package/src/cli/commands/query.ts +12 -0
  38. package/src/cli/commands/replay.ts +13 -3
  39. package/src/cli/commands/vsearch.ts +3 -1
  40. package/src/cli/program.ts +123 -0
  41. package/src/config/index.ts +9 -0
  42. package/src/config/types.ts +50 -0
  43. package/src/core/browser-clip.ts +16 -1
  44. package/src/core/collection-egress-policy-projection.ts +28 -0
  45. package/src/core/collection-egress-policy-service.ts +443 -0
  46. package/src/core/collection-egress-policy-validation.ts +242 -0
  47. package/src/core/config-mutation.ts +31 -19
  48. package/src/core/context-capsule-schema.ts +111 -1
  49. package/src/core/context-capsule-validation.ts +4 -4
  50. package/src/core/context-capsule-verification.ts +5 -1
  51. package/src/core/context-capsule.ts +177 -132
  52. package/src/core/context-evidence.ts +86 -14
  53. package/src/core/destination-classifier.ts +402 -0
  54. package/src/core/egress-audit.ts +239 -0
  55. package/src/core/egress-authorization.ts +50 -0
  56. package/src/core/egress-enforcement.ts +264 -0
  57. package/src/core/egress-policy.ts +440 -0
  58. package/src/core/egress-provenance.ts +336 -0
  59. package/src/core/job-manager.ts +37 -0
  60. package/src/core/network-boundary-inventory.ts +261 -0
  61. package/src/core/retrieval-qrels.ts +6 -0
  62. package/src/core/retrieval-replay.ts +18 -4
  63. package/src/core/retrieval-trace-export.ts +23 -4
  64. package/src/core/retrieval-trace-management-helpers.ts +1 -0
  65. package/src/core/retrieval-trace-management-types.ts +3 -0
  66. package/src/core/retrieval-trace-management.ts +9 -1
  67. package/src/core/retrieval-trace-session.ts +94 -0
  68. package/src/core/retrieval-trace.ts +22 -0
  69. package/src/ingestion/record-adapter.ts +12 -1
  70. package/src/ingestion/record-container.ts +38 -22
  71. package/src/ingestion/sync.ts +15 -0
  72. package/src/ingestion/types.ts +2 -0
  73. package/src/llm/errors.ts +10 -0
  74. package/src/llm/http-inference.ts +175 -0
  75. package/src/llm/http-policy.ts +537 -0
  76. package/src/llm/httpEmbedding.ts +47 -28
  77. package/src/llm/httpGeneration.ts +31 -18
  78. package/src/llm/httpRerank.ts +30 -18
  79. package/src/llm/index.ts +1 -0
  80. package/src/llm/nodeLlamaCpp/adapter.ts +55 -20
  81. package/src/llm/pinned-http-connection.ts +177 -0
  82. package/src/mcp/context.ts +53 -1
  83. package/src/mcp/http-egress.ts +220 -0
  84. package/src/mcp/http-security.ts +32 -21
  85. package/src/mcp/http-session.ts +13 -2
  86. package/src/mcp/http-transport.ts +96 -6
  87. package/src/mcp/sync-egress.ts +24 -0
  88. package/src/mcp/tools/add-collection.ts +4 -0
  89. package/src/mcp/tools/ask.ts +1 -1
  90. package/src/mcp/tools/context.ts +9 -2
  91. package/src/mcp/tools/egress.ts +247 -0
  92. package/src/mcp/tools/embed.ts +1 -0
  93. package/src/mcp/tools/index-cmd.ts +7 -0
  94. package/src/mcp/tools/index.ts +98 -0
  95. package/src/mcp/tools/query.ts +10 -0
  96. package/src/mcp/tools/remove-collection.ts +4 -0
  97. package/src/mcp/tools/sync.ts +6 -0
  98. package/src/mcp/tools/trace.ts +7 -1
  99. package/src/mcp/tools/vsearch.ts +1 -0
  100. package/src/pipeline/egress-lineage.ts +124 -0
  101. package/src/pipeline/hybrid.ts +14 -0
  102. package/src/pipeline/search.ts +12 -0
  103. package/src/pipeline/types.ts +3 -0
  104. package/src/pipeline/vsearch.ts +41 -20
  105. package/src/publish/artifact.ts +31 -3
  106. package/src/publish/export-service.ts +25 -0
  107. package/src/sdk/client.ts +119 -2
  108. package/src/sdk/embed.ts +1 -0
  109. package/src/sdk/types.ts +33 -0
  110. package/src/serve/clipper-capture.ts +5 -1
  111. package/src/serve/closed-json.ts +61 -0
  112. package/src/serve/config-sync.ts +4 -1
  113. package/src/serve/context.ts +1 -0
  114. package/src/serve/doc-events.ts +110 -39
  115. package/src/serve/public/components/CollectionEgressCheckPanel.tsx +411 -0
  116. package/src/serve/public/components/CollectionEgressPolicyEditor.tsx +78 -0
  117. package/src/serve/public/components/CollectionModelDialog.tsx +93 -9
  118. package/src/serve/public/components/EgressAuditPanel.tsx +278 -0
  119. package/src/serve/public/globals.built.css +1 -1
  120. package/src/serve/public/pages/Collections.tsx +3 -0
  121. package/src/serve/resident-request.ts +80 -3
  122. package/src/serve/resident-runtime.ts +63 -1
  123. package/src/serve/routes/api.ts +174 -0
  124. package/src/serve/routes/clipper.ts +64 -1
  125. package/src/serve/routes/mcp.ts +6 -0
  126. package/src/serve/routes/traces.ts +27 -4
  127. package/src/serve/server.ts +118 -7
  128. package/src/store/migrations/023-collection-egress-policy.ts +61 -0
  129. package/src/store/migrations/024-egress-derived-lineage.ts +121 -0
  130. package/src/store/migrations/025-collection-egress-policy-revision.ts +40 -0
  131. package/src/store/migrations/index.ts +6 -0
  132. package/src/store/retrieval-trace-codec.ts +15 -0
  133. package/src/store/sqlite/adapter.ts +170 -7
  134. package/src/store/sqlite/change-journal-store.ts +41 -2
  135. package/src/store/sqlite/egress-audit-store.ts +485 -0
  136. package/src/store/sqlite/retrieval-trace-management-store.ts +47 -6
  137. package/src/store/sqlite/retrieval-trace-retention.ts +2 -0
  138. package/src/store/sqlite/retrieval-trace-rows.ts +8 -1
  139. package/src/store/sqlite/retrieval-trace-store.ts +104 -5
  140. package/src/store/types.ts +126 -1
  141. package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip +0 -0
  142. package/browser-extension/artifacts/gno-browser-clipper-v1.28.0.zip.sha256 +0 -1
  143. package/browser-extension/dist/chunk-vn5f663b.js +0 -50
@@ -3,8 +3,10 @@ import { z } from "zod";
3
3
  import { canonicalizeIndexName } from "../app/index-name";
4
4
  import {
5
5
  contextCapsulePayloadV1Schema,
6
- type ContextCapsulePayloadV1,
6
+ contextCapsulePayloadV1_1Schema,
7
+ type ContextCapsulePayload,
7
8
  } from "./context-capsule-schema";
9
+ import { createEgressLineage, type EgressLineage } from "./egress-provenance";
8
10
 
9
11
  const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
10
12
 
@@ -51,136 +53,155 @@ const normalizeSet = (values: readonly string[]): string[] =>
51
53
  [...new Set(values.map(normalizeText))].sort(compareCodeUnits);
52
54
 
53
55
  const normalizePayload = (
54
- value: ContextCapsulePayloadV1
55
- ): ContextCapsulePayloadV1 => ({
56
- ...value,
57
- goal: normalizeText(value.goal),
58
- query: normalizeText(value.query),
59
- scope: {
60
- ...value.scope,
61
- indexName: canonicalizeIndexName(value.scope.indexName),
62
- collections: normalizeSet(value.scope.collections),
63
- uriPrefix:
64
- value.scope.uriPrefix === null
65
- ? null
66
- : normalizeText(value.scope.uriPrefix),
67
- tagsAll: normalizeSet(value.scope.tagsAll),
68
- tagsAny: normalizeSet(value.scope.tagsAny),
69
- categories: normalizeSet(value.scope.categories),
70
- since: normalizeDate(value.scope.since),
71
- until: normalizeDate(value.scope.until),
72
- },
73
- retrieval: {
74
- ...value.retrieval,
75
- facets: normalizeSet(value.retrieval.facets),
76
- queryVariants: value.retrieval.queryVariants.map(normalizeText),
77
- request: {
78
- ...value.retrieval.request,
79
- author:
80
- value.retrieval.request.author === null
56
+ value: ContextCapsulePayload
57
+ ): ContextCapsulePayload =>
58
+ ({
59
+ ...value,
60
+ goal: normalizeText(value.goal),
61
+ query: normalizeText(value.query),
62
+ scope: {
63
+ ...value.scope,
64
+ indexName: canonicalizeIndexName(value.scope.indexName),
65
+ collections: normalizeSet(value.scope.collections),
66
+ uriPrefix:
67
+ value.scope.uriPrefix === null
81
68
  ? null
82
- : normalizeText(value.retrieval.request.author),
83
- lang:
84
- value.retrieval.request.lang === null
85
- ? null
86
- : normalizeText(value.retrieval.request.lang),
87
- ...(value.retrieval.request.intent === undefined
69
+ : normalizeText(value.scope.uriPrefix),
70
+ tagsAll: normalizeSet(value.scope.tagsAll),
71
+ tagsAny: normalizeSet(value.scope.tagsAny),
72
+ categories: normalizeSet(value.scope.categories),
73
+ since: normalizeDate(value.scope.since),
74
+ until: normalizeDate(value.scope.until),
75
+ },
76
+ retrieval: {
77
+ ...value.retrieval,
78
+ facets: normalizeSet(value.retrieval.facets),
79
+ queryVariants: value.retrieval.queryVariants.map(normalizeText),
80
+ request: {
81
+ ...value.retrieval.request,
82
+ author:
83
+ value.retrieval.request.author === null
84
+ ? null
85
+ : normalizeText(value.retrieval.request.author),
86
+ lang:
87
+ value.retrieval.request.lang === null
88
+ ? null
89
+ : normalizeText(value.retrieval.request.lang),
90
+ ...(value.retrieval.request.intent === undefined
91
+ ? {}
92
+ : {
93
+ intent:
94
+ value.retrieval.request.intent === null
95
+ ? null
96
+ : normalizeText(value.retrieval.request.intent),
97
+ }),
98
+ ...(value.retrieval.request.exclude === undefined
99
+ ? {}
100
+ : { exclude: normalizeSet(value.retrieval.request.exclude) }),
101
+ queryModes: value.retrieval.request.queryModes.map((mode) => ({
102
+ ...mode,
103
+ text: normalizeText(mode.text),
104
+ })),
105
+ },
106
+ capabilityStates: Object.fromEntries(
107
+ Object.entries(value.retrieval.capabilityStates).map(([key, state]) => [
108
+ key,
109
+ { ...state, fallbackReasons: normalizeSet(state.fallbackReasons) },
110
+ ])
111
+ ) as typeof value.retrieval.capabilityStates,
112
+ },
113
+ fallbacks: [...value.fallbacks].sort((left, right) =>
114
+ compareCodeUnits(
115
+ `${left.code}\0${left.capability}`,
116
+ `${right.code}\0${right.capability}`
117
+ )
118
+ ),
119
+ ...(value.schemaVersion === "1.1"
120
+ ? {
121
+ egressLineage: {
122
+ ...value.egressLineage,
123
+ sources: [...value.egressLineage.sources],
124
+ },
125
+ }
126
+ : {}),
127
+ evidence: value.evidence.map((item) => ({
128
+ ...item,
129
+ title: item.title === null ? null : normalizeText(item.title),
130
+ heading: item.heading === null ? null : normalizeText(item.heading),
131
+ modifiedAt: normalizeDate(item.modifiedAt),
132
+ documentDate: normalizeDocumentDate(item.documentDate),
133
+ observedAt: normalizeDate(item.observedAt),
134
+ contextIds: normalizeSet(item.contextIds),
135
+ ...(item.egressLineage === undefined
88
136
  ? {}
89
137
  : {
90
- intent:
91
- value.retrieval.request.intent === null
92
- ? null
93
- : normalizeText(value.retrieval.request.intent),
138
+ egressLineage: {
139
+ ...item.egressLineage,
140
+ sources: [...item.egressLineage.sources],
141
+ },
94
142
  }),
95
- ...(value.retrieval.request.exclude === undefined
143
+ ...(item.retrievalSources === undefined
96
144
  ? {}
97
- : { exclude: normalizeSet(value.retrieval.request.exclude) }),
98
- queryModes: value.retrieval.request.queryModes.map((mode) => ({
99
- ...mode,
100
- text: normalizeText(mode.text),
101
- })),
102
- },
103
- capabilityStates: Object.fromEntries(
104
- Object.entries(value.retrieval.capabilityStates).map(([key, state]) => [
105
- key,
106
- { ...state, fallbackReasons: normalizeSet(state.fallbackReasons) },
107
- ])
108
- ) as typeof value.retrieval.capabilityStates,
109
- },
110
- fallbacks: [...value.fallbacks].sort((left, right) =>
111
- compareCodeUnits(
112
- `${left.code}\0${left.capability}`,
113
- `${right.code}\0${right.capability}`
114
- )
115
- ),
116
- evidence: value.evidence.map((item) => ({
117
- ...item,
118
- title: item.title === null ? null : normalizeText(item.title),
119
- heading: item.heading === null ? null : normalizeText(item.heading),
120
- modifiedAt: normalizeDate(item.modifiedAt),
121
- documentDate: normalizeDocumentDate(item.documentDate),
122
- observedAt: normalizeDate(item.observedAt),
123
- contextIds: normalizeSet(item.contextIds),
124
- ...(item.retrievalSources === undefined
125
- ? {}
126
- : {
127
- retrievalSources: [...new Set(item.retrievalSources)].sort(
128
- compareCodeUnits
129
- ),
130
- }),
131
- facets: normalizeSet(item.facets),
132
- ...(item.record === undefined
133
- ? {}
134
- : {
135
- record: {
136
- ...item.record,
137
- anchors: item.record.anchors.map((anchor) => ({
138
- ...anchor,
139
- value: normalizeText(anchor.value),
140
- ...(anchor.endValue === undefined
145
+ : {
146
+ retrievalSources: [...new Set(item.retrievalSources)].sort(
147
+ compareCodeUnits
148
+ ),
149
+ }),
150
+ facets: normalizeSet(item.facets),
151
+ ...(item.record === undefined
152
+ ? {}
153
+ : {
154
+ record: {
155
+ ...item.record,
156
+ anchors: item.record.anchors.map((anchor) => ({
157
+ ...anchor,
158
+ value: normalizeText(anchor.value),
159
+ ...(anchor.endValue === undefined
160
+ ? {}
161
+ : { endValue: normalizeText(anchor.endValue) }),
162
+ })),
163
+ ...(item.record.participants === undefined
141
164
  ? {}
142
- : { endValue: normalizeText(anchor.endValue) }),
143
- })),
144
- ...(item.record.participants === undefined
145
- ? {}
146
- : { participants: normalizeSet(item.record.participants) }),
147
- ...(item.record.categories === undefined
148
- ? {}
149
- : { categories: normalizeSet(item.record.categories) }),
150
- },
151
- }),
152
- })),
153
- guidance: {
154
- ...value.guidance,
155
- configuredContexts: [...value.guidance.configuredContexts]
156
- .map((item) => ({
157
- ...item,
158
- scopeKey: normalizeText(item.scopeKey),
159
- text: normalizeText(item.text),
160
- }))
161
- .sort((left, right) => compareCodeUnits(left.contextId, right.contextId)),
162
- },
163
- coverage: {
164
- ...value.coverage,
165
- requestedFacets: normalizeSet(value.coverage.requestedFacets),
166
- coveredFacets: [...value.coverage.coveredFacets]
167
- .map((item) => ({
168
- facet: normalizeText(item.facet),
169
- evidenceIds: normalizeSet(item.evidenceIds),
170
- }))
171
- .sort((left, right) => compareCodeUnits(left.facet, right.facet)),
172
- unresolvedFacets: normalizeSet(value.coverage.unresolvedFacets),
173
- gaps: [...value.coverage.gaps].sort((left, right) =>
174
- compareCodeUnits(
175
- `${left.facet}\0${left.code}`,
176
- `${right.facet}\0${right.code}`
177
- )
165
+ : { participants: normalizeSet(item.record.participants) }),
166
+ ...(item.record.categories === undefined
167
+ ? {}
168
+ : { categories: normalizeSet(item.record.categories) }),
169
+ },
170
+ }),
171
+ })),
172
+ guidance: {
173
+ ...value.guidance,
174
+ configuredContexts: [...value.guidance.configuredContexts]
175
+ .map((item) => ({
176
+ ...item,
177
+ scopeKey: normalizeText(item.scopeKey),
178
+ text: normalizeText(item.text),
179
+ }))
180
+ .sort((left, right) =>
181
+ compareCodeUnits(left.contextId, right.contextId)
182
+ ),
183
+ },
184
+ coverage: {
185
+ ...value.coverage,
186
+ requestedFacets: normalizeSet(value.coverage.requestedFacets),
187
+ coveredFacets: [...value.coverage.coveredFacets]
188
+ .map((item) => ({
189
+ facet: normalizeText(item.facet),
190
+ evidenceIds: normalizeSet(item.evidenceIds),
191
+ }))
192
+ .sort((left, right) => compareCodeUnits(left.facet, right.facet)),
193
+ unresolvedFacets: normalizeSet(value.coverage.unresolvedFacets),
194
+ gaps: [...value.coverage.gaps].sort((left, right) =>
195
+ compareCodeUnits(
196
+ `${left.facet}\0${left.code}`,
197
+ `${right.facet}\0${right.code}`
198
+ )
199
+ ),
200
+ },
201
+ warnings: [...value.warnings].sort((left, right) =>
202
+ compareCodeUnits(left.code, right.code)
178
203
  ),
179
- },
180
- warnings: [...value.warnings].sort((left, right) =>
181
- compareCodeUnits(left.code, right.code)
182
- ),
183
- });
204
+ }) as ContextCapsulePayload;
184
205
 
185
206
  export type ContextCapsuleErrorCode =
186
207
  | "identity_mismatch"
@@ -267,9 +288,14 @@ const contractError = (
267
288
  );
268
289
  };
269
290
 
270
- const parsePayload = (input: unknown): ContextCapsulePayloadV1 => {
291
+ const contextCapsulePayloadSchema = z.union([
292
+ contextCapsulePayloadV1Schema,
293
+ contextCapsulePayloadV1_1Schema,
294
+ ]);
295
+
296
+ const parsePayload = (input: unknown): ContextCapsulePayload => {
271
297
  try {
272
- return contextCapsulePayloadV1Schema.parse(input);
298
+ return contextCapsulePayloadSchema.parse(input);
273
299
  } catch (error) {
274
300
  throw contractError(input, error);
275
301
  }
@@ -279,7 +305,7 @@ export interface ContextCapsuleCreateOptions {
279
305
  countTokens?: (accountingJson: string) => number;
280
306
  }
281
307
 
282
- const identityPayload = (payload: ContextCapsulePayloadV1) => {
308
+ const identityPayload = (payload: ContextCapsulePayload) => {
283
309
  const {
284
310
  usedBytes: _usedBytes,
285
311
  usedTokens: _usedTokens,
@@ -288,7 +314,7 @@ const identityPayload = (payload: ContextCapsulePayloadV1) => {
288
314
  return { ...payload, budget: stableBudget };
289
315
  };
290
316
 
291
- const accountingPayload = <T extends ContextCapsulePayloadV1>(value: T) => {
317
+ const accountingPayload = <T extends ContextCapsulePayload>(value: T) => {
292
318
  const {
293
319
  usedBytes: _usedBytes,
294
320
  usedTokens: _usedTokens,
@@ -312,7 +338,7 @@ const activeTokenCount = (
312
338
  };
313
339
 
314
340
  const fixedPointCapsule = (
315
- payloadInput: ContextCapsulePayloadV1,
341
+ payloadInput: ContextCapsulePayload,
316
342
  options: ContextCapsuleCreateOptions
317
343
  ) => {
318
344
  const capsuleId = hashCanonical(identityPayload(payloadInput));
@@ -362,9 +388,10 @@ const fixedPointCapsule = (
362
388
  );
363
389
  };
364
390
 
365
- const contextCapsuleBaseV1Schema = contextCapsulePayloadV1Schema.extend({
366
- capsuleId: sha256Schema,
367
- });
391
+ const contextCapsuleBaseV1Schema = z.union([
392
+ contextCapsulePayloadV1Schema.extend({ capsuleId: sha256Schema }),
393
+ contextCapsulePayloadV1_1Schema.extend({ capsuleId: sha256Schema }),
394
+ ]);
368
395
 
369
396
  export const contextCapsuleV1Schema = contextCapsuleBaseV1Schema.superRefine(
370
397
  (value, context) => {
@@ -470,6 +497,24 @@ export const canonicalContextCapsuleJson = (input: unknown): string =>
470
497
  export const canonicalContextCapsuleAccountingJson = (input: unknown): string =>
471
498
  canonicalJson(accountingPayload(parseContextCapsuleV1(input)));
472
499
 
500
+ /**
501
+ * Runtime-only conservative projection for legacy 1.0 Capsules. Canonical
502
+ * bytes and capsuleId remain governed by the original schema.
503
+ */
504
+ export const contextCapsuleEgressLineage = (input: unknown): EgressLineage => {
505
+ const capsule = parseContextCapsuleV1(input);
506
+ if (capsule.schemaVersion === "1.1") return capsule.egressLineage;
507
+ return createEgressLineage(
508
+ [...new Set(capsule.evidence.map(({ collection }) => collection))].map(
509
+ (collection) => ({
510
+ collection,
511
+ policy: "local_only" as const,
512
+ source: "legacy_default" as const,
513
+ })
514
+ )
515
+ );
516
+ };
517
+
473
518
  export {
474
519
  contextCapsuleVerificationEvidenceSchema,
475
520
  contextCapsuleVerificationSchema,
@@ -1,5 +1,6 @@
1
1
  /** Strict indexed-evidence loading for Context Capsule compilation. */
2
2
 
3
+ import type { EgressPolicy } from "../config/types";
3
4
  import type { SearchResults } from "../pipeline/types";
4
5
  import type {
5
6
  ActivationIndexSnapshot,
@@ -12,7 +13,7 @@ import type {
12
13
  ContextCanonicalProjection,
13
14
  MaterializedContextCandidate,
14
15
  } from "./context-budget";
15
- import type { ContextCapsulePayloadV1 } from "./context-capsule-schema";
16
+ import type { ContextCapsulePayloadV1_1 } from "./context-capsule-schema";
16
17
  import type {
17
18
  ContextCanonicalPlanDraft,
18
19
  ContextCompilerInput,
@@ -21,6 +22,7 @@ import type {
21
22
  ContextRetrievalCandidate,
22
23
  ContextRetrievalRequest,
23
24
  } from "./context-compiler";
25
+ import type { EgressLineage } from "./egress-provenance";
24
26
  import type { RecordEvidenceMetadata } from "./record-metadata";
25
27
 
26
28
  import { decorateUriForIndex, deriveDocid, parseUri } from "../app/constants";
@@ -36,6 +38,7 @@ import {
36
38
  } from "./context-capsule-validation";
37
39
  import { planContextEvidence } from "./context-compiler";
38
40
  import { projectContextEvidenceMetadata } from "./context-evidence-metadata";
41
+ import { createEgressLineage, resolveEgressLineage } from "./egress-provenance";
39
42
  import { projectRecordEvidenceMetadata } from "./record-metadata";
40
43
  import {
41
44
  extractInclusiveLines,
@@ -82,6 +85,7 @@ export class ContextEvidenceError extends Error {
82
85
 
83
86
  export interface ContextEvidenceSnapshot {
84
87
  collections: string[];
88
+ egressLineage: EgressLineage;
85
89
  contexts: ContextRow[];
86
90
  documents: ContextEvidenceSnapshotDocument[];
87
91
  contextFingerprint: string;
@@ -105,7 +109,8 @@ export interface ContextEvidenceValue {
105
109
  observedAt: null;
106
110
  contextIds: string[];
107
111
  trust: "untrusted";
108
- egress: "unavailable";
112
+ egress: EgressPolicy | "unavailable";
113
+ egressLineage?: EgressLineage;
109
114
  record?: RecordEvidenceMetadata;
110
115
  }
111
116
 
@@ -117,6 +122,7 @@ export type ContextEvidenceCompilerInput = Omit<
117
122
  export interface ContextEvidenceProjectionContext {
118
123
  contextFingerprint: string;
119
124
  indexFingerprint: string;
125
+ egressLineage: EgressLineage;
120
126
  }
121
127
 
122
128
  export interface ContextEvidenceCompilerDeps<P> {
@@ -208,18 +214,34 @@ export const captureContextEvidenceSnapshot = async (
208
214
  "context_load_failed",
209
215
  "Failed to load configured contexts"
210
216
  ).map((row) => ({ ...row }));
217
+ const collectionRows = unwrapStore(
218
+ await store.getCollections(),
219
+ "collection_load_failed",
220
+ "Failed to load indexed collections"
221
+ );
211
222
  const collections =
212
223
  requestedCollections.length > 0
213
224
  ? [...new Set(requestedCollections)].sort(compareCodeUnits)
214
- : [
215
- ...new Set(
216
- unwrapStore(
217
- await store.getCollections(),
218
- "collection_load_failed",
219
- "Failed to load indexed collections"
220
- ).map((row) => row.name)
221
- ),
222
- ].sort(compareCodeUnits);
225
+ : [...new Set(collectionRows.map((row) => row.name))].sort(
226
+ compareCodeUnits
227
+ );
228
+ let egressLineage: EgressLineage;
229
+ try {
230
+ egressLineage = resolveEgressLineage(
231
+ collectionRows.map((row) => ({
232
+ collection: row.name,
233
+ policy: row.egressPolicy,
234
+ source: row.egressPolicySource,
235
+ })),
236
+ collections
237
+ );
238
+ } catch (error) {
239
+ throw new ContextEvidenceError(
240
+ "collection_load_failed",
241
+ "Failed to resolve complete collection policy lineage",
242
+ error
243
+ );
244
+ }
223
245
  const indexName = canonicalizeIndexName(indexNameInput);
224
246
  const indexSnapshots: ReturnType<typeof canonicalIndexSnapshot>[] = [];
225
247
  const documents: ContextEvidenceSnapshotDocument[] = [];
@@ -248,6 +270,7 @@ export const captureContextEvidenceSnapshot = async (
248
270
  }
249
271
  return {
250
272
  collections,
273
+ egressLineage,
251
274
  contexts,
252
275
  documents: documents.sort(
253
276
  (left, right) =>
@@ -256,7 +279,10 @@ export const captureContextEvidenceSnapshot = async (
256
279
  compareCodeUnits(left.mirrorHash ?? "", right.mirrorHash ?? "")
257
280
  ),
258
281
  contextFingerprint: fingerprintContextRows(contexts),
259
- indexFingerprint: hashJson(indexSnapshots),
282
+ indexFingerprint: hashJson({
283
+ snapshots: indexSnapshots,
284
+ egressLineage,
285
+ }),
260
286
  };
261
287
  };
262
288
 
@@ -313,6 +339,21 @@ export const materializeContextEvidenceCandidates = async (
313
339
  "document_load_failed",
314
340
  "Failed to batch-load Context evidence documents"
315
341
  );
342
+ const collectionRows = unwrapStore(
343
+ await store.getCollections(),
344
+ "collection_load_failed",
345
+ "Failed to load Context evidence policies"
346
+ );
347
+ const policyByCollection = new Map(
348
+ collectionRows.map((row) => [
349
+ row.name,
350
+ {
351
+ collection: row.name,
352
+ policy: row.egressPolicy,
353
+ source: row.egressPolicySource,
354
+ },
355
+ ])
356
+ );
316
357
  const alignedDocuments = candidates.map((candidate) =>
317
358
  referenceDocument(documents, candidate, indexName)
318
359
  );
@@ -343,6 +384,24 @@ export const materializeContextEvidenceCandidates = async (
343
384
  return candidates.map((candidate, index) => {
344
385
  const result = candidate.result;
345
386
  const document = alignedDocuments[index];
387
+ if (!document) {
388
+ throw new ContextEvidenceError(
389
+ "document_load_failed",
390
+ `Document identity is unavailable for ${result.uri}`
391
+ );
392
+ }
393
+ const fallbackPolicy = policyByCollection.get(document.collection);
394
+ if (!result.egressLineage && !fallbackPolicy) {
395
+ throw new ContextEvidenceError(
396
+ "collection_load_failed",
397
+ `Policy lineage is unavailable for ${result.uri}`
398
+ );
399
+ }
400
+ const egressLineage =
401
+ result.egressLineage ??
402
+ createEgressLineage([
403
+ fallbackPolicy as NonNullable<typeof fallbackPolicy>,
404
+ ]);
346
405
  const metadata = result[SEARCH_RESULT_PLANNER_METADATA];
347
406
  const snippetRange = result.snippetRange;
348
407
  if (!document?.mirrorHash || !metadata || !snippetRange) {
@@ -418,7 +477,8 @@ export const materializeContextEvidenceCandidates = async (
418
477
  observedAt: null,
419
478
  contextIds: [...candidate.contextIds],
420
479
  trust: "untrusted",
421
- egress: "unavailable",
480
+ egress: egressLineage.effectivePolicy,
481
+ egressLineage,
422
482
  ...(projectRecordEvidenceMetadata(document)
423
483
  ? { record: projectRecordEvidenceMetadata(document) }
424
484
  : {}),
@@ -441,6 +501,7 @@ export const compileContextEvidence = async <P>(
441
501
  const fingerprints = {
442
502
  contextFingerprint: before.contextFingerprint,
443
503
  indexFingerprint: before.indexFingerprint,
504
+ egressLineage: before.egressLineage,
444
505
  };
445
506
  const plan = await planContextEvidence(
446
507
  { ...input, contextSnapshot: before.contexts, observedAt: null },
@@ -479,7 +540,16 @@ export const compileContextEvidence = async <P>(
479
540
  export const toContextCapsuleEvidence = (
480
541
  candidate: MaterializedContextCandidate<ContextEvidenceValue>,
481
542
  selectionRank: number
482
- ): ContextCapsulePayloadV1["evidence"][number] => {
543
+ ): ContextCapsulePayloadV1_1["evidence"][number] => {
544
+ if (
545
+ candidate.value.egress === "unavailable" ||
546
+ candidate.value.egressLineage === undefined
547
+ ) {
548
+ throw new ContextEvidenceError(
549
+ "collection_load_failed",
550
+ `Policy lineage is unavailable for ${candidate.uri}`
551
+ );
552
+ }
483
553
  const identity = {
484
554
  uri: candidate.uri,
485
555
  docid: candidate.docid,
@@ -493,6 +563,8 @@ export const toContextCapsuleEvidence = (
493
563
  evidenceId: contextCapsuleEvidenceIdentity(identity),
494
564
  ...identity,
495
565
  ...candidate.value,
566
+ egress: candidate.value.egress,
567
+ egressLineage: candidate.value.egressLineage,
496
568
  text: candidate.text,
497
569
  retrievalRank: candidate.retrievalRank,
498
570
  selectionRank,