@gmickel/gno 1.16.0 → 1.18.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 (86) hide show
  1. package/README.md +34 -18
  2. package/assets/skill/SKILL.md +25 -6
  3. package/assets/skill/mcp-reference.md +21 -0
  4. package/package.json +2 -2
  5. package/src/app/context-agent-projection.ts +303 -0
  6. package/src/app/context-format.ts +249 -0
  7. package/src/app/context-runtime-contract.ts +325 -0
  8. package/src/app/context-runtime-input.ts +362 -0
  9. package/src/app/context-runtime-types.ts +65 -0
  10. package/src/app/context-runtime.ts +170 -0
  11. package/src/app/context-surface.ts +145 -0
  12. package/src/cli/commands/context-build.ts +149 -0
  13. package/src/cli/commands/context-verify.ts +90 -0
  14. package/src/cli/commands/daemon.ts +69 -2
  15. package/src/cli/commands/models/pull.ts +13 -3
  16. package/src/cli/commands/status.ts +2 -0
  17. package/src/cli/detach.ts +37 -20
  18. package/src/cli/options.ts +4 -0
  19. package/src/cli/program.ts +252 -27
  20. package/src/config/index.ts +3 -0
  21. package/src/config/types.ts +37 -0
  22. package/src/core/context-budget.ts +461 -0
  23. package/src/core/context-capsule-index-schema.ts +15 -0
  24. package/src/core/context-capsule-retrieval-schema.ts +81 -0
  25. package/src/core/context-capsule-schema.ts +473 -0
  26. package/src/core/context-capsule-validation.ts +416 -0
  27. package/src/core/context-capsule-verification.ts +218 -0
  28. package/src/core/context-capsule.ts +439 -0
  29. package/src/core/context-compiler.ts +513 -0
  30. package/src/core/context-evidence-metadata.ts +33 -0
  31. package/src/core/context-evidence.ts +495 -0
  32. package/src/core/context-facets.ts +163 -0
  33. package/src/core/context-guidance.ts +69 -0
  34. package/src/core/context-scope.ts +32 -0
  35. package/src/core/context-verifier-canonical.ts +90 -0
  36. package/src/core/context-verifier-input.ts +66 -0
  37. package/src/core/context-verifier.ts +447 -0
  38. package/src/core/job-manager.ts +19 -0
  39. package/src/core/mutation-generations.ts +33 -0
  40. package/src/core/sections.ts +63 -0
  41. package/src/llm/cache.ts +13 -3
  42. package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
  43. package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
  44. package/src/mcp/context.ts +161 -0
  45. package/src/mcp/http-security.ts +477 -0
  46. package/src/mcp/http-session.ts +272 -0
  47. package/src/mcp/http-transport.ts +370 -0
  48. package/src/mcp/resources/index.ts +141 -134
  49. package/src/mcp/server.ts +28 -82
  50. package/src/mcp/tools/add-collection.ts +3 -1
  51. package/src/mcp/tools/capture.ts +3 -0
  52. package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
  53. package/src/mcp/tools/context.ts +230 -0
  54. package/src/mcp/tools/embed.ts +62 -52
  55. package/src/mcp/tools/index-cmd.ts +88 -74
  56. package/src/mcp/tools/index.ts +49 -2
  57. package/src/mcp/tools/remove-collection.ts +2 -0
  58. package/src/mcp/tools/status.ts +11 -0
  59. package/src/mcp/tools/sync.ts +16 -14
  60. package/src/mcp/tools/workspace-write.ts +7 -3
  61. package/src/pipeline/chunk-lookup.ts +33 -0
  62. package/src/pipeline/hybrid.ts +79 -57
  63. package/src/pipeline/types.ts +14 -0
  64. package/src/sdk/client.ts +68 -6
  65. package/src/sdk/index.ts +21 -0
  66. package/src/sdk/types.ts +24 -0
  67. package/src/serve/background-runtime.ts +12 -211
  68. package/src/serve/context-capsule.ts +136 -0
  69. package/src/serve/context.ts +10 -1
  70. package/src/serve/embed-scheduler.ts +74 -43
  71. package/src/serve/index.ts +9 -0
  72. package/src/serve/jobs.ts +78 -80
  73. package/src/serve/public/components/HealthCenter.tsx +74 -1
  74. package/src/serve/public/globals.built.css +1 -1
  75. package/src/serve/public/pages/Dashboard.tsx +1 -0
  76. package/src/serve/resident-admission.ts +159 -0
  77. package/src/serve/resident-background-work.ts +39 -0
  78. package/src/serve/resident-request.ts +55 -0
  79. package/src/serve/resident-runtime.ts +490 -0
  80. package/src/serve/resident-status.ts +96 -0
  81. package/src/serve/routes/api.ts +265 -167
  82. package/src/serve/routes/mcp.ts +69 -0
  83. package/src/serve/server.ts +212 -35
  84. package/src/serve/status-model.ts +51 -0
  85. package/src/serve/status.ts +5 -0
  86. package/src/store/sqlite/adapter.ts +64 -29
