@gmickel/gno 1.16.0 → 1.17.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 (46) hide show
  1. package/README.md +20 -16
  2. package/assets/skill/SKILL.md +8 -5
  3. package/package.json +1 -1
  4. package/src/app/context-agent-projection.ts +303 -0
  5. package/src/app/context-format.ts +249 -0
  6. package/src/app/context-runtime-contract.ts +325 -0
  7. package/src/app/context-runtime-input.ts +362 -0
  8. package/src/app/context-runtime-types.ts +65 -0
  9. package/src/app/context-runtime.ts +170 -0
  10. package/src/app/context-surface.ts +145 -0
  11. package/src/cli/commands/context-build.ts +149 -0
  12. package/src/cli/commands/context-verify.ts +90 -0
  13. package/src/cli/options.ts +4 -0
  14. package/src/cli/program.ts +178 -0
  15. package/src/core/context-budget.ts +461 -0
  16. package/src/core/context-capsule-index-schema.ts +15 -0
  17. package/src/core/context-capsule-retrieval-schema.ts +81 -0
  18. package/src/core/context-capsule-schema.ts +473 -0
  19. package/src/core/context-capsule-validation.ts +416 -0
  20. package/src/core/context-capsule-verification.ts +218 -0
  21. package/src/core/context-capsule.ts +439 -0
  22. package/src/core/context-compiler.ts +513 -0
  23. package/src/core/context-evidence-metadata.ts +33 -0
  24. package/src/core/context-evidence.ts +495 -0
  25. package/src/core/context-facets.ts +163 -0
  26. package/src/core/context-guidance.ts +69 -0
  27. package/src/core/context-scope.ts +32 -0
  28. package/src/core/context-verifier-canonical.ts +90 -0
  29. package/src/core/context-verifier-input.ts +66 -0
  30. package/src/core/context-verifier.ts +447 -0
  31. package/src/core/sections.ts +63 -0
  32. package/src/mcp/server.ts +10 -4
  33. package/src/mcp/tools/context.ts +229 -0
  34. package/src/mcp/tools/index.ts +27 -0
  35. package/src/pipeline/chunk-lookup.ts +33 -0
  36. package/src/pipeline/hybrid.ts +79 -57
  37. package/src/pipeline/types.ts +14 -0
  38. package/src/sdk/client.ts +68 -6
  39. package/src/sdk/index.ts +21 -0
  40. package/src/sdk/types.ts +24 -0
  41. package/src/serve/background-runtime.ts +1 -0
  42. package/src/serve/context-capsule.ts +136 -0
  43. package/src/serve/context.ts +10 -1
  44. package/src/serve/routes/api.ts +2 -0
  45. package/src/serve/server.ts +23 -0
  46. package/src/store/sqlite/adapter.ts +38 -20
