@gmickel/gno 1.34.5 → 1.35.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 (49) hide show
  1. package/README.md +22 -1
  2. package/browser-extension/artifacts/{gno-browser-clipper-v1.34.5.zip → gno-browser-clipper-v1.35.0.zip} +0 -0
  3. package/browser-extension/artifacts/gno-browser-clipper-v1.35.0.zip.sha256 +1 -0
  4. package/browser-extension/dist/manifest.json +1 -1
  5. package/package.json +4 -1
  6. package/spec/cli.md +43 -0
  7. package/spec/output-schemas/mcp-job-status.schema.json +6 -2
  8. package/src/config/index.ts +4 -0
  9. package/src/config/types.ts +14 -0
  10. package/src/core/path-rules.ts +34 -0
  11. package/src/ingestion/index.ts +21 -0
  12. package/src/ingestion/record-container.ts +23 -1
  13. package/src/ingestion/source-availability/darwin-io.ts +295 -0
  14. package/src/ingestion/source-availability/darwin-path.ts +58 -0
  15. package/src/ingestion/source-availability/directory.ts +402 -0
  16. package/src/ingestion/source-availability/index.ts +74 -0
  17. package/src/ingestion/source-availability/readers.ts +360 -0
  18. package/src/ingestion/source-availability/resolve.ts +28 -0
  19. package/src/ingestion/source-availability/types.ts +170 -0
  20. package/src/ingestion/sync.ts +565 -108
  21. package/src/ingestion/types.ts +45 -3
  22. package/src/ingestion/walker.ts +263 -5
  23. package/src/serve/public/globals.built.css +1 -1
  24. package/src/serve/watch-reconciliation-fallback-disk.ts +359 -0
  25. package/src/serve/watch-reconciliation-fallback.ts +434 -0
  26. package/src/serve/watch-reconciliation-shared.ts +348 -0
  27. package/src/serve/watch-reconciliation.ts +129 -0
  28. package/src/serve/watch-service-events.ts +261 -0
  29. package/src/serve/watch-service-flush-generation.ts +140 -0
  30. package/src/serve/watch-service-flush-helpers.ts +147 -0
  31. package/src/serve/watch-service-flush.ts +443 -0
  32. package/src/serve/watch-service-hosts.ts +109 -0
  33. package/src/serve/watch-service-lifecycle.ts +221 -0
  34. package/src/serve/watch-service-run-flush.ts +236 -0
  35. package/src/serve/watch-service-snapshot.ts +125 -0
  36. package/src/serve/watch-service-state.ts +146 -0
  37. package/src/serve/watch-service.ts +266 -306
  38. package/src/serve/watch-snapshot-availability.ts +51 -0
  39. package/src/serve/watch-snapshot-handles.ts +365 -0
  40. package/src/serve/watch-snapshot-libc.ts +510 -0
  41. package/src/serve/watch-snapshot-ops.ts +541 -0
  42. package/src/serve/watch-snapshot-resolve.ts +246 -0
  43. package/src/serve/watch-snapshot-scan.ts +300 -0
  44. package/src/serve/watch-snapshot-types.ts +392 -0
  45. package/src/serve/watch-snapshot.ts +51 -0
  46. package/src/store/index.ts +1 -1
  47. package/src/store/sqlite/adapter.ts +191 -0
  48. package/src/store/types.ts +66 -0
  49. package/browser-extension/artifacts/gno-browser-clipper-v1.34.5.zip.sha256 +0 -1