@@ -0,0 +1,249 @@
1
+ /** Deterministic readable projections for Context Capsule surfaces. */
2
+
3
+ import type {
4
+ ContextCapsuleV1,
5
+ ContextCapsuleVerification,
6
+ } from "../core/context-capsule";
7
+
8
+ import { canonicalContextCapsuleJson } from "../core/context-capsule";
9
+ import { canonicalContextCapsuleVerificationJson } from "../core/context-verifier";
10
+
11
+ const nullable = (value: string | number | null): string =>
12
+ value === null ? "unavailable" : String(value);
13
+ const json = (value: unknown): string => JSON.stringify(value);
14
+ const indentedJson = (value: unknown): string[] =>
15
+ JSON.stringify(value, null, 2)
16
+ .split("\n")
17
+ .map((line) => ` ${line}`);
18
+
19
+ const longestRun = (value: string, character: "`" | "~"): number => {
20
+ let longest = 0;
21
+ let current = 0;
22
+ for (const codePoint of value) {
23
+ if (codePoint === character) {
24
+ current += 1;
25
+ longest = Math.max(longest, current);
26
+ } else {
27
+ current = 0;
28
+ }
29
+ }
30
+ return longest;
31
+ };
32
+
33
+ const untrustedFence = (value: string): string => {
34
+ const backtickLength = Math.max(3, longestRun(value, "`") + 1);
35
+ const tildeLength = Math.max(3, longestRun(value, "~") + 1);
36
+ const character = backtickLength <= tildeLength ? "`" : "~";
37
+ return character.repeat(Math.min(backtickLength, tildeLength));
38
+ };
39
+
40
+ const untrustedBlock = (label: string, value: string): string[] => {
41
+ const fence = untrustedFence(value);
42
+ return [`${fence}${label}`, value, fence];
43
+ };
44
+
45
+ const capabilityLines = (capsule: ContextCapsuleV1): string[] =>
46
+ Object.entries(capsule.retrieval.capabilityStates).flatMap(
47
+ ([capability, state]) => [
48
+ `- ${capability}: ${state.outcome}`,
49
+ ` - requested: ${state.requested}`,
50
+ ` - attempted: ${state.attempted}`,
51
+ ` - fallback reasons: ${
52
+ state.fallbackReasons.length > 0
53
+ ? state.fallbackReasons.join(", ")
54
+ : "none"
55
+ }`,
56
+ ]
57
+ );
58
+
59
+ const configuredContextLines = (
60
+ capsule: ContextCapsuleV1,
61
+ contextIds: readonly string[]
62
+ ): string[] => {
63
+ const contexts = capsule.guidance.configuredContexts.filter((context) =>
64
+ contextIds.includes(context.contextId)
65
+ );
66
+ return contexts.length === 0
67
+ ? [" []"]
68
+ : indentedJson(
69
+ contexts.map(({ contextId, scopeType, scopeKey, text }) => ({
70
+ contextId,
71
+ scopeType,
72
+ scopeKey,
73
+ text,
74
+ }))
75
+ );
76
+ };
77
+
78
+ const evidenceBlock = (
79
+ capsule: ContextCapsuleV1,
80
+ item: ContextCapsuleV1["evidence"][number]
81
+ ): string[] => {
82
+ const metadata = [
83
+ `{"title":${json(item.title)},"heading":${json(item.heading)}}`,
84
+ "configuredContexts:",
85
+ ...configuredContextLines(capsule, item.contextIds).map((line) =>
86
+ line.startsWith(" ") ? line.slice(4) : line
87
+ ),
88
+ ].join("\n");
89
+ return [
90
+ `## Evidence ${item.selectionRank}`,
91
+ "",
92
+ `- Evidence ID: \`${item.evidenceId}\``,
93
+ `- URI: \`${item.uri}\``,
94
+ `- Docid: \`${item.docid}\``,
95
+ `- Collection: \`${item.collection}\``,
96
+ `- Lines: ${item.startLine}-${item.endLine}`,
97
+ `- Retrieval rank: ${item.retrievalRank}`,
98
+ `- Selection rank: ${item.selectionRank}`,
99
+ `- Modified: ${nullable(item.modifiedAt)}`,
100
+ `- Document date: ${nullable(item.documentDate)}`,
101
+ `- Observed: ${nullable(item.observedAt)}`,
102
+ `- Facets: ${item.facets.length > 0 ? item.facets.join(", ") : "none"}`,
103
+ `- Trust: ${item.trust}`,
104
+ `- Egress: ${item.egress}`,
105
+ `- Source hash: \`${item.sourceHash}\``,
106
+ `- Mirror hash: \`${item.mirrorHash}\``,
107
+ `- Passage hash: \`${item.passageHash}\``,
108
+ "",
109
+ ...untrustedBlock(`gno-untrusted-metadata-${item.evidenceId}`, metadata),
110
+ "",
111
+ ...untrustedBlock(`gno-untrusted-evidence-${item.evidenceId}`, item.text),
112
+ "",
113
+ ];
114
+ };
115
+
116
+ const omissionLines = (capsule: ContextCapsuleV1): string[] => [
117
+ `- Total: ${capsule.omissions.total}`,
118
+ `- Visible items: ${capsule.omissions.items.length}`,
119
+ `- Bounded-list truncated: ${capsule.omissions.truncated}`,
120
+ ...Object.entries(capsule.omissions.reasonCounts).map(
121
+ ([reason, count]) => `- ${reason}: ${count}`
122
+ ),
123
+ ];
124
+
125
+ export const formatContextCapsuleMarkdown = (
126
+ capsule: ContextCapsuleV1
127
+ ): string => {
128
+ const lines = [
129
+ "# GNO Context Capsule",
130
+ "",
131
+ `- Schema: ${capsule.schemaVersion}`,
132
+ `- Coordinate space: ${capsule.coordinateSpace}`,
133
+ `- Capsule ID: \`${capsule.capsuleId}\``,
134
+ `- Goal: ${json(capsule.goal)}`,
135
+ `- Query: ${json(capsule.query)}`,
136
+ `- Index: \`${capsule.scope.indexName}\``,
137
+ `- Collections: ${
138
+ capsule.scope.collections.length > 0
139
+ ? capsule.scope.collections.join(", ")
140
+ : "all"
141
+ }`,
142
+ `- URI prefix: ${nullable(capsule.scope.uriPrefix)}`,
143
+ `- Tags all: ${json(capsule.scope.tagsAll)}`,
144
+ `- Tags any: ${json(capsule.scope.tagsAny)}`,
145
+ `- Categories: ${json(capsule.scope.categories)}`,
146
+ `- Since/until: ${nullable(capsule.scope.since)} / ${nullable(capsule.scope.until)}`,
147
+ "",
148
+ "## Budget and retrieval",
149
+ "",
150
+ `- Budget: ${capsule.budget.usedTokens}/${capsule.budget.requestedTokens} tokens; ${capsule.budget.usedBytes}/${capsule.budget.requestedBytes} bytes`,
151
+ `- Safety margin: ${capsule.budget.safetyMarginTokens} tokens; ${capsule.budget.safetyMarginBytes} bytes`,
152
+ `- Estimator: ${capsule.budget.estimator}`,
153
+ `- Tokenizer fingerprint: ${nullable(capsule.budget.tokenizerFingerprint)}`,
154
+ `- Depth: ${capsule.retrieval.depthPolicy}`,
155
+ `- Facets: ${json(capsule.retrieval.facets)}`,
156
+ `- Query variants: ${json(capsule.retrieval.queryVariants)}`,
157
+ `- Request: ${json(capsule.retrieval.request)}`,
158
+ `- Index snapshot: ${json(capsule.retrieval.indexSnapshot)}`,
159
+ "",
160
+ "## Capabilities and fallbacks",
161
+ "",
162
+ ...capabilityLines(capsule),
163
+ `- Effective capabilities: ${json(capsule.capabilities)}`,
164
+ `- Fallbacks: ${json(capsule.fallbacks)}`,
165
+ "",
166
+ "## Fingerprints",
167
+ "",
168
+ ...Object.entries(capsule.fingerprints).map(
169
+ ([name, value]) => `- ${name}: ${nullable(value)}`
170
+ ),
171
+ "",
172
+ ...capsule.evidence.flatMap((item) => evidenceBlock(capsule, item)),
173
+ "## Coverage, omissions, and truncation",
174
+ "",
175
+ `- Coverage complete: ${capsule.coverage.complete}`,
176
+ `- Requested facets: ${json(capsule.coverage.requestedFacets)}`,
177
+ `- Covered facets: ${json(capsule.coverage.coveredFacets)}`,
178
+ `- Unresolved facets: ${json(capsule.coverage.unresolvedFacets)}`,
179
+ `- Gaps: ${json(capsule.coverage.gaps)}`,
180
+ `- Capsule truncated: ${capsule.truncated}`,
181
+ `- Warnings: ${json(capsule.warnings)}`,
182
+ ...omissionLines(capsule),
183
+ `- Omission items: ${json(capsule.omissions.items)}`,
184
+ "",
185
+ "## Canonical manifest",
186
+ "",
187
+ ...untrustedBlock(
188
+ "gno-untrusted-manifest-json",
189
+ JSON.stringify(JSON.parse(canonicalContextCapsuleJson(capsule)), null, 2)
190
+ ),
191
+ "",
192
+ ];
193
+ return lines.join("\n");
194
+ };
195
+
196
+ export const formatContextCapsuleVerificationMarkdown = (
197
+ receipt: ContextCapsuleVerification
198
+ ): string => {
199
+ const lines = [
200
+ "# GNO Context Capsule verification",
201
+ "",
202
+ `- Schema: ${receipt.schemaVersion}`,
203
+ `- Coordinate space: ${receipt.coordinateSpace}`,
204
+ `- Capsule ID: \`${receipt.capsuleId}\``,
205
+ `- Operation: ${receipt.operationStatus}`,
206
+ `- Content: ${receipt.contentStatus} (${receipt.contentCode})`,
207
+ `- Ranking: ${receipt.rankingStatus} (${receipt.rankingCode})`,
208
+ `- Fingerprints: ${receipt.fingerprintStatus}`,
209
+ `- Fingerprint reasons: ${
210
+ receipt.fingerprintReasons.length > 0
211
+ ? receipt.fingerprintReasons.join(", ")
212
+ : "none"
213
+ }`,
214
+ `- Index snapshot: ${json(receipt.indexSnapshot)}`,
215
+ "",
216
+ "## Current fingerprints",
217
+ "",
218
+ ...Object.entries(receipt.currentFingerprints).map(
219
+ ([name, value]) => `- ${name}: ${nullable(value)}`
220
+ ),
221
+ "",
222
+ "## Evidence",
223
+ "",
224
+ ...receipt.evidence.flatMap((item) => [
225
+ `### \`${item.evidenceId}\``,
226
+ "",
227
+ `- URI: \`${item.uri}\``,
228
+ `- Content: ${item.contentStatus} (${item.contentCode})`,
229
+ `- Ranking: ${item.rankingStatus} (${item.rankingCode})`,
230
+ `- Current rank: ${nullable(item.currentRetrievalRank)}`,
231
+ `- Current source hash: ${nullable(item.currentSourceHash)}`,
232
+ `- Current mirror hash: ${nullable(item.currentMirrorHash)}`,
233
+ `- Current passage hash: ${nullable(item.currentPassageHash)}`,
234
+ "",
235
+ ]),
236
+ "## Canonical receipt",
237
+ "",
238
+ ...untrustedBlock(
239
+ "gno-untrusted-receipt-json",
240
+ JSON.stringify(
241
+ JSON.parse(canonicalContextCapsuleVerificationJson(receipt)),
242
+ null,
243
+ 2
244
+ )
245
+ ),
246
+ "",
247
+ ];
248
+ return lines.join("\n");
249
+ };
@@ -0,0 +1,325 @@
1
+ /** Validation, projection, and fingerprint rules for Context runtime surfaces. */
2
+
3
+ import type { ContextCanonicalProjection } from "../core/context-budget";
4
+ import type { ContextCapsuleV1 } from "../core/context-capsule";
5
+ import type { ContextCapabilityState } from "../core/context-capsule-retrieval-schema";
6
+ import type { ContextCapsulePayloadV1 } from "../core/context-capsule-schema";
7
+ import type { ContextCanonicalPlanDraft } from "../core/context-compiler";
8
+ import type { ContextEvidenceValue } from "../core/context-evidence";
9
+ import type { NormalizedContextBuildInput } from "./context-runtime-input";
10
+ import type { ContextCapsuleRuntimeDeps } from "./context-runtime-types";
11
+
12
+ import {
13
+ ContextCapsuleContractError,
14
+ createContextCapsuleV1,
15
+ } from "../core/context-capsule";
16
+ import { sha256Text } from "../core/context-capsule-validation";
17
+ import {
18
+ fingerprintContextRows,
19
+ toContextCapsuleEvidence,
20
+ } from "../core/context-evidence";
21
+ import { canonicalVerifierJson } from "../core/context-verifier-canonical";
22
+ import { resolveModelUri } from "../llm/registry";
23
+
24
+ const fingerprint = (value: unknown): string =>
25
+ sha256Text(canonicalVerifierJson(value));
26
+
27
+ const configFingerprint = (deps: ContextCapsuleRuntimeDeps): string =>
28
+ fingerprint(deps.config);
29
+
30
+ const configuredContextFingerprint = (
31
+ deps: ContextCapsuleRuntimeDeps
32
+ ): string =>
33
+ fingerprintContextRows(
34
+ (deps.config.contexts ?? []).map((context) => ({
35
+ ...context,
36
+ syncedAt: "",
37
+ }))
38
+ );
39
+
40
+ const retrievalFingerprint = (
41
+ capsule: Pick<
42
+ ContextCapsuleV1,
43
+ "goal" | "query" | "scope" | "retrieval" | "capabilities"
44
+ >,
45
+ contextFingerprint: string
46
+ ): string =>
47
+ fingerprint({
48
+ capabilities: capsule.capabilities,
49
+ contextFingerprint,
50
+ goal: capsule.goal,
51
+ query: capsule.query,
52
+ retrieval: capsule.retrieval,
53
+ scope: capsule.scope,
54
+ });
55
+
56
+ const capabilityState = (
57
+ requested: boolean,
58
+ used: boolean,
59
+ unavailableReasons: string[],
60
+ usedReasons: string[] = []
61
+ ): ContextCapabilityState => ({
62
+ requested,
63
+ attempted: requested,
64
+ outcome: used ? "used" : requested ? "unavailable" : "not_requested",
65
+ fallbackReasons: requested ? (used ? usedReasons : unavailableReasons) : [],
66
+ });
67
+
68
+ const capsuleCapabilityStates = (
69
+ draft: ContextCanonicalPlanDraft<ContextEvidenceValue>,
70
+ input: NormalizedContextBuildInput
71
+ ) => ({
72
+ semanticSearch: capabilityState(
73
+ input.depthPolicy !== "fast",
74
+ draft.retrieval.semanticSearch,
75
+ ["embedding_unavailable"]
76
+ ),
77
+ reranking: capabilityState(
78
+ input.depthPolicy !== "fast",
79
+ draft.retrieval.reranked,
80
+ ["reranking_unavailable"]
81
+ ),
82
+ graphExpansion: capabilityState(
83
+ input.graph,
84
+ draft.retrieval.graphExpansion,
85
+ draft.retrieval.graphFallbackReasons.length > 0
86
+ ? draft.retrieval.graphFallbackReasons
87
+ : ["graph_unavailable"],
88
+ draft.retrieval.graphFallbackReasons
89
+ ),
90
+ });
91
+
92
+ const capsuleCapabilities = (
93
+ states: ReturnType<typeof capsuleCapabilityStates>,
94
+ exactTokens: boolean,
95
+ configuredContext: boolean
96
+ ) => ({
97
+ lexicalSearch: true as const,
98
+ semanticSearch: states.semanticSearch.outcome === "used",
99
+ reranking: states.reranking.outcome === "used",
100
+ graphExpansion: states.graphExpansion.outcome === "used",
101
+ exactTokenCount: exactTokens,
102
+ configuredContext,
103
+ egressPolicy: false,
104
+ });
105
+
106
+ const capsuleFallbacks = (
107
+ capabilities: ReturnType<typeof capsuleCapabilities>,
108
+ states: ReturnType<typeof capsuleCapabilityStates>
109
+ ): ContextCapsulePayloadV1["fallbacks"] => [
110
+ ...(states.semanticSearch.outcome === "unavailable"
111
+ ? [
112
+ {
113
+ code: "embedding_unavailable" as const,
114
+ capability: "semantic_search" as const,
115
+ },
116
+ ]
117
+ : []),
118
+ ...(states.reranking.outcome === "unavailable"
119
+ ? [
120
+ {
121
+ code: "reranking_unavailable" as const,
122
+ capability: "reranking" as const,
123
+ },
124
+ ]
125
+ : []),
126
+ ...(states.graphExpansion.outcome === "unavailable"
127
+ ? [
128
+ {
129
+ code: "graph_unavailable" as const,
130
+ capability: "graph_expansion" as const,
131
+ },
132
+ ]
133
+ : []),
134
+ ...(capabilities.exactTokenCount
135
+ ? []
136
+ : [
137
+ {
138
+ code: "tokenizer_unavailable" as const,
139
+ capability: "token_count" as const,
140
+ },
141
+ ]),
142
+ { code: "egress_policy_unavailable", capability: "egress_policy" },
143
+ ];
144
+
145
+ export const projectContextCapsule = (
146
+ draft: ContextCanonicalPlanDraft<ContextEvidenceValue>,
147
+ snapshots: { indexFingerprint: string; contextFingerprint: string },
148
+ input: NormalizedContextBuildInput,
149
+ deps: ContextCapsuleRuntimeDeps
150
+ ): ContextCanonicalProjection<ContextCapsuleV1> | null => {
151
+ if (draft.selection.selected.length === 0) return null;
152
+ const evidence = draft.selection.selected.map((candidate, index) =>
153
+ toContextCapsuleEvidence(candidate, index + 1)
154
+ );
155
+ const evidenceIdsByFacet = new Map<string, string[]>();
156
+ for (const item of evidence) {
157
+ for (const facet of item.facets) {
158
+ const ids = evidenceIdsByFacet.get(facet) ?? [];
159
+ ids.push(item.evidenceId);
160
+ evidenceIdsByFacet.set(facet, ids);
161
+ }
162
+ }
163
+ const exactTokens =
164
+ deps.countTokens !== undefined && deps.tokenizerFingerprint != null;
165
+ const capabilityStates = capsuleCapabilityStates(draft, input);
166
+ const capabilities = capsuleCapabilities(
167
+ capabilityStates,
168
+ exactTokens,
169
+ draft.configuredContexts.length > 0
170
+ );
171
+ const base = {
172
+ schemaVersion: "1.0" as const,
173
+ coordinateSpace: "canonical_mirror" as const,
174
+ goal: draft.goal,
175
+ query: draft.query,
176
+ scope: {
177
+ indexName: draft.indexName,
178
+ collections: draft.collections,
179
+ uriPrefix: draft.uriPrefix,
180
+ tagsAll: input.tagsAll,
181
+ tagsAny: input.tagsAny,
182
+ categories: input.categories,
183
+ since: input.since ?? null,
184
+ until: input.until ?? null,
185
+ },
186
+ retrieval: {
187
+ depthPolicy: input.depthPolicy,
188
+ facets: draft.retrieval.facets,
189
+ queryVariants: draft.retrieval.queryVariants,
190
+ expansionPolicy: "deterministic_only" as const,
191
+ request: {
192
+ author: input.author,
193
+ lang: input.lang,
194
+ queryModes: input.queryModes,
195
+ limit: input.limit,
196
+ candidateLimit: input.candidateLimit,
197
+ graphRequested: input.graph,
198
+ },
199
+ capabilityStates,
200
+ indexSnapshot: {
201
+ before: snapshots.indexFingerprint,
202
+ after: snapshots.indexFingerprint,
203
+ stable: true as const,
204
+ },
205
+ },
206
+ capabilities,
207
+ };
208
+ const payload: ContextCapsulePayloadV1 = {
209
+ ...base,
210
+ budget: {
211
+ authority: "canonical_json",
212
+ requestedTokens: input.budgetTokens,
213
+ requestedBytes: input.budgetBytes,
214
+ safetyMarginTokens: input.safetyMarginTokens,
215
+ safetyMarginBytes: input.safetyMarginBytes,
216
+ usedTokens: 1,
217
+ usedBytes: 0,
218
+ estimator: exactTokens ? "active_tokenizer" : "unicode_conservative",
219
+ tokenizerFingerprint: exactTokens
220
+ ? (deps.tokenizerFingerprint ?? null)
221
+ : null,
222
+ },
223
+ fingerprints: {
224
+ config: configFingerprint(deps),
225
+ retrieval: retrievalFingerprint(base, snapshots.contextFingerprint),
226
+ embeddingModel: capabilities.semanticSearch
227
+ ? sha256Text(deps.embedPort?.modelUri ?? "")
228
+ : null,
229
+ rerankModel: capabilities.reranking
230
+ ? sha256Text(deps.rerankPort?.modelUri ?? "")
231
+ : null,
232
+ tokenizer: exactTokens ? (deps.tokenizerFingerprint ?? null) : null,
233
+ },
234
+ fallbacks: capsuleFallbacks(capabilities, capabilityStates),
235
+ guidance: {
236
+ extractiveOnly: true,
237
+ evidenceTrust: "untrusted_data",
238
+ instructionBoundary: "hard_delimited",
239
+ configuredContexts: draft.configuredContexts,
240
+ },
241
+ evidence,
242
+ coverage: {
243
+ complete: draft.selection.coverage.unresolvedFacets.length === 0,
244
+ requestedFacets: draft.retrieval.facets,
245
+ coveredFacets: draft.selection.coverage.coveredFacets.map((facet) => ({
246
+ facet,
247
+ evidenceIds: evidenceIdsByFacet.get(facet) ?? [],
248
+ })),
249
+ unresolvedFacets: draft.selection.coverage.unresolvedFacets,
250
+ gaps: draft.selection.coverage.gaps,
251
+ },
252
+ omissions: {
253
+ total: draft.selection.omissions.length,
254
+ items: draft.selection.omissions.slice(0, 20),
255
+ reasonCounts: draft.selection.reasonCounts,
256
+ truncated: draft.selection.omissions.length > 20,
257
+ },
258
+ truncated: draft.selection.omissions.some(
259
+ (item) => item.reason === "global_budget"
260
+ ),
261
+ warnings: [
262
+ ...(draft.selection.coverage.unresolvedFacets.length > 0
263
+ ? [{ code: "incomplete_coverage" as const }]
264
+ : []),
265
+ ...(draft.selection.omissions.length > 20
266
+ ? [{ code: "omissions_truncated" as const }]
267
+ : []),
268
+ ...(exactTokens ? [] : [{ code: "token_estimate_used" as const }]),
269
+ ],
270
+ };
271
+ try {
272
+ const value = createContextCapsuleV1(payload, {
273
+ countTokens: deps.countTokens,
274
+ });
275
+ return {
276
+ value,
277
+ usedBytes: value.budget.usedBytes,
278
+ usedTokens: value.budget.usedTokens,
279
+ };
280
+ } catch (error) {
281
+ if (
282
+ error instanceof ContextCapsuleContractError &&
283
+ error.code === "invalid_budget"
284
+ ) {
285
+ return null;
286
+ }
287
+ throw error;
288
+ }
289
+ };
290
+
291
+ export const currentContextFingerprints = (
292
+ capsule: ContextCapsuleV1,
293
+ deps: ContextCapsuleRuntimeDeps
294
+ ) => ({
295
+ config: configFingerprint(deps),
296
+ retrieval: retrievalFingerprint(capsule, configuredContextFingerprint(deps)),
297
+ embeddingModel: capsule.capabilities.semanticSearch
298
+ ? sha256Text(
299
+ resolveModelUri(
300
+ deps.config,
301
+ "embed",
302
+ undefined,
303
+ capsule.scope.collections.length === 1
304
+ ? capsule.scope.collections[0]
305
+ : undefined
306
+ )
307
+ )
308
+ : null,
309
+ rerankModel: capsule.capabilities.reranking
310
+ ? sha256Text(
311
+ resolveModelUri(
312
+ deps.config,
313
+ "rerank",
314
+ undefined,
315
+ capsule.scope.collections.length === 1
316
+ ? capsule.scope.collections[0]
317
+ : undefined
318
+ )
319
+ )
320
+ : null,
321
+ tokenizer:
322
+ capsule.capabilities.exactTokenCount && deps.tokenizerFingerprint
323
+ ? deps.tokenizerFingerprint
324
+ : null,
325
+ });