@gmickel/gno 2.4.0 → 2.5.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 (58) hide show
  1. package/README.md +10 -2
  2. package/assets/skill/SKILL.md +23 -0
  3. package/assets/skill/cli-reference.md +17 -0
  4. package/assets/skill/examples.md +15 -0
  5. package/assets/skill/mcp-reference.md +10 -0
  6. package/assets/spa-production.json.gz +0 -0
  7. package/browser-extension/artifacts/{gno-browser-clipper-v2.4.0.zip → gno-browser-clipper-v2.5.1.zip} +0 -0
  8. package/browser-extension/artifacts/gno-browser-clipper-v2.5.1.zip.sha256 +1 -0
  9. package/browser-extension/dist/manifest.json +1 -1
  10. package/package.json +1 -1
  11. package/spec/cli.md +11 -0
  12. package/spec/compiled-context.md +68 -0
  13. package/spec/mcp.md +25 -0
  14. package/spec/output-schemas/compiled-context-check.schema.json +44 -0
  15. package/spec/output-schemas/compiled-context-file.schema.json +165 -0
  16. package/spec/output-schemas/compiled-context-preview.schema.json +142 -0
  17. package/src/app/compiled-context-files.ts +361 -0
  18. package/src/app/compiled-context.ts +240 -0
  19. package/src/app/context-surface.ts +7 -2
  20. package/src/cli/commands/audit.ts +4 -0
  21. package/src/cli/commands/context-compiled.ts +136 -0
  22. package/src/cli/errors.ts +10 -3
  23. package/src/cli/program.ts +70 -0
  24. package/src/core/compiled-context.ts +254 -0
  25. package/src/core/context-budget.ts +2 -10
  26. package/src/core/file-lock.ts +22 -5
  27. package/src/core/folder-setup-planning.ts +2 -1
  28. package/src/core/network-boundary-inventory.ts +8 -0
  29. package/src/core/setup-receipt.ts +27 -20
  30. package/src/core/typed-metadata.ts +4 -0
  31. package/src/core/validation.ts +9 -2
  32. package/src/core/windows-private-path.ts +96 -0
  33. package/src/index.ts +2 -2
  34. package/src/ingestion/compiled-context.ts +15 -0
  35. package/src/ingestion/sync.ts +40 -8
  36. package/src/ingestion/walker.ts +5 -4
  37. package/src/llm/nodeLlamaCpp/simulator-install.ts +6 -2
  38. package/src/mcp/http-egress.ts +11 -3
  39. package/src/mcp/retrieval-warnings.ts +24 -0
  40. package/src/mcp/tools/ask.ts +14 -1
  41. package/src/mcp/tools/context.ts +46 -2
  42. package/src/mcp/tools/index.ts +53 -7
  43. package/src/mcp/tools/query.ts +3 -1
  44. package/src/mcp/tools/search.ts +3 -1
  45. package/src/mcp/tools/vsearch.ts +3 -1
  46. package/src/sdk/client.ts +72 -5
  47. package/src/sdk/index.ts +7 -0
  48. package/src/sdk/types.ts +28 -1
  49. package/src/serve/compiled-context.ts +84 -0
  50. package/src/serve/public/app.tsx +11 -0
  51. package/src/serve/public/globals.built.css +1 -1
  52. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  53. package/src/serve/public/pages/CompiledContext.tsx +364 -0
  54. package/src/serve/public/pages/Dashboard.tsx +7 -0
  55. package/src/serve/server.ts +43 -2
  56. package/src/serve/spa-production-build.ts +6 -5
  57. package/src/store/sqlite/adapter.ts +14 -1
  58. package/browser-extension/artifacts/gno-browser-clipper-v2.4.0.zip.sha256 +0 -1
