@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.
- package/README.md +34 -18
- package/assets/skill/SKILL.md +25 -6
- package/assets/skill/mcp-reference.md +21 -0
- package/package.json +2 -2
- package/src/app/context-agent-projection.ts +303 -0
- package/src/app/context-format.ts +249 -0
- package/src/app/context-runtime-contract.ts +325 -0
- package/src/app/context-runtime-input.ts +362 -0
- package/src/app/context-runtime-types.ts +65 -0
- package/src/app/context-runtime.ts +170 -0
- package/src/app/context-surface.ts +145 -0
- package/src/cli/commands/context-build.ts +149 -0
- package/src/cli/commands/context-verify.ts +90 -0
- package/src/cli/commands/daemon.ts +69 -2
- package/src/cli/commands/models/pull.ts +13 -3
- package/src/cli/commands/status.ts +2 -0
- package/src/cli/detach.ts +37 -20
- package/src/cli/options.ts +4 -0
- package/src/cli/program.ts +252 -27
- package/src/config/index.ts +3 -0
- package/src/config/types.ts +37 -0
- package/src/core/context-budget.ts +461 -0
- package/src/core/context-capsule-index-schema.ts +15 -0
- package/src/core/context-capsule-retrieval-schema.ts +81 -0
- package/src/core/context-capsule-schema.ts +473 -0
- package/src/core/context-capsule-validation.ts +416 -0
- package/src/core/context-capsule-verification.ts +218 -0
- package/src/core/context-capsule.ts +439 -0
- package/src/core/context-compiler.ts +513 -0
- package/src/core/context-evidence-metadata.ts +33 -0
- package/src/core/context-evidence.ts +495 -0
- package/src/core/context-facets.ts +163 -0
- package/src/core/context-guidance.ts +69 -0
- package/src/core/context-scope.ts +32 -0
- package/src/core/context-verifier-canonical.ts +90 -0
- package/src/core/context-verifier-input.ts +66 -0
- package/src/core/context-verifier.ts +447 -0
- package/src/core/job-manager.ts +19 -0
- package/src/core/mutation-generations.ts +33 -0
- package/src/core/sections.ts +63 -0
- package/src/llm/cache.ts +13 -3
- package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
- package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
- package/src/mcp/context.ts +161 -0
- package/src/mcp/http-security.ts +477 -0
- package/src/mcp/http-session.ts +272 -0
- package/src/mcp/http-transport.ts +370 -0
- package/src/mcp/resources/index.ts +141 -134
- package/src/mcp/server.ts +28 -82
- package/src/mcp/tools/add-collection.ts +3 -1
- package/src/mcp/tools/capture.ts +3 -0
- package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
- package/src/mcp/tools/context.ts +230 -0
- package/src/mcp/tools/embed.ts +62 -52
- package/src/mcp/tools/index-cmd.ts +88 -74
- package/src/mcp/tools/index.ts +49 -2
- package/src/mcp/tools/remove-collection.ts +2 -0
- package/src/mcp/tools/status.ts +11 -0
- package/src/mcp/tools/sync.ts +16 -14
- package/src/mcp/tools/workspace-write.ts +7 -3
- package/src/pipeline/chunk-lookup.ts +33 -0
- package/src/pipeline/hybrid.ts +79 -57
- package/src/pipeline/types.ts +14 -0
- package/src/sdk/client.ts +68 -6
- package/src/sdk/index.ts +21 -0
- package/src/sdk/types.ts +24 -0
- package/src/serve/background-runtime.ts +12 -211
- package/src/serve/context-capsule.ts +136 -0
- package/src/serve/context.ts +10 -1
- package/src/serve/embed-scheduler.ts +74 -43
- package/src/serve/index.ts +9 -0
- package/src/serve/jobs.ts +78 -80
- package/src/serve/public/components/HealthCenter.tsx +74 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Dashboard.tsx +1 -0
- package/src/serve/resident-admission.ts +159 -0
- package/src/serve/resident-background-work.ts +39 -0
- package/src/serve/resident-request.ts +55 -0
- package/src/serve/resident-runtime.ts +490 -0
- package/src/serve/resident-status.ts +96 -0
- package/src/serve/routes/api.ts +265 -167
- package/src/serve/routes/mcp.ts +69 -0
- package/src/serve/server.ts +212 -35
- package/src/serve/status-model.ts +51 -0
- package/src/serve/status.ts +5 -0
- package/src/store/sqlite/adapter.ts +64 -29
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import type { RefinementCtx } from "zod";
|
|
2
|
+
|
|
3
|
+
import type { ContextCapsulePayloadV1 } from "./context-capsule-schema";
|
|
4
|
+
|
|
5
|
+
import { DEFAULT_INDEX_NAME, parseUri } from "../app/constants";
|
|
6
|
+
import { canonicalizeIndexName } from "../app/index-name";
|
|
7
|
+
|
|
8
|
+
export const sha256Text = (value: string): string =>
|
|
9
|
+
new Bun.CryptoHasher("sha256").update(value).digest("hex");
|
|
10
|
+
|
|
11
|
+
export const contextCapsuleContextIdentity = (value: {
|
|
12
|
+
scopeType: "global" | "collection" | "prefix";
|
|
13
|
+
scopeKey: string;
|
|
14
|
+
text: string;
|
|
15
|
+
}): string =>
|
|
16
|
+
sha256Text(
|
|
17
|
+
JSON.stringify({
|
|
18
|
+
scopeKey: value.scopeKey,
|
|
19
|
+
scopeType: value.scopeType,
|
|
20
|
+
text: value.text,
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export const contextCapsuleEvidenceIdentity = (value: {
|
|
25
|
+
uri: string;
|
|
26
|
+
docid: string;
|
|
27
|
+
startLine: number;
|
|
28
|
+
endLine: number;
|
|
29
|
+
sourceHash: string;
|
|
30
|
+
mirrorHash: string;
|
|
31
|
+
passageHash: string;
|
|
32
|
+
}): string =>
|
|
33
|
+
sha256Text(
|
|
34
|
+
JSON.stringify({
|
|
35
|
+
docid: value.docid,
|
|
36
|
+
endLine: value.endLine,
|
|
37
|
+
mirrorHash: value.mirrorHash,
|
|
38
|
+
passageHash: value.passageHash,
|
|
39
|
+
sourceHash: value.sourceHash,
|
|
40
|
+
startLine: value.startLine,
|
|
41
|
+
uri: value.uri,
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export const contextCapsuleOmissionIdentity = (value: {
|
|
46
|
+
uri: string;
|
|
47
|
+
docid: string;
|
|
48
|
+
startLine: number | null;
|
|
49
|
+
endLine: number | null;
|
|
50
|
+
passageHash: string | null;
|
|
51
|
+
sourceHash: string;
|
|
52
|
+
mirrorHash: string;
|
|
53
|
+
}): string =>
|
|
54
|
+
sha256Text(
|
|
55
|
+
JSON.stringify({
|
|
56
|
+
docid: value.docid,
|
|
57
|
+
endLine: value.endLine,
|
|
58
|
+
mirrorHash: value.mirrorHash,
|
|
59
|
+
passageHash: value.passageHash,
|
|
60
|
+
sourceHash: value.sourceHash,
|
|
61
|
+
startLine: value.startLine,
|
|
62
|
+
uri: value.uri,
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const unique = (values: readonly string[]): boolean =>
|
|
67
|
+
new Set(values).size === values.length;
|
|
68
|
+
|
|
69
|
+
const sameValues = (
|
|
70
|
+
left: readonly string[],
|
|
71
|
+
right: readonly string[]
|
|
72
|
+
): boolean =>
|
|
73
|
+
left.length === right.length && left.every((value) => right.includes(value));
|
|
74
|
+
|
|
75
|
+
const uriIndex = (indexName?: string): string =>
|
|
76
|
+
canonicalizeIndexName(indexName ?? DEFAULT_INDEX_NAME);
|
|
77
|
+
|
|
78
|
+
const isWithinPrefix = (
|
|
79
|
+
uri: NonNullable<ReturnType<typeof parseUri>>,
|
|
80
|
+
prefix: NonNullable<ReturnType<typeof parseUri>>
|
|
81
|
+
): boolean =>
|
|
82
|
+
uri.collection === prefix.collection &&
|
|
83
|
+
uriIndex(uri.indexName) === uriIndex(prefix.indexName) &&
|
|
84
|
+
(prefix.path === "" ||
|
|
85
|
+
uri.path === prefix.path ||
|
|
86
|
+
uri.path.startsWith(`${prefix.path}/`));
|
|
87
|
+
|
|
88
|
+
const validateCapabilityBindings = (
|
|
89
|
+
value: ContextCapsulePayloadV1,
|
|
90
|
+
fallbackKeys: Set<string>,
|
|
91
|
+
context: RefinementCtx
|
|
92
|
+
): void => {
|
|
93
|
+
const bindings = [
|
|
94
|
+
[
|
|
95
|
+
"semanticSearch",
|
|
96
|
+
"semanticSearch",
|
|
97
|
+
"embedding_unavailable",
|
|
98
|
+
"embeddingModel",
|
|
99
|
+
],
|
|
100
|
+
["reranking", "reranking", "reranking_unavailable", "rerankModel"],
|
|
101
|
+
["graphExpansion", "graphExpansion", "graph_unavailable", null],
|
|
102
|
+
] as const;
|
|
103
|
+
const expectedFallbackCapabilities = new Map([
|
|
104
|
+
["embedding_unavailable", "semantic_search"],
|
|
105
|
+
["reranking_unavailable", "reranking"],
|
|
106
|
+
["graph_unavailable", "graph_expansion"],
|
|
107
|
+
["tokenizer_unavailable", "token_count"],
|
|
108
|
+
["egress_policy_unavailable", "egress_policy"],
|
|
109
|
+
]);
|
|
110
|
+
if (
|
|
111
|
+
!unique(value.fallbacks.map((item) => item.code)) ||
|
|
112
|
+
value.fallbacks.some(
|
|
113
|
+
(item) => expectedFallbackCapabilities.get(item.code) !== item.capability
|
|
114
|
+
)
|
|
115
|
+
) {
|
|
116
|
+
context.addIssue({
|
|
117
|
+
code: "custom",
|
|
118
|
+
message: "invalid or duplicate fallback binding",
|
|
119
|
+
path: ["fallbacks"],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
for (const [capability, stateKey, fallback, fingerprint] of bindings) {
|
|
123
|
+
const state = value.retrieval.capabilityStates[stateKey];
|
|
124
|
+
const used = state.outcome === "used";
|
|
125
|
+
const unavailable = state.outcome === "unavailable";
|
|
126
|
+
if (value.capabilities[capability] !== used) {
|
|
127
|
+
context.addIssue({
|
|
128
|
+
code: "custom",
|
|
129
|
+
message: `${capability} must reflect its retrieval outcome`,
|
|
130
|
+
path: ["capabilities", capability],
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (fallbackKeys.has(fallback) !== unavailable) {
|
|
134
|
+
context.addIssue({
|
|
135
|
+
code: "custom",
|
|
136
|
+
message: `${fallback} must describe attempted unavailable retrieval only`,
|
|
137
|
+
path: ["fallbacks"],
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (fingerprint && (value.fingerprints[fingerprint] !== null) !== used) {
|
|
141
|
+
context.addIssue({
|
|
142
|
+
code: "custom",
|
|
143
|
+
message: `${fingerprint} must be present exactly when ${capability} is available`,
|
|
144
|
+
path: ["fingerprints", fingerprint],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (
|
|
149
|
+
fallbackKeys.has("egress_policy_unavailable") ===
|
|
150
|
+
value.capabilities.egressPolicy
|
|
151
|
+
) {
|
|
152
|
+
context.addIssue({
|
|
153
|
+
code: "custom",
|
|
154
|
+
message: "egress fallback must reflect policy availability",
|
|
155
|
+
path: ["fallbacks"],
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (value.fingerprints.tokenizer !== value.budget.tokenizerFingerprint) {
|
|
159
|
+
context.addIssue({
|
|
160
|
+
code: "custom",
|
|
161
|
+
message: "tokenizer fingerprints disagree",
|
|
162
|
+
path: ["fingerprints", "tokenizer"],
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const validateCoverageBindings = (
|
|
168
|
+
value: ContextCapsulePayloadV1,
|
|
169
|
+
context: RefinementCtx
|
|
170
|
+
): void => {
|
|
171
|
+
const requested = new Set(value.coverage.requestedFacets);
|
|
172
|
+
const covered = value.coverage.coveredFacets.map((item) => item.facet);
|
|
173
|
+
const unresolved = value.coverage.unresolvedFacets;
|
|
174
|
+
const evidenceById = new Map(
|
|
175
|
+
value.evidence.map((item) => [item.evidenceId, item])
|
|
176
|
+
);
|
|
177
|
+
const contextsById = new Map(
|
|
178
|
+
value.guidance.configuredContexts.map((item) => [item.contextId, item])
|
|
179
|
+
);
|
|
180
|
+
const partitions = [...covered, ...unresolved];
|
|
181
|
+
if (!sameValues(value.retrieval.facets, value.coverage.requestedFacets)) {
|
|
182
|
+
context.addIssue({
|
|
183
|
+
code: "custom",
|
|
184
|
+
message: "retrieval facets must exactly match requested coverage facets",
|
|
185
|
+
path: ["retrieval", "facets"],
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (
|
|
189
|
+
!unique(value.coverage.requestedFacets) ||
|
|
190
|
+
!unique(covered) ||
|
|
191
|
+
!unique(unresolved) ||
|
|
192
|
+
partitions.length !== requested.size ||
|
|
193
|
+
partitions.some((facet) => !requested.has(facet))
|
|
194
|
+
) {
|
|
195
|
+
context.addIssue({
|
|
196
|
+
code: "custom",
|
|
197
|
+
message:
|
|
198
|
+
"covered and unresolved facets must uniquely partition requested facets",
|
|
199
|
+
path: ["coverage"],
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (!unique(value.evidence.map((item) => item.evidenceId))) {
|
|
203
|
+
context.addIssue({
|
|
204
|
+
code: "custom",
|
|
205
|
+
message: "duplicate evidenceId",
|
|
206
|
+
path: ["evidence"],
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const configuredContextsValid =
|
|
210
|
+
unique(value.guidance.configuredContexts.map((item) => item.contextId)) &&
|
|
211
|
+
value.guidance.configuredContexts.every(
|
|
212
|
+
(item) => contextCapsuleContextIdentity(item) === item.contextId
|
|
213
|
+
);
|
|
214
|
+
if (!configuredContextsValid) {
|
|
215
|
+
context.addIssue({
|
|
216
|
+
code: "custom",
|
|
217
|
+
message: "configured context IDs must be unique and content-bound",
|
|
218
|
+
path: ["guidance", "configuredContexts"],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
for (const [index, evidence] of value.evidence.entries()) {
|
|
222
|
+
const parsedUri = parseUri(evidence.uri);
|
|
223
|
+
const prefix =
|
|
224
|
+
value.scope.uriPrefix === null ? null : parseUri(value.scope.uriPrefix);
|
|
225
|
+
const inScope =
|
|
226
|
+
parsedUri !== null &&
|
|
227
|
+
evidence.collection === parsedUri.collection &&
|
|
228
|
+
uriIndex(parsedUri.indexName) ===
|
|
229
|
+
canonicalizeIndexName(value.scope.indexName) &&
|
|
230
|
+
(value.scope.collections.length === 0 ||
|
|
231
|
+
value.scope.collections.includes(parsedUri.collection)) &&
|
|
232
|
+
(prefix === null || isWithinPrefix(parsedUri, prefix));
|
|
233
|
+
if (!inScope) {
|
|
234
|
+
context.addIssue({
|
|
235
|
+
code: "custom",
|
|
236
|
+
message:
|
|
237
|
+
"evidence URI, collection, index, and requested scope disagree",
|
|
238
|
+
path: ["evidence", index, "uri"],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
if (evidence.selectionRank !== index + 1) {
|
|
242
|
+
context.addIssue({
|
|
243
|
+
code: "custom",
|
|
244
|
+
message: "selectionRank must match deterministic evidence order",
|
|
245
|
+
path: ["evidence", index, "selectionRank"],
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (
|
|
249
|
+
!unique(evidence.facets) ||
|
|
250
|
+
evidence.facets.some((facet) => !requested.has(facet))
|
|
251
|
+
) {
|
|
252
|
+
context.addIssue({
|
|
253
|
+
code: "custom",
|
|
254
|
+
message: "evidence facets must be unique requested facets",
|
|
255
|
+
path: ["evidence", index, "facets"],
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
const contextScopesApply = evidence.contextIds.every((contextId) => {
|
|
259
|
+
const configured = contextsById.get(contextId);
|
|
260
|
+
if (!configured || !parsedUri) return false;
|
|
261
|
+
if (configured.scopeType === "global") return true;
|
|
262
|
+
if (configured.scopeType === "collection") {
|
|
263
|
+
return configured.scopeKey === `${parsedUri.collection}:`;
|
|
264
|
+
}
|
|
265
|
+
const contextPrefix = parseUri(configured.scopeKey);
|
|
266
|
+
return contextPrefix !== null && isWithinPrefix(parsedUri, contextPrefix);
|
|
267
|
+
});
|
|
268
|
+
if (!contextScopesApply) {
|
|
269
|
+
context.addIssue({
|
|
270
|
+
code: "custom",
|
|
271
|
+
message: "evidence context references must apply to its URI scope",
|
|
272
|
+
path: ["evidence", index, "contextIds"],
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
if (
|
|
276
|
+
!unique(evidence.contextIds) ||
|
|
277
|
+
evidence.contextIds.some((contextId) => !contextsById.has(contextId))
|
|
278
|
+
) {
|
|
279
|
+
context.addIssue({
|
|
280
|
+
code: "custom",
|
|
281
|
+
message:
|
|
282
|
+
"evidence context references must bind trusted configured guidance",
|
|
283
|
+
path: ["evidence", index, "contextIds"],
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
if (!unique(value.retrieval.queryVariants)) {
|
|
288
|
+
context.addIssue({
|
|
289
|
+
code: "custom",
|
|
290
|
+
message: "query variants must be unique while preserving plan order",
|
|
291
|
+
path: ["retrieval", "queryVariants"],
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
for (const [index, item] of value.coverage.coveredFacets.entries()) {
|
|
295
|
+
const validReferences =
|
|
296
|
+
unique(item.evidenceIds) &&
|
|
297
|
+
item.evidenceIds.every((id) =>
|
|
298
|
+
evidenceById.get(id)?.facets.includes(item.facet)
|
|
299
|
+
);
|
|
300
|
+
if (!validReferences) {
|
|
301
|
+
context.addIssue({
|
|
302
|
+
code: "custom",
|
|
303
|
+
message:
|
|
304
|
+
"covered facet references must bind evidence carrying that facet",
|
|
305
|
+
path: ["coverage", "coveredFacets", index, "evidenceIds"],
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const gapFacets = value.coverage.gaps.map((item) => item.facet);
|
|
310
|
+
if (
|
|
311
|
+
!unique(gapFacets) ||
|
|
312
|
+
unresolved.some((facet) => !gapFacets.includes(facet)) ||
|
|
313
|
+
gapFacets.some((facet) => !unresolved.includes(facet))
|
|
314
|
+
) {
|
|
315
|
+
context.addIssue({
|
|
316
|
+
code: "custom",
|
|
317
|
+
message: "each unresolved facet requires exactly one gap",
|
|
318
|
+
path: ["coverage", "gaps"],
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
const complete = unresolved.length === 0 && value.coverage.gaps.length === 0;
|
|
322
|
+
if (value.coverage.complete !== complete) {
|
|
323
|
+
context.addIssue({
|
|
324
|
+
code: "custom",
|
|
325
|
+
message: "complete must reflect unresolved facets and gaps",
|
|
326
|
+
path: ["coverage", "complete"],
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
export const validateContextCapsulePayload = (
|
|
332
|
+
value: ContextCapsulePayloadV1,
|
|
333
|
+
context: RefinementCtx
|
|
334
|
+
): void => {
|
|
335
|
+
const fallbackKeys = new Set(value.fallbacks.map((item) => item.code));
|
|
336
|
+
const exactTokenizer =
|
|
337
|
+
value.budget.estimator === "active_tokenizer" &&
|
|
338
|
+
value.budget.tokenizerFingerprint !== null &&
|
|
339
|
+
value.capabilities.exactTokenCount &&
|
|
340
|
+
!fallbackKeys.has("tokenizer_unavailable");
|
|
341
|
+
const fallbackTokenizer =
|
|
342
|
+
value.budget.estimator === "unicode_conservative" &&
|
|
343
|
+
value.budget.tokenizerFingerprint === null &&
|
|
344
|
+
!value.capabilities.exactTokenCount &&
|
|
345
|
+
fallbackKeys.has("tokenizer_unavailable");
|
|
346
|
+
if (!(exactTokenizer || fallbackTokenizer)) {
|
|
347
|
+
context.addIssue({
|
|
348
|
+
code: "custom",
|
|
349
|
+
message:
|
|
350
|
+
"token estimator authority, capability, fingerprint, and fallback disagree",
|
|
351
|
+
path: ["budget", "estimator"],
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
const semanticRequested = value.retrieval.depthPolicy !== "fast";
|
|
355
|
+
if (
|
|
356
|
+
value.retrieval.capabilityStates.semanticSearch.requested !==
|
|
357
|
+
semanticRequested ||
|
|
358
|
+
value.retrieval.capabilityStates.reranking.requested !==
|
|
359
|
+
semanticRequested ||
|
|
360
|
+
value.retrieval.capabilityStates.graphExpansion.requested !==
|
|
361
|
+
value.retrieval.request.graphRequested
|
|
362
|
+
) {
|
|
363
|
+
context.addIssue({
|
|
364
|
+
code: "custom",
|
|
365
|
+
message: "retrieval requests and capability states disagree",
|
|
366
|
+
path: ["retrieval", "capabilityStates"],
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
validateCapabilityBindings(value, fallbackKeys, context);
|
|
370
|
+
validateCoverageBindings(value, context);
|
|
371
|
+
const warningKeys = value.warnings.map((item) => item.code);
|
|
372
|
+
const warningSet = new Set(warningKeys);
|
|
373
|
+
if (!unique(warningKeys)) {
|
|
374
|
+
context.addIssue({
|
|
375
|
+
code: "custom",
|
|
376
|
+
message: "warning codes must be unique",
|
|
377
|
+
path: ["warnings"],
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
const expectedWarnings = new Set<string>();
|
|
381
|
+
if (!value.coverage.complete) expectedWarnings.add("incomplete_coverage");
|
|
382
|
+
if (value.omissions.truncated) expectedWarnings.add("omissions_truncated");
|
|
383
|
+
if (fallbackTokenizer) expectedWarnings.add("token_estimate_used");
|
|
384
|
+
if (
|
|
385
|
+
warningSet.size !== expectedWarnings.size ||
|
|
386
|
+
[...warningSet].some((warning) => !expectedWarnings.has(warning))
|
|
387
|
+
) {
|
|
388
|
+
context.addIssue({
|
|
389
|
+
code: "custom",
|
|
390
|
+
message: "warnings must exactly describe deterministic fallback state",
|
|
391
|
+
path: ["warnings"],
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
if (
|
|
395
|
+
(!value.capabilities.egressPolicy &&
|
|
396
|
+
value.evidence.some((item) => item.egress !== "unavailable")) ||
|
|
397
|
+
(value.capabilities.egressPolicy &&
|
|
398
|
+
value.evidence.some((item) => item.egress === "unavailable"))
|
|
399
|
+
) {
|
|
400
|
+
context.addIssue({
|
|
401
|
+
code: "custom",
|
|
402
|
+
message: "evidence egress classification must match policy availability",
|
|
403
|
+
path: ["evidence"],
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
const hasGlobalBudget = value.omissions.items.some(
|
|
407
|
+
(item) => item.reason === "global_budget"
|
|
408
|
+
);
|
|
409
|
+
if (value.truncated !== (value.omissions.truncated || hasGlobalBudget)) {
|
|
410
|
+
context.addIssue({
|
|
411
|
+
code: "custom",
|
|
412
|
+
message: "truncated must reflect bounded or budget-omitted candidates",
|
|
413
|
+
path: ["truncated"],
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CONTEXT_CAPSULE_COORDINATE_SPACE,
|
|
5
|
+
CONTEXT_CAPSULE_SCHEMA_VERSION,
|
|
6
|
+
contextCapsuleGnoUriSchema,
|
|
7
|
+
contextCapsuleIndexSnapshotSchema,
|
|
8
|
+
} from "./context-capsule-schema";
|
|
9
|
+
|
|
10
|
+
const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
11
|
+
const nullableSha256Schema = sha256Schema.nullable();
|
|
12
|
+
const positiveIntegerSchema = z.number().int().positive();
|
|
13
|
+
|
|
14
|
+
export const CONTEXT_CAPSULE_FINGERPRINT_DRIFT_REASONS = [
|
|
15
|
+
"config_changed",
|
|
16
|
+
"retrieval_changed",
|
|
17
|
+
"embedding_model_changed",
|
|
18
|
+
"rerank_model_changed",
|
|
19
|
+
"tokenizer_changed",
|
|
20
|
+
"index_changed",
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
const fingerprintDriftReasonSchema = z.enum(
|
|
24
|
+
CONTEXT_CAPSULE_FINGERPRINT_DRIFT_REASONS
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
export const contextCapsuleCurrentFingerprintsSchema = z
|
|
28
|
+
.object({
|
|
29
|
+
config: sha256Schema,
|
|
30
|
+
retrieval: sha256Schema,
|
|
31
|
+
embeddingModel: nullableSha256Schema,
|
|
32
|
+
rerankModel: nullableSha256Schema,
|
|
33
|
+
tokenizer: nullableSha256Schema,
|
|
34
|
+
index: sha256Schema,
|
|
35
|
+
})
|
|
36
|
+
.strict();
|
|
37
|
+
|
|
38
|
+
export const contextCapsuleVerificationEvidenceSchema = z
|
|
39
|
+
.object({
|
|
40
|
+
evidenceId: sha256Schema,
|
|
41
|
+
uri: contextCapsuleGnoUriSchema,
|
|
42
|
+
contentStatus: z.enum(["unchanged", "stale", "missing"]),
|
|
43
|
+
contentCode: z.enum([
|
|
44
|
+
"verified_unchanged",
|
|
45
|
+
"source_stale",
|
|
46
|
+
"mirror_stale",
|
|
47
|
+
"mirror_corrupt",
|
|
48
|
+
"passage_stale",
|
|
49
|
+
"chunk_missing",
|
|
50
|
+
"chunk_corrupt",
|
|
51
|
+
"source_missing",
|
|
52
|
+
"mirror_missing",
|
|
53
|
+
]),
|
|
54
|
+
rankingStatus: z.enum(["unchanged", "reranked", "unavailable"]),
|
|
55
|
+
rankingCode: z.enum([
|
|
56
|
+
"ranking_unchanged",
|
|
57
|
+
"ranking_changed",
|
|
58
|
+
"ranking_unavailable",
|
|
59
|
+
]),
|
|
60
|
+
currentSourceHash: nullableSha256Schema,
|
|
61
|
+
currentMirrorHash: nullableSha256Schema,
|
|
62
|
+
currentPassageHash: nullableSha256Schema,
|
|
63
|
+
currentRetrievalRank: positiveIntegerSchema.nullable(),
|
|
64
|
+
})
|
|
65
|
+
.strict()
|
|
66
|
+
.superRefine((value, context) => {
|
|
67
|
+
const hashes = [
|
|
68
|
+
value.currentSourceHash,
|
|
69
|
+
value.currentMirrorHash,
|
|
70
|
+
value.currentPassageHash,
|
|
71
|
+
];
|
|
72
|
+
const contentValid =
|
|
73
|
+
(value.contentStatus === "unchanged" &&
|
|
74
|
+
value.contentCode === "verified_unchanged" &&
|
|
75
|
+
hashes.every((hash) => hash !== null)) ||
|
|
76
|
+
(value.contentStatus === "stale" &&
|
|
77
|
+
[
|
|
78
|
+
"source_stale",
|
|
79
|
+
"mirror_stale",
|
|
80
|
+
"mirror_corrupt",
|
|
81
|
+
"passage_stale",
|
|
82
|
+
"chunk_missing",
|
|
83
|
+
"chunk_corrupt",
|
|
84
|
+
].includes(value.contentCode) &&
|
|
85
|
+
hashes.every((hash) => hash !== null)) ||
|
|
86
|
+
(value.contentStatus === "missing" &&
|
|
87
|
+
value.contentCode === "source_missing" &&
|
|
88
|
+
hashes.every((hash) => hash === null)) ||
|
|
89
|
+
(value.contentStatus === "missing" &&
|
|
90
|
+
value.contentCode === "mirror_missing" &&
|
|
91
|
+
value.currentSourceHash !== null &&
|
|
92
|
+
value.currentPassageHash === null);
|
|
93
|
+
const rankingValid =
|
|
94
|
+
(value.rankingStatus === "unchanged" &&
|
|
95
|
+
value.rankingCode === "ranking_unchanged" &&
|
|
96
|
+
value.currentRetrievalRank !== null) ||
|
|
97
|
+
(value.rankingStatus === "reranked" &&
|
|
98
|
+
value.rankingCode === "ranking_changed" &&
|
|
99
|
+
value.currentRetrievalRank !== null) ||
|
|
100
|
+
(value.rankingStatus === "unavailable" &&
|
|
101
|
+
value.rankingCode === "ranking_unavailable" &&
|
|
102
|
+
value.currentRetrievalRank === null);
|
|
103
|
+
if (!contentValid) {
|
|
104
|
+
context.addIssue({
|
|
105
|
+
code: "custom",
|
|
106
|
+
message: "impossible content result",
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
!rankingValid ||
|
|
111
|
+
(value.contentStatus !== "unchanged" &&
|
|
112
|
+
value.rankingStatus !== "unavailable")
|
|
113
|
+
) {
|
|
114
|
+
context.addIssue({
|
|
115
|
+
code: "custom",
|
|
116
|
+
message: "impossible ranking result",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export const contextCapsuleVerificationSchema = z
|
|
122
|
+
.object({
|
|
123
|
+
schemaVersion: z.literal(CONTEXT_CAPSULE_SCHEMA_VERSION),
|
|
124
|
+
coordinateSpace: z.literal(CONTEXT_CAPSULE_COORDINATE_SPACE),
|
|
125
|
+
capsuleId: sha256Schema,
|
|
126
|
+
operationStatus: z.literal("completed"),
|
|
127
|
+
contentStatus: z.enum(["unchanged", "stale", "missing"]),
|
|
128
|
+
contentCode: z.enum([
|
|
129
|
+
"verified_unchanged",
|
|
130
|
+
"content_stale",
|
|
131
|
+
"content_missing",
|
|
132
|
+
]),
|
|
133
|
+
rankingStatus: z.enum(["unchanged", "reranked", "unavailable"]),
|
|
134
|
+
rankingCode: z.enum([
|
|
135
|
+
"ranking_unchanged",
|
|
136
|
+
"ranking_changed",
|
|
137
|
+
"ranking_unavailable",
|
|
138
|
+
]),
|
|
139
|
+
currentFingerprints: contextCapsuleCurrentFingerprintsSchema,
|
|
140
|
+
fingerprintStatus: z.enum(["unchanged", "drifted"]),
|
|
141
|
+
fingerprintReasons: z.array(fingerprintDriftReasonSchema),
|
|
142
|
+
indexSnapshot: contextCapsuleIndexSnapshotSchema,
|
|
143
|
+
evidence: z.array(contextCapsuleVerificationEvidenceSchema).min(1),
|
|
144
|
+
})
|
|
145
|
+
.strict()
|
|
146
|
+
.superRefine((value, context) => {
|
|
147
|
+
const ids = value.evidence.map((item) => item.evidenceId);
|
|
148
|
+
if (new Set(ids).size !== ids.length) {
|
|
149
|
+
context.addIssue({
|
|
150
|
+
code: "custom",
|
|
151
|
+
message: "duplicate evidence verification",
|
|
152
|
+
path: ["evidence"],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const content = value.evidence.some(
|
|
156
|
+
(item) => item.contentStatus === "missing"
|
|
157
|
+
)
|
|
158
|
+
? "missing"
|
|
159
|
+
: value.evidence.some((item) => item.contentStatus === "stale")
|
|
160
|
+
? "stale"
|
|
161
|
+
: "unchanged";
|
|
162
|
+
const ranking = value.evidence.some(
|
|
163
|
+
(item) => item.rankingStatus === "unavailable"
|
|
164
|
+
)
|
|
165
|
+
? "unavailable"
|
|
166
|
+
: value.evidence.some((item) => item.rankingStatus === "reranked")
|
|
167
|
+
? "reranked"
|
|
168
|
+
: "unchanged";
|
|
169
|
+
const contentCode = {
|
|
170
|
+
unchanged: "verified_unchanged",
|
|
171
|
+
stale: "content_stale",
|
|
172
|
+
missing: "content_missing",
|
|
173
|
+
}[content];
|
|
174
|
+
const rankingCode = {
|
|
175
|
+
unchanged: "ranking_unchanged",
|
|
176
|
+
reranked: "ranking_changed",
|
|
177
|
+
unavailable: "ranking_unavailable",
|
|
178
|
+
}[ranking];
|
|
179
|
+
if (value.contentStatus !== content || value.contentCode !== contentCode) {
|
|
180
|
+
context.addIssue({
|
|
181
|
+
code: "custom",
|
|
182
|
+
message: "aggregate content status disagrees with evidence",
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (value.rankingStatus !== ranking || value.rankingCode !== rankingCode) {
|
|
186
|
+
context.addIssue({
|
|
187
|
+
code: "custom",
|
|
188
|
+
message: "aggregate ranking status disagrees with evidence",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const reasonIndexes = value.fingerprintReasons.map((reason) =>
|
|
192
|
+
CONTEXT_CAPSULE_FINGERPRINT_DRIFT_REASONS.indexOf(reason)
|
|
193
|
+
);
|
|
194
|
+
const canonicalReasons =
|
|
195
|
+
new Set(value.fingerprintReasons).size ===
|
|
196
|
+
value.fingerprintReasons.length &&
|
|
197
|
+
reasonIndexes.every(
|
|
198
|
+
(reasonIndex, index) =>
|
|
199
|
+
index === 0 || reasonIndex > (reasonIndexes[index - 1] ?? -1)
|
|
200
|
+
);
|
|
201
|
+
const fingerprintsValid =
|
|
202
|
+
(value.fingerprintStatus === "unchanged" &&
|
|
203
|
+
value.fingerprintReasons.length === 0) ||
|
|
204
|
+
(value.fingerprintStatus === "drifted" &&
|
|
205
|
+
value.fingerprintReasons.length > 0);
|
|
206
|
+
if (!canonicalReasons || !fingerprintsValid) {
|
|
207
|
+
context.addIssue({
|
|
208
|
+
code: "custom",
|
|
209
|
+
message:
|
|
210
|
+
"fingerprint drift reasons must be distinct and canonically ordered",
|
|
211
|
+
path: ["fingerprintReasons"],
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
export type ContextCapsuleVerification = z.infer<
|
|
217
|
+
typeof contextCapsuleVerificationSchema
|
|
218
|
+
>;
|