@@ -0,0 +1,81 @@
1
+ /** Versioned normalized retrieval request and capability-state schema. */
2
+
3
+ import { z } from "zod";
4
+
5
+ import { contextCapsuleIndexSnapshotSchema } from "./context-capsule-index-schema";
6
+
7
+ const nonEmptyText = z.string().min(1);
8
+ const capabilityOutcomeSchema = z.enum([
9
+ "not_requested",
10
+ "not_attempted",
11
+ "used",
12
+ "unavailable",
13
+ ]);
14
+
15
+ export const contextCapabilityStateSchema = z
16
+ .object({
17
+ requested: z.boolean(),
18
+ attempted: z.boolean(),
19
+ outcome: capabilityOutcomeSchema,
20
+ fallbackReasons: z.array(nonEmptyText.max(256)).max(32),
21
+ })
22
+ .strict()
23
+ .superRefine((value, context) => {
24
+ const valid =
25
+ (value.outcome === "not_requested" &&
26
+ !value.requested &&
27
+ !value.attempted &&
28
+ value.fallbackReasons.length === 0) ||
29
+ (value.outcome === "not_attempted" &&
30
+ value.requested &&
31
+ !value.attempted) ||
32
+ (value.outcome === "used" && value.requested && value.attempted) ||
33
+ (value.outcome === "unavailable" &&
34
+ value.requested &&
35
+ value.attempted &&
36
+ value.fallbackReasons.length > 0);
37
+ if (!valid) {
38
+ context.addIssue({
39
+ code: "custom",
40
+ message: "requested, attempted, outcome, and fallback reasons disagree",
41
+ });
42
+ }
43
+ });
44
+
45
+ const queryModeSchema = z
46
+ .object({
47
+ mode: z.enum(["term", "intent", "hyde"]),
48
+ text: nonEmptyText.max(4096),
49
+ })
50
+ .strict();
51
+
52
+ export const contextCapsuleRetrievalSchema = z
53
+ .object({
54
+ depthPolicy: z.enum(["fast", "balanced", "thorough"]),
55
+ facets: z.array(nonEmptyText.max(512)).max(128),
56
+ queryVariants: z.array(nonEmptyText.max(4096)).min(1).max(128),
57
+ expansionPolicy: z.literal("deterministic_only"),
58
+ request: z
59
+ .object({
60
+ author: z.string().min(1).max(256).nullable(),
61
+ lang: z.string().min(1).max(64).nullable(),
62
+ queryModes: z.array(queryModeSchema).max(128),
63
+ limit: z.number().int().positive(),
64
+ candidateLimit: z.number().int().positive(),
65
+ graphRequested: z.boolean(),
66
+ })
67
+ .strict(),
68
+ capabilityStates: z
69
+ .object({
70
+ semanticSearch: contextCapabilityStateSchema,
71
+ reranking: contextCapabilityStateSchema,
72
+ graphExpansion: contextCapabilityStateSchema,
73
+ })
74
+ .strict(),
75
+ indexSnapshot: contextCapsuleIndexSnapshotSchema,
76
+ })
77
+ .strict();
78
+
79
+ export type ContextCapabilityState = z.infer<
80
+ typeof contextCapabilityStateSchema
81
+ >;
@@ -0,0 +1,473 @@
1
+ import { z } from "zod";
2
+
3
+ import { buildUri, deriveDocid, parseUri } from "../app/constants";
4
+ import { isValidIndexName } from "../app/index-name";
5
+ import { contextCapsuleIndexSnapshotSchema } from "./context-capsule-index-schema";
6
+ import { contextCapsuleRetrievalSchema } from "./context-capsule-retrieval-schema";
7
+ import {
8
+ contextCapsuleEvidenceIdentity,
9
+ contextCapsuleOmissionIdentity,
10
+ sha256Text,
11
+ validateContextCapsulePayload,
12
+ } from "./context-capsule-validation";
13
+
14
+ export { contextCapsuleIndexSnapshotSchema } from "./context-capsule-index-schema";
15
+
16
+ export const CONTEXT_CAPSULE_SCHEMA_VERSION = "1.0" as const;
17
+ export const CONTEXT_CAPSULE_COORDINATE_SPACE = "canonical_mirror" as const;
18
+
19
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
20
+ const sha256Schema = z.string().regex(SHA256_PATTERN);
21
+ const nullableSha256Schema = sha256Schema.nullable();
22
+ const nullableDateSchema = z.string().datetime({ offset: true }).nullable();
23
+ const nullableDocumentDateSchema = z
24
+ .union([z.string().date(), z.string().datetime({ offset: true })])
25
+ .nullable();
26
+ const textSchema = z.string().max(16_384);
27
+ const nonEmptyTextSchema = textSchema.min(1);
28
+ const positiveIntegerSchema = z.number().int().positive();
29
+ const nonNegativeIntegerSchema = z.number().int().nonnegative();
30
+ const COLLECTION_PATTERN = /^[a-z0-9][a-z0-9_-]{0,63}$/;
31
+ const collectionSchema = nonEmptyTextSchema.max(64).regex(COLLECTION_PATTERN);
32
+ const compareCodeUnits = (left: string, right: string): number =>
33
+ left < right ? -1 : left > right ? 1 : 0;
34
+
35
+ const isCanonicalCapsuleUri = (
36
+ value: string,
37
+ allowCollectionRoot: boolean
38
+ ): boolean => {
39
+ const parsed = parseUri(value);
40
+ if (
41
+ !parsed ||
42
+ parsed.collection.length === 0 ||
43
+ (!allowCollectionRoot && parsed.path.length === 0)
44
+ ) {
45
+ return false;
46
+ }
47
+ if (!COLLECTION_PATTERN.test(parsed.collection)) return false;
48
+ if (parsed.indexName !== undefined && !isValidIndexName(parsed.indexName))
49
+ return false;
50
+ try {
51
+ return (
52
+ buildUri(parsed.collection, parsed.path, {
53
+ indexName: parsed.indexName,
54
+ }) === value
55
+ );
56
+ } catch {
57
+ return false;
58
+ }
59
+ };
60
+
61
+ export const contextCapsuleGnoUriSchema = z
62
+ .string()
63
+ .max(2048)
64
+ .refine(
65
+ (value) => isCanonicalCapsuleUri(value, false),
66
+ "URI must be a canonical indexed GNO document reference"
67
+ );
68
+
69
+ export const contextCapsulePrefixUriSchema = z
70
+ .string()
71
+ .max(2048)
72
+ .refine(
73
+ (value) => isCanonicalCapsuleUri(value, true),
74
+ "URI must be a canonical indexed GNO prefix reference"
75
+ );
76
+
77
+ export const contextCapsuleFallbackCodeSchema = z.enum([
78
+ "embedding_unavailable",
79
+ "reranking_unavailable",
80
+ "graph_unavailable",
81
+ "tokenizer_unavailable",
82
+ "egress_policy_unavailable",
83
+ ]);
84
+ export const contextCapsuleWarningCodeSchema = z.enum([
85
+ "incomplete_coverage",
86
+ "omissions_truncated",
87
+ "token_estimate_used",
88
+ ]);
89
+ export const contextCapsuleGapCodeSchema = z.enum([
90
+ "facet_not_found",
91
+ "global_budget_exhausted",
92
+ "capability_unavailable",
93
+ "filtered_by_scope",
94
+ ]);
95
+ export const contextCapsuleOmissionCodeSchema = z.enum([
96
+ "duplicate",
97
+ "overlap",
98
+ "global_budget",
99
+ "redundant_coverage",
100
+ "document_share_cap",
101
+ "filtered_by_scope",
102
+ "invalid_coordinates",
103
+ ]);
104
+
105
+ const scopeSchema = z
106
+ .object({
107
+ indexName: nonEmptyTextSchema.max(64).refine(isValidIndexName),
108
+ collections: z.array(collectionSchema).max(128),
109
+ uriPrefix: contextCapsulePrefixUriSchema.nullable(),
110
+ tagsAll: z.array(nonEmptyTextSchema.max(256)).max(128),
111
+ tagsAny: z.array(nonEmptyTextSchema.max(256)).max(128),
112
+ categories: z.array(nonEmptyTextSchema.max(256)).max(128),
113
+ since: nullableDateSchema,
114
+ until: nullableDateSchema,
115
+ })
116
+ .strict();
117
+
118
+ const budgetSchema = z
119
+ .object({
120
+ authority: z.literal("canonical_json"),
121
+ requestedTokens: positiveIntegerSchema,
122
+ requestedBytes: positiveIntegerSchema,
123
+ safetyMarginTokens: nonNegativeIntegerSchema,
124
+ safetyMarginBytes: nonNegativeIntegerSchema,
125
+ usedTokens: positiveIntegerSchema,
126
+ usedBytes: nonNegativeIntegerSchema,
127
+ estimator: z.enum(["active_tokenizer", "unicode_conservative"]),
128
+ tokenizerFingerprint: nullableSha256Schema,
129
+ })
130
+ .strict()
131
+ .refine((value) => value.usedTokens <= value.requestedTokens, {
132
+ message: "usedTokens cannot exceed requestedTokens",
133
+ path: ["usedTokens"],
134
+ })
135
+ .refine((value) => value.usedBytes <= value.requestedBytes, {
136
+ message: "usedBytes cannot exceed requestedBytes",
137
+ path: ["usedBytes"],
138
+ })
139
+ .refine(
140
+ (value) =>
141
+ value.usedTokens + value.safetyMarginTokens <= value.requestedTokens,
142
+ {
143
+ message: "usedTokens and its safety margin cannot exceed requestedTokens",
144
+ path: ["safetyMarginTokens"],
145
+ }
146
+ )
147
+ .refine(
148
+ (value) =>
149
+ value.usedBytes + value.safetyMarginBytes <= value.requestedBytes,
150
+ {
151
+ message: "usedBytes and its safety margin cannot exceed requestedBytes",
152
+ path: ["safetyMarginBytes"],
153
+ }
154
+ );
155
+
156
+ const capabilitiesSchema = z
157
+ .object({
158
+ lexicalSearch: z.literal(true),
159
+ semanticSearch: z.boolean(),
160
+ reranking: z.boolean(),
161
+ graphExpansion: z.boolean(),
162
+ exactTokenCount: z.boolean(),
163
+ configuredContext: z.boolean(),
164
+ egressPolicy: z.boolean(),
165
+ })
166
+ .strict();
167
+
168
+ const fallbackSchema = z
169
+ .object({
170
+ code: contextCapsuleFallbackCodeSchema,
171
+ capability: z.enum([
172
+ "semantic_search",
173
+ "reranking",
174
+ "graph_expansion",
175
+ "token_count",
176
+ "egress_policy",
177
+ ]),
178
+ })
179
+ .strict();
180
+
181
+ const fingerprintsSchema = z
182
+ .object({
183
+ config: sha256Schema,
184
+ retrieval: sha256Schema,
185
+ embeddingModel: nullableSha256Schema,
186
+ rerankModel: nullableSha256Schema,
187
+ tokenizer: nullableSha256Schema,
188
+ })
189
+ .strict();
190
+
191
+ const guidanceSchema = z
192
+ .object({
193
+ extractiveOnly: z.literal(true),
194
+ evidenceTrust: z.literal("untrusted_data"),
195
+ instructionBoundary: z.literal("hard_delimited"),
196
+ configuredContexts: z
197
+ .array(
198
+ z
199
+ .object({
200
+ contextId: sha256Schema,
201
+ scopeType: z.enum(["global", "collection", "prefix"]),
202
+ scopeKey: nonEmptyTextSchema.max(2048),
203
+ text: nonEmptyTextSchema,
204
+ })
205
+ .strict()
206
+ .superRefine((value, context) => {
207
+ const valid =
208
+ (value.scopeType === "global" && value.scopeKey === "/") ||
209
+ (value.scopeType === "collection" &&
210
+ /^[a-z0-9][a-z0-9_-]{0,63}:$/.test(value.scopeKey)) ||
211
+ (value.scopeType === "prefix" &&
212
+ contextCapsulePrefixUriSchema.safeParse(value.scopeKey)
213
+ .success);
214
+ if (!valid) {
215
+ context.addIssue({
216
+ code: "custom",
217
+ message: "configured context scopeKey is not canonical",
218
+ path: ["scopeKey"],
219
+ });
220
+ }
221
+ })
222
+ )
223
+ .max(128),
224
+ })
225
+ .strict();
226
+
227
+ export const contextCapsuleEvidenceSchema = z
228
+ .object({
229
+ evidenceId: sha256Schema,
230
+ uri: contextCapsuleGnoUriSchema,
231
+ docid: z.string().regex(/^#[a-f0-9]{6,}$/),
232
+ collection: collectionSchema,
233
+ title: textSchema.max(2048).nullable(),
234
+ heading: textSchema.max(2048).nullable(),
235
+ startLine: positiveIntegerSchema,
236
+ endLine: positiveIntegerSchema,
237
+ text: z.string().min(1),
238
+ sourceHash: sha256Schema,
239
+ mirrorHash: sha256Schema,
240
+ passageHash: sha256Schema,
241
+ modifiedAt: nullableDateSchema,
242
+ documentDate: nullableDocumentDateSchema,
243
+ observedAt: nullableDateSchema,
244
+ contextIds: z.array(sha256Schema).max(128),
245
+ retrievalRank: positiveIntegerSchema,
246
+ selectionRank: positiveIntegerSchema,
247
+ facets: z.array(nonEmptyTextSchema.max(512)).max(128),
248
+ trust: z.literal("untrusted"),
249
+ egress: z.enum([
250
+ "local_only",
251
+ "lan",
252
+ "remote",
253
+ "unclassified",
254
+ "unavailable",
255
+ ]),
256
+ })
257
+ .strict()
258
+ .superRefine((value, context) => {
259
+ if (value.endLine < value.startLine) {
260
+ context.addIssue({
261
+ code: "custom",
262
+ message: "endLine must be greater than or equal to startLine",
263
+ path: ["endLine"],
264
+ });
265
+ }
266
+ if (value.text.includes("\r")) {
267
+ context.addIssue({
268
+ code: "custom",
269
+ message: "evidence text must preserve canonical mirror LF bytes",
270
+ path: ["text"],
271
+ });
272
+ }
273
+ if (value.text.split("\n").length !== value.endLine - value.startLine + 1) {
274
+ context.addIssue({
275
+ code: "custom",
276
+ message:
277
+ "evidence text line count must match its inclusive coordinates",
278
+ path: ["text"],
279
+ });
280
+ }
281
+ if (sha256Text(value.text) !== value.passageHash) {
282
+ context.addIssue({
283
+ code: "custom",
284
+ message: "passageHash must hash the exact evidence text bytes",
285
+ path: ["passageHash"],
286
+ });
287
+ }
288
+ if (deriveDocid(value.sourceHash) !== value.docid) {
289
+ context.addIssue({
290
+ code: "custom",
291
+ message: "docid must be derived from sourceHash",
292
+ path: ["docid"],
293
+ });
294
+ }
295
+ if (contextCapsuleEvidenceIdentity(value) !== value.evidenceId) {
296
+ context.addIssue({
297
+ code: "custom",
298
+ message:
299
+ "evidenceId must bind the exact evidence coordinate and hashes",
300
+ path: ["evidenceId"],
301
+ });
302
+ }
303
+ });
304
+
305
+ const coveredFacetSchema = z
306
+ .object({
307
+ facet: nonEmptyTextSchema.max(512),
308
+ evidenceIds: z.array(sha256Schema).min(1).max(256),
309
+ })
310
+ .strict();
311
+ const gapSchema = z
312
+ .object({
313
+ facet: nonEmptyTextSchema.max(512),
314
+ code: contextCapsuleGapCodeSchema,
315
+ })
316
+ .strict();
317
+ const coverageSchema = z
318
+ .object({
319
+ complete: z.boolean(),
320
+ requestedFacets: z.array(nonEmptyTextSchema.max(512)).max(128),
321
+ coveredFacets: z.array(coveredFacetSchema).max(128),
322
+ unresolvedFacets: z.array(nonEmptyTextSchema.max(512)).max(128),
323
+ gaps: z.array(gapSchema).max(128),
324
+ })
325
+ .strict();
326
+
327
+ const omissionSchema = z
328
+ .object({
329
+ candidateId: sha256Schema,
330
+ uri: contextCapsuleGnoUriSchema,
331
+ docid: z.string().regex(/^#[a-f0-9]{6,}$/),
332
+ startLine: positiveIntegerSchema.nullable(),
333
+ endLine: positiveIntegerSchema.nullable(),
334
+ passageHash: nullableSha256Schema,
335
+ sourceHash: sha256Schema,
336
+ mirrorHash: sha256Schema,
337
+ reason: contextCapsuleOmissionCodeSchema,
338
+ })
339
+ .strict()
340
+ .refine(
341
+ (value) =>
342
+ (value.startLine === null &&
343
+ value.endLine === null &&
344
+ value.passageHash === null) ||
345
+ (value.startLine !== null &&
346
+ value.endLine !== null &&
347
+ value.endLine >= value.startLine &&
348
+ value.passageHash !== null),
349
+ {
350
+ message:
351
+ "omission coordinates and passageHash must be absent or complete",
352
+ path: ["endLine"],
353
+ }
354
+ );
355
+
356
+ const omissionsSchema = z
357
+ .object({
358
+ total: nonNegativeIntegerSchema,
359
+ items: z.array(omissionSchema).max(100),
360
+ reasonCounts: z
361
+ .object({
362
+ duplicate: nonNegativeIntegerSchema,
363
+ overlap: nonNegativeIntegerSchema,
364
+ global_budget: nonNegativeIntegerSchema,
365
+ redundant_coverage: nonNegativeIntegerSchema,
366
+ document_share_cap: nonNegativeIntegerSchema,
367
+ filtered_by_scope: nonNegativeIntegerSchema,
368
+ invalid_coordinates: nonNegativeIntegerSchema,
369
+ })
370
+ .strict(),
371
+ truncated: z.boolean(),
372
+ })
373
+ .strict()
374
+ .superRefine((value, context) => {
375
+ if (value.total < value.items.length) {
376
+ context.addIssue({
377
+ code: "custom",
378
+ message: "invalid total",
379
+ path: ["total"],
380
+ });
381
+ }
382
+ if (value.truncated !== value.total > value.items.length) {
383
+ context.addIssue({
384
+ code: "custom",
385
+ message: "truncated must reflect bounded omitted items",
386
+ path: ["truncated"],
387
+ });
388
+ }
389
+ const countedTotal = Object.values(value.reasonCounts).reduce(
390
+ (sum, count) => sum + count,
391
+ 0
392
+ );
393
+ const visibleCounts = new Map<string, number>();
394
+ for (const item of value.items) {
395
+ visibleCounts.set(item.reason, (visibleCounts.get(item.reason) ?? 0) + 1);
396
+ }
397
+ if (
398
+ countedTotal !== value.total ||
399
+ [...visibleCounts].some(
400
+ ([reason, count]) =>
401
+ count > value.reasonCounts[reason as keyof typeof value.reasonCounts]
402
+ )
403
+ ) {
404
+ context.addIssue({
405
+ code: "custom",
406
+ message: "reasonCounts must account for every omitted candidate",
407
+ path: ["reasonCounts"],
408
+ });
409
+ }
410
+ const ids = value.items.map((item) => item.candidateId);
411
+ if (new Set(ids).size !== ids.length) {
412
+ context.addIssue({
413
+ code: "custom",
414
+ message: "duplicate candidateId",
415
+ path: ["items"],
416
+ });
417
+ }
418
+ for (const [index, item] of value.items.entries()) {
419
+ if (contextCapsuleOmissionIdentity(item) !== item.candidateId) {
420
+ context.addIssue({
421
+ code: "custom",
422
+ message: "candidateId must bind the omitted candidate coordinate",
423
+ path: ["items", index, "candidateId"],
424
+ });
425
+ }
426
+ if (index > 0) {
427
+ const previous = value.items[index - 1];
428
+ const outOfOrder =
429
+ previous !== undefined &&
430
+ (compareCodeUnits(previous.reason, item.reason) ||
431
+ compareCodeUnits(previous.uri, item.uri) ||
432
+ (previous.startLine ?? 0) - (item.startLine ?? 0) ||
433
+ compareCodeUnits(previous.candidateId, item.candidateId)) > 0;
434
+ if (outOfOrder) {
435
+ context.addIssue({
436
+ code: "custom",
437
+ message: "omission items must use canonical deterministic order",
438
+ path: ["items", index],
439
+ });
440
+ }
441
+ }
442
+ }
443
+ });
444
+
445
+ const warningSchema = z
446
+ .object({ code: contextCapsuleWarningCodeSchema })
447
+ .strict();
448
+
449
+ export const contextCapsulePayloadV1Schema = z
450
+ .object({
451
+ schemaVersion: z.literal(CONTEXT_CAPSULE_SCHEMA_VERSION),
452
+ coordinateSpace: z.literal(CONTEXT_CAPSULE_COORDINATE_SPACE),
453
+ goal: nonEmptyTextSchema,
454
+ query: nonEmptyTextSchema,
455
+ scope: scopeSchema,
456
+ budget: budgetSchema,
457
+ retrieval: contextCapsuleRetrievalSchema,
458
+ fingerprints: fingerprintsSchema,
459
+ capabilities: capabilitiesSchema,
460
+ fallbacks: z.array(fallbackSchema).max(16),
461
+ guidance: guidanceSchema,
462
+ evidence: z.array(contextCapsuleEvidenceSchema).min(1),
463
+ coverage: coverageSchema,
464
+ omissions: omissionsSchema,
465
+ truncated: z.boolean(),
466
+ warnings: z.array(warningSchema).max(32),
467
+ })
468
+ .strict()
469
+ .superRefine(validateContextCapsulePayload);
470
+
471
+ export type ContextCapsulePayloadV1 = z.infer<
472
+ typeof contextCapsulePayloadV1Schema
473
+ >;