@@ -0,0 +1,361 @@
1
+ // Bun has no lstat, exclusive-create, hard-link or atomic-rename primitives.
2
+ import { lstat, open, link, rename, unlink } from "node:fs/promises";
3
+ import { dirname, parse, resolve, join } from "node:path";
4
+ import { z } from "zod";
5
+
6
+ import type {
7
+ CompiledContextCheck,
8
+ CompiledContextPreview,
9
+ } from "../core/compiled-context";
10
+ import type { ContextCapsuleV1 } from "../core/context-capsule";
11
+ import type { ContextCapsuleBuildInput } from "./context-runtime";
12
+
13
+ import { withWriteLock } from "../core/file-lock";
14
+ import {
15
+ checkCompiledContext,
16
+ compiledContextRefreshRequest,
17
+ previewCompiledContext,
18
+ type CompiledContextRuntimeDeps,
19
+ } from "./compiled-context";
20
+
21
+ const MAX_BYTES = 4 * 1024 * 1024;
22
+ const snapshotSchema = z
23
+ .object({
24
+ capsulePath: z.string().min(1),
25
+ outputDigest: z.string().regex(/^[a-f0-9]{64}$/),
26
+ settings: z
27
+ .object({
28
+ budgetTokens: z.number().int().positive(),
29
+ budgetBytes: z.number().int().positive().optional(),
30
+ })
31
+ .strict(),
32
+ })
33
+ .strict();
34
+ const sidecarSchema = snapshotSchema
35
+ .extend({
36
+ artifactKind: z.literal("gno_compiled_context_sidecar"),
37
+ schemaVersion: z.literal("1.0"),
38
+ rendererVersion: z.literal("1"),
39
+ previous: snapshotSchema.optional(),
40
+ })
41
+ .strict();
42
+ type Snapshot = z.infer<typeof snapshotSchema>;
43
+ type Sidecar = z.infer<typeof sidecarSchema>;
44
+ export interface CompiledContextFileResult {
45
+ status: "written" | "unchanged";
46
+ outputPath: string;
47
+ capsulePath: string;
48
+ sidecarPath: string;
49
+ preview: CompiledContextPreview;
50
+ }
51
+ const digest = (text: string): string =>
52
+ new Bun.CryptoHasher("sha256").update(text).digest("hex");
53
+ const isMissing = (error: unknown): boolean =>
54
+ !!error &&
55
+ typeof error === "object" &&
56
+ "code" in error &&
57
+ error.code === "ENOENT";
58
+
59
+ /** Refuse links in every existing path component; never create parent directories. */
60
+ async function safePath(path: string, allowMissing = false): Promise<string> {
61
+ const absolute = resolve(path);
62
+ const root = parse(absolute).root;
63
+ const parts = absolute.slice(root.length).split(/[\\/]/).filter(Boolean);
64
+ let current = root;
65
+ for (const [index, part] of parts.entries()) {
66
+ current = join(current, part);
67
+ try {
68
+ const stat = await lstat(current);
69
+ if (
70
+ stat.isSymbolicLink() ||
71
+ (index < parts.length - 1 ? !stat.isDirectory() : !stat.isFile())
72
+ ) {
73
+ throw new Error(`Unsafe compiled-context path: ${current}`);
74
+ }
75
+ } catch (error) {
76
+ if (allowMissing && index === parts.length - 1 && isMissing(error))
77
+ return absolute;
78
+ throw error;
79
+ }
80
+ }
81
+ return absolute;
82
+ }
83
+ async function readBounded(path: string): Promise<string> {
84
+ await safePath(path);
85
+ const file = Bun.file(path);
86
+ if (file.size > MAX_BYTES)
87
+ throw new Error("Compiled-context input exceeds 4 MiB");
88
+ return file.text();
89
+ }
90
+ async function absent(path: string): Promise<void> {
91
+ await safePath(path, true);
92
+ try {
93
+ await lstat(path);
94
+ } catch (error) {
95
+ if (isMissing(error)) return;
96
+ throw error;
97
+ }
98
+ throw new Error(`Destination already exists: ${path}`);
99
+ }
100
+ function outputTarget(path: string): string {
101
+ if (!path.endsWith(".gno-context.md"))
102
+ throw new Error("Output must end in .gno-context.md");
103
+ return resolve(path);
104
+ }
105
+ async function stage(path: string, content: string): Promise<string> {
106
+ await safePath(path, true);
107
+ const temp = join(dirname(path), `.${crypto.randomUUID()}.gno-context.tmp`);
108
+ const handle = await open(temp, "wx", 0o600);
109
+ try {
110
+ await Bun.write(Bun.file(handle.fd), content);
111
+ await handle.sync();
112
+ } catch (error) {
113
+ await unlink(temp);
114
+ throw error;
115
+ } finally {
116
+ await handle.close();
117
+ }
118
+ return temp;
119
+ }
120
+ async function removeTemp(path: string): Promise<void> {
121
+ try {
122
+ await unlink(path);
123
+ } catch (error) {
124
+ if (!isMissing(error)) throw error;
125
+ }
126
+ }
127
+ function sidecar(snapshot: Snapshot, previous?: Snapshot): Sidecar {
128
+ return {
129
+ artifactKind: "gno_compiled_context_sidecar",
130
+ schemaVersion: "1.0",
131
+ rendererVersion: "1",
132
+ ...snapshot,
133
+ ...(previous ? { previous } : {}),
134
+ };
135
+ }
136
+ async function owned(
137
+ outputPath: string
138
+ ): Promise<{ markdown: string; snapshot: Snapshot; sidecarText: string }> {
139
+ const markdown = await readBounded(outputPath);
140
+ const sidecarText = await readBounded(`${outputPath}.json`);
141
+ const metadata = sidecarSchema.parse(JSON.parse(sidecarText));
142
+ const outputDigest = digest(markdown);
143
+ const snapshot =
144
+ metadata.outputDigest === outputDigest
145
+ ? snapshotSchema.parse({
146
+ capsulePath: metadata.capsulePath,
147
+ outputDigest: metadata.outputDigest,
148
+ settings: metadata.settings,
149
+ })
150
+ : metadata.previous;
151
+ if (!snapshot || snapshot.outputDigest !== outputDigest)
152
+ throw new Error("Conflict: compiled context was manually changed");
153
+ return { markdown, snapshot, sidecarText };
154
+ }
155
+ async function unchanged(
156
+ outputPath: string,
157
+ original: Awaited<ReturnType<typeof owned>>
158
+ ): Promise<void> {
159
+ if (
160
+ (await readBounded(outputPath)) !== original.markdown ||
161
+ (await readBounded(`${outputPath}.json`)) !== original.sidecarText
162
+ ) {
163
+ throw new Error("Conflict: compiled context changed during publication");
164
+ }
165
+ }
166
+ function result(
167
+ outputPath: string,
168
+ capsulePath: string,
169
+ preview: CompiledContextPreview,
170
+ status: "written" | "unchanged"
171
+ ): CompiledContextFileResult {
172
+ return {
173
+ status,
174
+ outputPath,
175
+ capsulePath,
176
+ sidecarPath: `${outputPath}.json`,
177
+ preview,
178
+ };
179
+ }
180
+
181
+ export async function compileContextFile(
182
+ input: {
183
+ capsulePath: string;
184
+ outputPath: string;
185
+ budgetTokens: number;
186
+ budgetBytes?: number;
187
+ },
188
+ deps: CompiledContextRuntimeDeps
189
+ ): Promise<CompiledContextFileResult> {
190
+ const outputPath = outputTarget(input.outputPath);
191
+ const capsulePath = await safePath(input.capsulePath);
192
+ await absent(outputPath);
193
+ await absent(`${outputPath}.json`);
194
+ await safePath(`${outputPath}.lock`, true);
195
+ return withWriteLock(`${outputPath}.lock`, async () => {
196
+ await absent(outputPath);
197
+ await absent(`${outputPath}.json`);
198
+ const capsuleText = await readBounded(capsulePath);
199
+ const capsule = JSON.parse(capsuleText);
200
+ const settings = {
201
+ budgetTokens: input.budgetTokens,
202
+ ...(input.budgetBytes === undefined
203
+ ? {}
204
+ : { budgetBytes: input.budgetBytes }),
205
+ };
206
+ const preview = await previewCompiledContext(
207
+ { capsule, ...settings },
208
+ deps
209
+ );
210
+ const metadata = sidecar({
211
+ capsulePath,
212
+ outputDigest: preview.digest,
213
+ settings,
214
+ });
215
+ const stagedOutput = await stage(outputPath, preview.markdown);
216
+ let stagedSidecar: string | undefined;
217
+ let publishedSidecar = false;
218
+ try {
219
+ stagedSidecar = await stage(
220
+ `${outputPath}.json`,
221
+ JSON.stringify(metadata)
222
+ );
223
+ const checked = await checkCompiledContext(
224
+ { capsule, markdown: preview.markdown },
225
+ deps
226
+ );
227
+ if (
228
+ checked.status !== "current" ||
229
+ (await readBounded(capsulePath)) !== capsuleText
230
+ )
231
+ throw new Error("Source or policy changed before publication");
232
+ await absent(outputPath);
233
+ await absent(`${outputPath}.json`);
234
+ await link(stagedSidecar, `${outputPath}.json`);
235
+ publishedSidecar = true;
236
+ await link(stagedOutput, outputPath);
237
+ return result(outputPath, capsulePath, preview, "written");
238
+ } catch (error) {
239
+ if (publishedSidecar) await removeTemp(`${outputPath}.json`);
240
+ throw error;
241
+ } finally {
242
+ await removeTemp(stagedOutput);
243
+ if (stagedSidecar) await removeTemp(stagedSidecar);
244
+ }
245
+ });
246
+ }
247
+
248
+ export async function checkContextFile(
249
+ input: { outputPath: string; capsulePath?: string },
250
+ deps: CompiledContextRuntimeDeps
251
+ ): Promise<CompiledContextCheck> {
252
+ try {
253
+ const outputPath = outputTarget(input.outputPath);
254
+ const saved = await owned(outputPath);
255
+ const capsule = JSON.parse(
256
+ await readBounded(input.capsulePath ?? saved.snapshot.capsulePath)
257
+ );
258
+ return await checkCompiledContext(
259
+ { capsule, markdown: saved.markdown },
260
+ deps
261
+ );
262
+ } catch (error) {
263
+ const message =
264
+ error instanceof Error ? error.message : "Unavailable compiled context";
265
+ return {
266
+ schemaVersion: "1.0",
267
+ status: message.startsWith("Conflict:") ? "conflict" : "unverifiable",
268
+ reasons: [message],
269
+ digest: null,
270
+ capsuleId: null,
271
+ };
272
+ }
273
+ }
274
+
275
+ export async function refreshContextFile(
276
+ input: { outputPath: string; capsuleOutputPath: string },
277
+ deps: CompiledContextRuntimeDeps,
278
+ build: (request: ContextCapsuleBuildInput) => Promise<ContextCapsuleV1>
279
+ ): Promise<CompiledContextFileResult> {
280
+ const outputPath = outputTarget(input.outputPath);
281
+ if (!input.capsuleOutputPath.endsWith(".gno-context.capsule.json"))
282
+ throw new Error("Refreshed Capsule must end in .gno-context.capsule.json");
283
+ const capsulePath = await safePath(input.capsuleOutputPath, true);
284
+ await safePath(`${outputPath}.lock`, true);
285
+ return withWriteLock(`${outputPath}.lock`, async () => {
286
+ const saved = await owned(outputPath);
287
+ const priorCapsuleText = await readBounded(saved.snapshot.capsulePath);
288
+ const priorCapsule = JSON.parse(priorCapsuleText);
289
+ const current = await checkCompiledContext(
290
+ { capsule: priorCapsule, markdown: saved.markdown },
291
+ deps
292
+ );
293
+ if (current.status === "current") {
294
+ const preview = await previewCompiledContext(
295
+ { capsule: priorCapsule, ...saved.snapshot.settings },
296
+ deps
297
+ );
298
+ await unchanged(outputPath, saved);
299
+ if ((await readBounded(saved.snapshot.capsulePath)) !== priorCapsuleText)
300
+ throw new Error("Capsule changed during refresh");
301
+ return result(
302
+ outputPath,
303
+ saved.snapshot.capsulePath,
304
+ preview,
305
+ "unchanged"
306
+ );
307
+ }
308
+ if (current.status !== "stale")
309
+ throw new Error(
310
+ `Cannot refresh ${current.status} context: ${current.reasons.join("; ")}`
311
+ );
312
+ await absent(capsulePath);
313
+ // Stage first so an unwritable Capsule destination fails before retrieval/publication.
314
+ const stagedCapsule = await stage(capsulePath, "");
315
+ let stagedOutput: string | undefined;
316
+ let stagedSidecar: string | undefined;
317
+ try {
318
+ const capsule = await build(
319
+ compiledContextRefreshRequest(priorCapsule, deps)
320
+ );
321
+ const preview = await previewCompiledContext(
322
+ { capsule, ...saved.snapshot.settings },
323
+ deps
324
+ );
325
+ await Bun.write(stagedCapsule, JSON.stringify(capsule));
326
+ stagedOutput = await stage(outputPath, preview.markdown);
327
+ stagedSidecar = await stage(
328
+ `${outputPath}.json`,
329
+ JSON.stringify(
330
+ sidecar(
331
+ {
332
+ capsulePath,
333
+ outputDigest: preview.digest,
334
+ settings: saved.snapshot.settings,
335
+ },
336
+ saved.snapshot
337
+ )
338
+ )
339
+ );
340
+ const checked = await checkCompiledContext(
341
+ { capsule, markdown: preview.markdown },
342
+ deps
343
+ );
344
+ if (checked.status !== "current")
345
+ throw new Error("Source or policy changed before publication");
346
+ await unchanged(outputPath, saved);
347
+ if ((await readBounded(saved.snapshot.capsulePath)) !== priorCapsuleText)
348
+ throw new Error("Capsule changed during refresh");
349
+ await absent(capsulePath);
350
+ await link(stagedCapsule, capsulePath);
351
+ // Retain one prior reference: either output remains verifiable after interruption.
352
+ await rename(stagedSidecar, `${outputPath}.json`);
353
+ await rename(stagedOutput, outputPath);
354
+ return result(outputPath, capsulePath, preview, "written");
355
+ } finally {
356
+ await removeTemp(stagedCapsule);
357
+ if (stagedOutput) await removeTemp(stagedOutput);
358
+ if (stagedSidecar) await removeTemp(stagedSidecar);
359
+ }
360
+ });
361
+ }
@@ -0,0 +1,240 @@
1
+ /** Verification and caller authority shared by all compiled-context surfaces. */
2
+ import type { ContextCapsuleV1 } from "../core/context-capsule";
3
+ import type {
4
+ EgressCallerContext,
5
+ EgressDestinationZone,
6
+ } from "../core/egress-policy";
7
+ import type {
8
+ ContextCapsuleBuildInput,
9
+ ContextCapsuleRuntimeDeps,
10
+ } from "./context-runtime";
11
+
12
+ import { currentEgressSources } from "../core/collection-egress-policy-service";
13
+ import {
14
+ compiledContextPreviewInputSchema,
15
+ compiledContextCheckInputSchema,
16
+ COMPILED_CONTEXT_MAX_BYTES,
17
+ readCompiledContextMetadata,
18
+ compiledContextBodyMatches,
19
+ renderCompiledContext,
20
+ type CompiledContextCheck,
21
+ type CompiledContextPreview,
22
+ } from "../core/compiled-context";
23
+ import { sha256Text } from "../core/context-capsule-validation";
24
+ import {
25
+ parseCanonicalContextCapsuleForVerification,
26
+ rawCanonicalContextJson,
27
+ } from "../core/context-verifier-input";
28
+ import { evaluateEgressPolicy } from "../core/egress-policy";
29
+ import { resolveEgressLineage } from "../core/egress-provenance";
30
+ import {
31
+ verifyContextCapsuleRuntime,
32
+ canonicalVerifiedContextCapsuleJson,
33
+ } from "./context-runtime";
34
+ export interface CompiledContextRuntimeDeps extends ContextCapsuleRuntimeDeps {
35
+ destinationZone?: EgressDestinationZone;
36
+ caller?: EgressCallerContext;
37
+ }
38
+ class CompiledContextStateError extends Error {
39
+ constructor(
40
+ readonly state: "stale" | "unverifiable",
41
+ message: string
42
+ ) {
43
+ super(message);
44
+ this.name = "CompiledContextStateError";
45
+ }
46
+ }
47
+ function parse(
48
+ input: unknown,
49
+ deps: CompiledContextRuntimeDeps
50
+ ): ContextCapsuleV1 {
51
+ if (
52
+ new TextEncoder().encode(rawCanonicalContextJson(input)).byteLength >
53
+ COMPILED_CONTEXT_MAX_BYTES
54
+ )
55
+ throw new Error("Capsule exceeds 4 MiB");
56
+ const capsule = parseCanonicalContextCapsuleForVerification(input, deps);
57
+ if (capsule.schemaVersion === "1.0")
58
+ throw new Error(
59
+ "Rebuild legacy Capsule with current provenance before compiling"
60
+ );
61
+ return capsule;
62
+ }
63
+ function authorize(
64
+ capsule: ContextCapsuleV1,
65
+ deps: CompiledContextRuntimeDeps
66
+ ): string {
67
+ if (capsule.schemaVersion === "1.0")
68
+ throw new Error("Capsule lineage unavailable");
69
+ const names = [
70
+ ...new Set([
71
+ ...capsule.scope.collections,
72
+ ...capsule.evidence.map((item) => item.collection),
73
+ ...capsule.egressLineage.sources.map((item) => item.collection),
74
+ ]),
75
+ ];
76
+ if (
77
+ names.some(
78
+ (name) =>
79
+ !deps.config.collections.some((collection) => collection.name === name)
80
+ )
81
+ )
82
+ throw new CompiledContextStateError(
83
+ "unverifiable",
84
+ "Collection scope is no longer configured; rebuild within eligible scope"
85
+ );
86
+ const sources = currentEgressSources(deps.config, names);
87
+ const decision = evaluateEgressPolicy({
88
+ collections: sources,
89
+ action: "export",
90
+ destination: { zone: deps.destinationZone ?? "local_process" },
91
+ caller: deps.caller ?? { authenticated: true, operationAuthorized: true },
92
+ contentClass: "capsule",
93
+ });
94
+ if (!decision.allowed)
95
+ throw new CompiledContextStateError(
96
+ "unverifiable",
97
+ "Current collection policy or caller authority denies this context"
98
+ );
99
+ const lineage = resolveEgressLineage(sources, names);
100
+ if (lineage.digest !== capsule.egressLineage.digest)
101
+ throw new CompiledContextStateError(
102
+ "stale",
103
+ "Collection policy changed; rebuild and verify the Capsule"
104
+ );
105
+ return lineage.digest;
106
+ }
107
+ async function verified(
108
+ capsule: ContextCapsuleV1,
109
+ deps: CompiledContextRuntimeDeps
110
+ ): Promise<{ verificationDigest: string; lineageDigest: string }> {
111
+ const lineageDigest = authorize(capsule, deps);
112
+ const receipt = await verifyContextCapsuleRuntime(capsule, deps);
113
+ if (
114
+ receipt.contentStatus !== "unchanged" ||
115
+ receipt.fingerprintStatus !== "unchanged"
116
+ )
117
+ throw new CompiledContextStateError(
118
+ "stale",
119
+ `Capsule requires refresh: ${[receipt.contentCode, ...receipt.fingerprintReasons].join(", ")}`
120
+ );
121
+ authorize(capsule, deps);
122
+ return {
123
+ verificationDigest: sha256Text(
124
+ canonicalVerifiedContextCapsuleJson(receipt)
125
+ ),
126
+ lineageDigest,
127
+ };
128
+ }
129
+ export async function previewCompiledContext(
130
+ input: unknown,
131
+ deps: CompiledContextRuntimeDeps
132
+ ): Promise<CompiledContextPreview> {
133
+ const settings = compiledContextPreviewInputSchema.parse(input);
134
+ const capsule = parse(settings.capsule, deps);
135
+ const identity = await verified(capsule, deps);
136
+ const preview = renderCompiledContext(
137
+ capsule,
138
+ settings,
139
+ identity.verificationDigest,
140
+ identity.lineageDigest,
141
+ deps.countTokens
142
+ );
143
+ const finalIdentity = await verified(capsule, deps);
144
+ if (finalIdentity.verificationDigest !== identity.verificationDigest)
145
+ throw new CompiledContextStateError(
146
+ "stale",
147
+ "Index changed during compilation; retry after indexing completes"
148
+ );
149
+ return preview;
150
+ }
151
+ export async function checkCompiledContext(
152
+ input: unknown,
153
+ deps: CompiledContextRuntimeDeps
154
+ ): Promise<CompiledContextCheck> {
155
+ const result = (
156
+ status: CompiledContextCheck["status"],
157
+ reasons: string[],
158
+ digest: string | null = null,
159
+ capsuleId: string | null = null
160
+ ): CompiledContextCheck => ({
161
+ schemaVersion: "1.0",
162
+ status,
163
+ reasons,
164
+ digest,
165
+ capsuleId,
166
+ });
167
+ try {
168
+ const { capsule: raw, markdown } =
169
+ compiledContextCheckInputSchema.parse(input);
170
+ const capsule = parse(raw, deps);
171
+ // Authorize before reporting identifiers from supplied private evidence.
172
+ authorize(capsule, deps);
173
+ const meta = readCompiledContextMetadata(markdown);
174
+ if (!compiledContextBodyMatches(markdown))
175
+ return result("conflict", [
176
+ "Output bytes were edited; preserve edits before recompiling",
177
+ ]);
178
+ const expected = await previewCompiledContext(
179
+ {
180
+ capsule,
181
+ budgetTokens: meta.budgetTokens,
182
+ budgetBytes: meta.budgetBytes,
183
+ },
184
+ deps
185
+ );
186
+ if (expected.markdown !== markdown)
187
+ return result("conflict", [
188
+ "Artifact metadata, renderer settings, or Capsule identity differs",
189
+ ]);
190
+ return result("current", [], expected.digest, capsule.capsuleId);
191
+ } catch (error) {
192
+ if (error instanceof CompiledContextStateError)
193
+ return result(error.state, [error.message]);
194
+ // Do not echo parser paths/values or supplied private source material.
195
+ return result("unverifiable", [
196
+ "Invalid or unavailable Capsule, artifact metadata, index, or recorded tokenizer; rebuild or supply the original matching inputs",
197
+ ]);
198
+ }
199
+ }
200
+ export function compiledContextRefreshRequest(
201
+ input: unknown,
202
+ authority: Pick<
203
+ CompiledContextRuntimeDeps,
204
+ "countTokens" | "tokenizerFingerprint"
205
+ > = {}
206
+ ): ContextCapsuleBuildInput {
207
+ // Refresh cannot trust stale fingerprints, but must preserve the saved request exactly.
208
+ const capsule = parseCanonicalContextCapsuleForVerification(input, authority);
209
+ if (capsule.schemaVersion === "1.0")
210
+ throw new Error("Rebuild legacy Capsule before refresh");
211
+ const request = capsule.retrieval.request;
212
+ return {
213
+ goal: capsule.goal,
214
+ query: capsule.query,
215
+ indexName: capsule.scope.indexName,
216
+ collections: capsule.scope.collections,
217
+ uriPrefix: capsule.scope.uriPrefix,
218
+ tagsAll: capsule.scope.tagsAll,
219
+ tagsAny: capsule.scope.tagsAny,
220
+ categories: capsule.scope.categories,
221
+ since: capsule.scope.since ?? undefined,
222
+ until: capsule.scope.until ?? undefined,
223
+ filter: "filter" in capsule.scope ? capsule.scope.filter : undefined,
224
+ author: request.author ?? undefined,
225
+ lang: request.lang ?? undefined,
226
+ intent: request.intent ?? undefined,
227
+ exclude: request.exclude,
228
+ minScore: request.minScore ?? undefined,
229
+ queryModes: request.queryModes,
230
+ limit: request.limit,
231
+ candidateLimit: request.candidateLimit,
232
+ graph: request.graphRequested,
233
+ noRerank: request.rerankRequested === false,
234
+ depthPolicy: capsule.retrieval.depthPolicy,
235
+ budgetTokens: capsule.budget.requestedTokens,
236
+ budgetBytes: capsule.budget.requestedBytes,
237
+ safetyMarginTokens: capsule.budget.safetyMarginTokens,
238
+ safetyMarginBytes: capsule.budget.safetyMarginBytes,
239
+ };
240
+ }
@@ -5,7 +5,10 @@ import { z } from "zod";
5
5
  import type { ContextCapsuleBuildInput } from "./context-runtime-types";