@@ -0,0 +1,359 @@
1
+ /**
2
+ * No-follow disk enumeration for watcher fallback classification.
3
+ * Platforms without genuine anchored handles fail closed — no path-based TOCTOU
4
+ * fallback in production (createPathBackedWatcherFs is test-only).
5
+ *
6
+ * @module src/serve/watch-reconciliation-fallback-disk
7
+ */
8
+
9
+ // node:path — Bun has no path join helper.
10
+ import { join } from "node:path";
11
+
12
+ import type { Collection } from "../config/types";
13
+ import type { DirectoryAvailabilityPort } from "../ingestion/source-availability";
14
+
15
+ import {
16
+ matchesCollectionExclusion,
17
+ matchesCollectionSubtreeExclusion,
18
+ } from "../core/path-rules";
19
+ import { collectionToWalkConfig, matchesWalkPath } from "../ingestion";
20
+ import { findUnprovenAvailabilityPrefix } from "../ingestion/source-availability";
21
+ import { defaultFs, openDirByRel } from "./watch-snapshot-scan";
22
+ import {
23
+ isMissingFsError,
24
+ joinWatcherRelPath,
25
+ parentWatcherDir,
26
+ type WatcherSnapshotFs,
27
+ } from "./watch-snapshot-types";
28
+
29
+ export interface FallbackBudget {
30
+ readonly limit: number;
31
+ visitedDirs: number;
32
+ candidates: number;
33
+ removals: number;
34
+ dirtyDirs: number;
35
+ storeRows: number;
36
+ }
37
+
38
+ export function budgetExceeded(b: FallbackBudget): boolean {
39
+ return (
40
+ b.visitedDirs > b.limit ||
41
+ b.candidates > b.limit ||
42
+ b.removals > b.limit ||
43
+ b.dirtyDirs > b.limit ||
44
+ b.storeRows > b.limit
45
+ );
46
+ }
47
+
48
+ /**
49
+ * Production fallback FS: anchored handles only. Unsupported platforms surface
50
+ * scan_failed/ENOTSUP via openDirByRel — never claim path-based safety.
51
+ */
52
+ export function fallbackFs(): WatcherSnapshotFs {
53
+ return defaultFs;
54
+ }
55
+
56
+ export type DiskListResult =
57
+ | {
58
+ status: "ok";
59
+ paths: string[];
60
+ rootDirNames: string[];
61
+ unprovenPrefixes: string[];
62
+ }
63
+ | { status: "overflow" }
64
+ | { status: "error"; cause: unknown };
65
+
66
+ /**
67
+ * Bounded no-follow BFS under `dirRel`. Stops enumeration at remaining+1 names.
68
+ * Never descends through symlinks.
69
+ */
70
+ export async function listEligibleDiskSources(
71
+ rootAbs: string,
72
+ dirRel: string,
73
+ collection: Collection,
74
+ fs: WatcherSnapshotFs,
75
+ budget: FallbackBudget,
76
+ directoryAvailability?: DirectoryAvailabilityPort
77
+ ): Promise<DiskListResult> {
78
+ const walkConfig = collectionToWalkConfig(collection, 0);
79
+ const paths: string[] = [];
80
+ const rootDirNames: string[] = [];
81
+ const unprovenPrefixes: string[] = [];
82
+ const queue: string[] = [dirRel];
83
+ let head = 0;
84
+
85
+ while (head < queue.length) {
86
+ const current = queue[head] as string;
87
+ head += 1;
88
+ if (
89
+ current !== "" &&
90
+ matchesCollectionSubtreeExclusion(current, walkConfig.exclude)
91
+ ) {
92
+ continue;
93
+ }
94
+ budget.visitedDirs += 1;
95
+ if (budgetExceeded(budget)) {
96
+ return { status: "overflow" };
97
+ }
98
+
99
+ const remaining = Math.max(0, budget.limit - budget.visitedDirs + 1);
100
+ if (directoryAvailability?.mode === "local") {
101
+ if (!fs.readDirectChildrenSync) {
102
+ unprovenPrefixes.push(current);
103
+ continue;
104
+ }
105
+ const absPath = current === "" ? rootAbs : join(rootAbs, current);
106
+ const guarded = directoryAvailability.readDirectory(absPath, () =>
107
+ fs.readDirectChildrenSync!(rootAbs, current, remaining)
108
+ );
109
+ if (guarded.kind !== "available") {
110
+ unprovenPrefixes.push(current);
111
+ continue;
112
+ }
113
+ const listed = guarded.value;
114
+ if (listed.status === "missing") {
115
+ continue;
116
+ }
117
+ if (listed.status === "overflow") {
118
+ return { status: "overflow" };
119
+ }
120
+ if (listed.status !== "present") {
121
+ return {
122
+ status: "error",
123
+ cause:
124
+ listed.status === "scan_failed"
125
+ ? listed.cause
126
+ : new Error(`Disk scan failed under ${current || "."}`),
127
+ };
128
+ }
129
+ for (const [name, fingerprint] of listed.entries) {
130
+ const childRel = joinWatcherRelPath(current, name);
131
+ if (fingerprint.kind === "directory") {
132
+ if (matchesCollectionSubtreeExclusion(childRel, walkConfig.exclude)) {
133
+ continue;
134
+ }
135
+ if (current === "") {
136
+ rootDirNames.push(name);
137
+ }
138
+ queue.push(childRel);
139
+ } else if (matchesCollectionExclusion(childRel, walkConfig.exclude)) {
140
+ continue;
141
+ } else if (fingerprint.kind === "symlink") {
142
+ if (matchesWalkPath(childRel, walkConfig)) {
143
+ paths.push(childRel);
144
+ }
145
+ } else if (
146
+ fingerprint.kind === "file" &&
147
+ matchesWalkPath(childRel, walkConfig)
148
+ ) {
149
+ paths.push(childRel);
150
+ }
151
+ if (paths.length > budget.limit) {
152
+ return { status: "overflow" };
153
+ }
154
+ }
155
+ continue;
156
+ }
157
+
158
+ const scanCurrent = async (): Promise<
159
+ | { status: "ok" }
160
+ | { status: "overflow" }
161
+ | { status: "error"; cause: unknown }
162
+ > => {
163
+ const opened = await openDirByRel(rootAbs, current, fs);
164
+ if (opened.status === "missing") {
165
+ return { status: "ok" };
166
+ }
167
+ if (opened.status !== "ok") {
168
+ return {
169
+ status: "error",
170
+ cause:
171
+ opened.status === "scan_failed"
172
+ ? opened.cause
173
+ : new Error(`Disk scan failed under ${current || "."}`),
174
+ };
175
+ }
176
+
177
+ try {
178
+ const listed = await fs.readDir(opened.handle, remaining);
179
+ if (listed.status === "overflow") {
180
+ return { status: "overflow" };
181
+ }
182
+ const names = [...listed.names].sort((a, b) =>
183
+ a < b ? -1 : a > b ? 1 : 0
184
+ );
185
+ for (const name of names) {
186
+ if (name === "" || name === "." || name === "..") {
187
+ continue;
188
+ }
189
+ if (
190
+ name.includes("/") ||
191
+ name.includes("\\") ||
192
+ name.includes("\0")
193
+ ) {
194
+ return {
195
+ status: "error",
196
+ cause: new Error(`Invalid directory entry name: ${name}`),
197
+ };
198
+ }
199
+
200
+ const stat = await fs.lstatChild(opened.handle, name);
201
+ const childRel = joinWatcherRelPath(current, name);
202
+ if (stat.isDirectory()) {
203
+ if (
204
+ matchesCollectionSubtreeExclusion(childRel, walkConfig.exclude)
205
+ ) {
206
+ continue;
207
+ }
208
+ if (current === "") {
209
+ rootDirNames.push(name);
210
+ }
211
+ queue.push(childRel);
212
+ continue;
213
+ }
214
+ if (matchesCollectionExclusion(childRel, walkConfig.exclude)) {
215
+ continue;
216
+ }
217
+ if (stat.isSymbolicLink()) {
218
+ if (matchesWalkPath(childRel, walkConfig)) {
219
+ paths.push(childRel);
220
+ if (paths.length > budget.limit) {
221
+ return { status: "overflow" };
222
+ }
223
+ }
224
+ continue;
225
+ }
226
+ if (!stat.isFile() || !matchesWalkPath(childRel, walkConfig)) {
227
+ continue;
228
+ }
229
+ paths.push(childRel);
230
+ if (paths.length > budget.limit) {
231
+ return { status: "overflow" };
232
+ }
233
+ }
234
+ return { status: "ok" };
235
+ } catch (cause) {
236
+ return { status: "error", cause };
237
+ } finally {
238
+ await fs.closeDir(opened.handle);
239
+ }
240
+ };
241
+
242
+ const scanned = await scanCurrent();
243
+ if (scanned.status !== "ok") {
244
+ return scanned;
245
+ }
246
+ }
247
+
248
+ return { status: "ok", paths, rootDirNames, unprovenPrefixes };
249
+ }
250
+
251
+ export async function inspectNoFollowPresence(
252
+ rootAbs: string,
253
+ relPath: string,
254
+ fs: WatcherSnapshotFs,
255
+ directoryAvailability?: DirectoryAvailabilityPort
256
+ ): Promise<
257
+ | { status: "present"; indexable: boolean }
258
+ | { status: "missing" }
259
+ | { status: "error"; cause: unknown }
260
+ > {
261
+ if (directoryAvailability?.mode === "local") {
262
+ const unproven = await findUnprovenAvailabilityPrefix(
263
+ rootAbs,
264
+ relPath,
265
+ directoryAvailability
266
+ );
267
+ if (unproven) {
268
+ return {
269
+ status: "error",
270
+ cause: new Error(
271
+ `Source absence is unproven under ${unproven.relPath || "."}: ${unproven.code}`
272
+ ),
273
+ };
274
+ }
275
+ }
276
+ const parent = parentWatcherDir(relPath);
277
+ if (parent === null) {
278
+ return { status: "missing" };
279
+ }
280
+ const base = parent === "" ? relPath : relPath.slice(parent.length + 1);
281
+ if (base === "" || base.includes("/")) {
282
+ return { status: "error", cause: new Error(`Invalid path: ${relPath}`) };
283
+ }
284
+ const inspectParent = async (): Promise<
285
+ | { status: "present"; indexable: boolean }
286
+ | { status: "missing" }
287
+ | { status: "error"; cause: unknown }
288
+ > => {
289
+ const opened = await openDirByRel(rootAbs, parent, fs);
290
+ if (opened.status === "missing") {
291
+ return { status: "missing" };
292
+ }
293
+ if (opened.status !== "ok") {
294
+ return {
295
+ status: "error",
296
+ cause:
297
+ opened.status === "scan_failed"
298
+ ? opened.cause
299
+ : new Error("Failed to open parent for presence check"),
300
+ };
301
+ }
302
+ try {
303
+ const stat = await fs.lstatChild(opened.handle, base);
304
+ // Only regular files / symlinks are indexable sources. Directory, FIFO,
305
+ // device, and other specials prove the prior file source is gone.
306
+ const indexable = stat.isFile() || stat.isSymbolicLink();
307
+ return { status: "present", indexable };
308
+ } catch (cause) {
309
+ if (isMissingFsError(cause)) {
310
+ return { status: "missing" };
311
+ }
312
+ return { status: "error", cause };
313
+ } finally {
314
+ await fs.closeDir(opened.handle);
315
+ }
316
+ };
317
+
318
+ if (directoryAvailability?.mode === "local") {
319
+ if (!fs.lstatChildByRelSync) {
320
+ return {
321
+ status: "error",
322
+ cause: new Error(
323
+ "Synchronous anchored child metadata is unavailable in local mode"
324
+ ),
325
+ };
326
+ }
327
+ const parentAbs = parent === "" ? rootAbs : join(rootAbs, parent);
328
+ const guarded = directoryAvailability.readDirectory(parentAbs, () => {
329
+ try {
330
+ return {
331
+ status: "present" as const,
332
+ stat: fs.lstatChildByRelSync!(rootAbs, parent, base),
333
+ };
334
+ } catch (cause) {
335
+ if (isMissingFsError(cause)) {
336
+ return { status: "missing" as const };
337
+ }
338
+ throw cause;
339
+ }
340
+ });
341
+ if (guarded.kind !== "available") {
342
+ return {
343
+ status: "error",
344
+ cause: new Error(
345
+ `Source absence is unproven under ${parent || "."}: ${guarded.code}`
346
+ ),
347
+ };
348
+ }
349
+ if (guarded.value.status === "missing") {
350
+ return { status: "missing" };
351
+ }
352
+ return {
353
+ status: "present",
354
+ indexable:
355
+ guarded.value.stat.isFile() || guarded.value.stat.isSymbolicLink(),
356
+ };
357
+ }
358
+ return inspectParent();
359
+ }