6
6
 
7
7
  import { ContextCapsuleContractError } from "../core/context-capsule";
8
- import { metadataPredicateSchema } from "../core/typed-metadata";
8
+ import {
9
+ metadataPredicateSchema,
10
+ METADATA_FILTER_DESCRIPTION,
11
+ } from "../core/typed-metadata";
9
12
 
10
13
  const queryModeSchema = z
11
14
  .object({
@@ -29,7 +32,9 @@ export const contextBuildSurfaceSchema = z
29
32
  tagsAll: stringList.optional(),
30
33
  tagsAny: stringList.optional(),
31
34
  categories: stringList.optional(),
32
- filter: metadataPredicateSchema.optional(),
35
+ filter: metadataPredicateSchema
36
+ .optional()
37
+ .describe(METADATA_FILTER_DESCRIPTION),
33
38
  author: z.string().optional(),
34
39
  lang: z.string().optional(),
35
40
  intent: z.string().optional(),
@@ -21,6 +21,7 @@ import {
21
21
  import { runWorkspaceAudit } from "../../core/audit-workspace";
22
22
  import { normalizeTag, validateTag } from "../../core/tags";
23
23
  import { normalizeCollectionName } from "../../core/validation";
24
+ import { windowsPrivatePath } from "../../core/windows-private-path";
24
25
  import { SqliteAdapter } from "../../store/sqlite/adapter";
25
26
 
26
27
  export interface AuditCommandOptions {
@@ -218,12 +219,15 @@ export const writeAuditReport = async (
218
219
  );
219
220
  const temporaryPath = join(temporaryDirectory, "report");
220
221
  try {
222
+ if (process.platform === "win32")
223
+ await windowsPrivatePath(temporaryDirectory, true);
221
224
  await Bun.write(temporaryPath, `${formatAuditReport(report, options)}\n`, {
222
225
  createPath: false,
223
226
  mode: 0o600,
224
227
  });
225
228
  await chmod(temporaryPath, 0o600);
226
229
  await rename(temporaryPath, path);
230
+ if (process.platform === "win32") await windowsPrivatePath(path);
227
231
  } finally {
228
232
  await unlink(temporaryPath).catch(() => undefined);
229
233
  await rmdir(temporaryDirectory).catch(() => undefined);