@gmickel/gno 1.34.6 → 1.36.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 +12 -1
- package/assets/skill/SKILL.md +26 -3
- package/assets/skill/cli-reference.md +9 -0
- package/assets/skill/mcp-reference.md +3 -2
- package/browser-extension/artifacts/{gno-browser-clipper-v1.34.6.zip → gno-browser-clipper-v1.36.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.36.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +111 -1
- package/spec/mcp.md +60 -1
- package/spec/output-schemas/mcp-job-status.schema.json +6 -2
- package/spec/output-schemas/peek.schema.json +212 -0
- package/spec/output-schemas/search-results.schema.json +1 -1
- package/src/cli/commands/peek.ts +66 -0
- package/src/cli/options.ts +2 -0
- package/src/cli/program.ts +20 -0
- package/src/config/index.ts +4 -0
- package/src/config/types.ts +14 -0
- package/src/core/path-rules.ts +34 -0
- package/src/core/peek.ts +202 -0
- package/src/ingestion/index.ts +21 -0
- package/src/ingestion/record-container.ts +23 -1
- package/src/ingestion/source-availability/darwin-io.ts +295 -0
- package/src/ingestion/source-availability/darwin-path.ts +58 -0
- package/src/ingestion/source-availability/directory.ts +402 -0
- package/src/ingestion/source-availability/index.ts +74 -0
- package/src/ingestion/source-availability/readers.ts +360 -0
- package/src/ingestion/source-availability/resolve.ts +28 -0
- package/src/ingestion/source-availability/types.ts +170 -0
- package/src/ingestion/sync.ts +197 -24
- package/src/ingestion/types.ts +45 -3
- package/src/ingestion/walker.ts +263 -5
- package/src/mcp/http-egress.ts +1 -0
- package/src/mcp/tools/index.ts +14 -0
- package/src/mcp/tools/peek.ts +78 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/watch-reconciliation-fallback-disk.ts +239 -100
- package/src/serve/watch-reconciliation-fallback.ts +35 -5
- package/src/serve/watch-reconciliation-shared.ts +8 -3
- package/src/serve/watch-reconciliation.ts +7 -0
- package/src/serve/watch-service-flush.ts +10 -0
- package/src/serve/watch-service-lifecycle.ts +2 -0
- package/src/serve/watch-service-snapshot.ts +27 -3
- package/src/serve/watch-service.ts +1 -0
- package/src/serve/watch-snapshot-availability.ts +51 -0
- package/src/serve/watch-snapshot-handles.ts +117 -37
- package/src/serve/watch-snapshot-libc.ts +141 -22
- package/src/serve/watch-snapshot-ops.ts +151 -9
- package/src/serve/watch-snapshot-scan.ts +3 -0
- package/src/serve/watch-snapshot-types.ts +45 -3
- package/browser-extension/artifacts/gno-browser-clipper-v1.34.6.zip.sha256 +0 -1
package/src/ingestion/sync.ts
CHANGED
|
@@ -69,6 +69,19 @@ import {
|
|
|
69
69
|
} from "./frontmatter";
|
|
70
70
|
import { buildLineOffsets } from "./position";
|
|
71
71
|
import { processRecordContainer } from "./record-container";
|
|
72
|
+
import {
|
|
73
|
+
createDirectoryAvailability,
|
|
74
|
+
createSourceContentReader,
|
|
75
|
+
findUnprovenAvailabilityPrefix,
|
|
76
|
+
isSourceAvailabilitySkip,
|
|
77
|
+
isUnprovenAbsenceCode,
|
|
78
|
+
memoizeDirectoryAvailability,
|
|
79
|
+
relPathUnderAnyPrefix,
|
|
80
|
+
resolveSourceAvailability,
|
|
81
|
+
type DirectoryAvailabilityPort,
|
|
82
|
+
type SourceContentReaderPort,
|
|
83
|
+
type SourceReadFailure,
|
|
84
|
+
} from "./source-availability";
|
|
72
85
|
import { getExcludedRanges } from "./strip";
|
|
73
86
|
import { collectionToWalkConfig, DEFAULT_CHUNK_PARAMS } from "./types";
|
|
74
87
|
import { defaultWalker } from "./walker";
|
|
@@ -649,17 +662,32 @@ export class SyncService {
|
|
|
649
662
|
private readonly chunker: ChunkerPort;
|
|
650
663
|
private readonly mimeDetector: MimeDetector;
|
|
651
664
|
private readonly pipeline: ConversionPipeline;
|
|
665
|
+
private readonly sourceReaderFactory: (
|
|
666
|
+
mode: "any" | "local"
|
|
667
|
+
) => SourceContentReaderPort;
|
|
668
|
+
private readonly directoryAvailabilityFactory: (
|
|
669
|
+
mode: "any" | "local"
|
|
670
|
+
) => DirectoryAvailabilityPort;
|
|
652
671
|
|
|
653
672
|
constructor(
|
|
654
673
|
walker?: WalkerPort,
|
|
655
674
|
chunker?: ChunkerPort,
|
|
656
675
|
mimeDetector?: MimeDetector,
|
|
657
|
-
pipeline?: ConversionPipeline
|
|
676
|
+
pipeline?: ConversionPipeline,
|
|
677
|
+
sourceReaderFactory?: (mode: "any" | "local") => SourceContentReaderPort,
|
|
678
|
+
directoryAvailabilityFactory?: (
|
|
679
|
+
mode: "any" | "local"
|
|
680
|
+
) => DirectoryAvailabilityPort
|
|
658
681
|
) {
|
|
659
682
|
this.walker = walker ?? defaultWalker;
|
|
660
683
|
this.chunker = chunker ?? defaultChunker;
|
|
661
684
|
this.mimeDetector = mimeDetector ?? getDefaultMimeDetector();
|
|
662
685
|
this.pipeline = pipeline ?? getDefaultPipeline();
|
|
686
|
+
this.sourceReaderFactory =
|
|
687
|
+
sourceReaderFactory ?? ((mode) => createSourceContentReader(mode));
|
|
688
|
+
this.directoryAvailabilityFactory =
|
|
689
|
+
directoryAvailabilityFactory ??
|
|
690
|
+
((mode) => createDirectoryAvailability(mode));
|
|
663
691
|
}
|
|
664
692
|
|
|
665
693
|
private async selectRecordAdapter(
|
|
@@ -766,6 +794,28 @@ export class SyncService {
|
|
|
766
794
|
const contentTypeRulesFingerprint =
|
|
767
795
|
options.contentTypeRulesFingerprint ??
|
|
768
796
|
fingerprintContentTypeMetadataRules(contentTypeRules);
|
|
797
|
+
|
|
798
|
+
// 2. Local mode performs one guarded content-boundary read. `any` keeps
|
|
799
|
+
// the legacy record-stream and sniff/read paths byte-for-byte unchanged.
|
|
800
|
+
// No availability syscall is added during discovery.
|
|
801
|
+
const availabilityMode = resolveSourceAvailability(collection, options);
|
|
802
|
+
let guardedBytes: Uint8Array | undefined;
|
|
803
|
+
if (availabilityMode === "local") {
|
|
804
|
+
const sourceRead = await this.sourceReaderFactory("local").readAll(
|
|
805
|
+
entry.absPath,
|
|
806
|
+
sourceSize
|
|
807
|
+
);
|
|
808
|
+
if (!sourceRead.ok) {
|
|
809
|
+
return await this.finishSourceAvailabilityFailure(
|
|
810
|
+
collection,
|
|
811
|
+
entry,
|
|
812
|
+
store,
|
|
813
|
+
sourceRead
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
guardedBytes = sourceRead.bytes;
|
|
817
|
+
}
|
|
818
|
+
|
|
769
819
|
if (recordAdapter) {
|
|
770
820
|
return await processRecordContainer({
|
|
771
821
|
adapter: recordAdapter,
|
|
@@ -782,13 +832,16 @@ export class SyncService {
|
|
|
782
832
|
sourceCtime,
|
|
783
833
|
sourceMtime,
|
|
784
834
|
sourceSize,
|
|
835
|
+
sourceBytes: guardedBytes,
|
|
785
836
|
store,
|
|
786
837
|
});
|
|
787
838
|
}
|
|
788
839
|
|
|
789
|
-
const sniffBytes =
|
|
790
|
-
|
|
791
|
-
|
|
840
|
+
const sniffBytes = guardedBytes
|
|
841
|
+
? guardedBytes.subarray(0, Math.min(512, guardedBytes.byteLength))
|
|
842
|
+
: new Uint8Array(
|
|
843
|
+
await Bun.file(entry.absPath).slice(0, 512).arrayBuffer()
|
|
844
|
+
);
|
|
792
845
|
const mime = this.mimeDetector.detect(entry.relPath, sniffBytes);
|
|
793
846
|
|
|
794
847
|
const priorRecordDocuments = mustOk(
|
|
@@ -797,10 +850,9 @@ export class SyncService {
|
|
|
797
850
|
{ collection: collection.name, relPath: entry.relPath }
|
|
798
851
|
);
|
|
799
852
|
|
|
800
|
-
|
|
801
|
-
const bytes = await Bun.file(entry.absPath).bytes();
|
|
853
|
+
const bytes = guardedBytes ?? (await Bun.file(entry.absPath).bytes());
|
|
802
854
|
|
|
803
|
-
// 3. Compute sourceHash
|
|
855
|
+
// 3. Compute sourceHash from source bytes. Local mode cannot reopen.
|
|
804
856
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
805
857
|
hasher.update(bytes);
|
|
806
858
|
const sourceHash = hasher.digest("hex");
|
|
@@ -1152,6 +1204,38 @@ export class SyncService {
|
|
|
1152
1204
|
}
|
|
1153
1205
|
}
|
|
1154
1206
|
|
|
1207
|
+
/**
|
|
1208
|
+
* Translate guarded-read failures into skip (cloud placeholder/partial) or
|
|
1209
|
+
* fail-closed error outcomes. Never surfaces materialization refusal as a
|
|
1210
|
+
* conversion error.
|
|
1211
|
+
*/
|
|
1212
|
+
private async finishSourceAvailabilityFailure(
|
|
1213
|
+
collection: Collection,
|
|
1214
|
+
entry: WalkEntry,
|
|
1215
|
+
store: StorePort,
|
|
1216
|
+
failure: SourceReadFailure
|
|
1217
|
+
): Promise<FileSyncResult> {
|
|
1218
|
+
const status = isSourceAvailabilitySkip(failure.code) ? "skipped" : "error";
|
|
1219
|
+
await store
|
|
1220
|
+
.recordError({
|
|
1221
|
+
collection: collection.name,
|
|
1222
|
+
relPath: entry.relPath,
|
|
1223
|
+
code: failure.code,
|
|
1224
|
+
message: failure.message,
|
|
1225
|
+
details:
|
|
1226
|
+
failure.errno === undefined || failure.errno === null
|
|
1227
|
+
? undefined
|
|
1228
|
+
: { errno: failure.errno },
|
|
1229
|
+
})
|
|
1230
|
+
.catch(() => undefined);
|
|
1231
|
+
return {
|
|
1232
|
+
relPath: entry.relPath,
|
|
1233
|
+
status,
|
|
1234
|
+
errorCode: failure.code,
|
|
1235
|
+
errorMessage: failure.message,
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1155
1239
|
private async readPreviousStructure(
|
|
1156
1240
|
store: StorePort,
|
|
1157
1241
|
existing: DocumentRow | null
|
|
@@ -1251,6 +1335,10 @@ export class SyncService {
|
|
|
1251
1335
|
const results: FileSyncResult[] = [];
|
|
1252
1336
|
const projectionSourceIds = new Set<number>();
|
|
1253
1337
|
let markedInactive = 0;
|
|
1338
|
+
const availabilityMode = resolveSourceAvailability(collection, syncOptions);
|
|
1339
|
+
const directoryAvailability = memoizeDirectoryAvailability(
|
|
1340
|
+
this.directoryAvailabilityFactory(availabilityMode)
|
|
1341
|
+
);
|
|
1254
1342
|
|
|
1255
1343
|
for (const relPath of relPaths) {
|
|
1256
1344
|
const recordDocumentsResult = await store.listRecordDocuments(
|
|
@@ -1316,6 +1404,33 @@ export class SyncService {
|
|
|
1316
1404
|
continue;
|
|
1317
1405
|
}
|
|
1318
1406
|
|
|
1407
|
+
// Direct syncPaths must not bypass directory-availability guards.
|
|
1408
|
+
const unprovenPrefix = await findUnprovenAvailabilityPrefix(
|
|
1409
|
+
collection.path,
|
|
1410
|
+
relPath,
|
|
1411
|
+
directoryAvailability
|
|
1412
|
+
);
|
|
1413
|
+
if (unprovenPrefix) {
|
|
1414
|
+
const status = isSourceAvailabilitySkip(unprovenPrefix.code)
|
|
1415
|
+
? "skipped"
|
|
1416
|
+
: "error";
|
|
1417
|
+
await store
|
|
1418
|
+
.recordError({
|
|
1419
|
+
collection: collection.name,
|
|
1420
|
+
relPath: unprovenPrefix.relPath || relPath,
|
|
1421
|
+
code: unprovenPrefix.code,
|
|
1422
|
+
message: unprovenPrefix.message,
|
|
1423
|
+
})
|
|
1424
|
+
.catch(() => undefined);
|
|
1425
|
+
results.push({
|
|
1426
|
+
relPath,
|
|
1427
|
+
status,
|
|
1428
|
+
errorCode: unprovenPrefix.code,
|
|
1429
|
+
errorMessage: unprovenPrefix.message,
|
|
1430
|
+
});
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1319
1434
|
const absPath = join(collection.path, relPath);
|
|
1320
1435
|
let stats: Awaited<ReturnType<typeof stat>>;
|
|
1321
1436
|
try {
|
|
@@ -1881,23 +1996,44 @@ export class SyncService {
|
|
|
1881
1996
|
await gitPull(collection.path);
|
|
1882
1997
|
}
|
|
1883
1998
|
|
|
1884
|
-
// 2. Walk collection
|
|
1999
|
+
// 2. Walk collection (local mode refuses dataless/unproven directory descent)
|
|
1885
2000
|
const maxBytes = options.limits?.maxBytes ?? DEFAULT_LIMITS.maxBytes;
|
|
1886
|
-
const
|
|
2001
|
+
const availabilityMode = resolveSourceAvailability(collection, syncOptions);
|
|
2002
|
+
const directoryAvailability = memoizeDirectoryAvailability(
|
|
2003
|
+
this.directoryAvailabilityFactory(availabilityMode)
|
|
2004
|
+
);
|
|
2005
|
+
const walkConfig = {
|
|
2006
|
+
...collectionToWalkConfig(collection, maxBytes, syncOptions),
|
|
2007
|
+
sourceAvailability: availabilityMode,
|
|
2008
|
+
directoryAvailability,
|
|
2009
|
+
};
|
|
1887
2010
|
const { entries, skipped } = await this.walker.walk(walkConfig);
|
|
1888
2011
|
|
|
1889
2012
|
// Track seen paths for marking inactive
|
|
1890
2013
|
// Only include TOO_LARGE files (they exist but are unprocessable)
|
|
1891
2014
|
// EXCLUDED files should NOT be in seenPaths - if config changes to exclude
|
|
1892
2015
|
// a previously-included file, that doc SHOULD be marked inactive
|
|
2016
|
+
// Unproven prefixes (dataless / availability-unknown dirs) preserve
|
|
2017
|
+
// previously indexed descendants — never treat them as proven deletions.
|
|
1893
2018
|
const seenPaths = new Set<string>();
|
|
2019
|
+
const unprovenPrefixes: string[] = [];
|
|
1894
2020
|
for (const skip of skipped) {
|
|
1895
2021
|
if (skip.reason === "TOO_LARGE") {
|
|
1896
2022
|
seenPaths.add(skip.relPath);
|
|
1897
2023
|
}
|
|
2024
|
+
if (skip.unprovenPrefix || isUnprovenAbsenceCode(skip.reason)) {
|
|
2025
|
+
unprovenPrefixes.push(skip.relPath);
|
|
2026
|
+
}
|
|
1898
2027
|
}
|
|
1899
2028
|
|
|
1900
|
-
|
|
2029
|
+
let added = 0;
|
|
2030
|
+
let updated = 0;
|
|
2031
|
+
let unchanged = 0;
|
|
2032
|
+
let errored = 0;
|
|
2033
|
+
let dynamicSkipped = 0;
|
|
2034
|
+
const fileResults: FileSyncResult[] = [];
|
|
2035
|
+
|
|
2036
|
+
// 3. Record TOO_LARGE / availability-prefix outcomes for receipts
|
|
1901
2037
|
for (const skip of skipped) {
|
|
1902
2038
|
if (skip.reason === "TOO_LARGE") {
|
|
1903
2039
|
const recordResult = await store.recordError({
|
|
@@ -1919,6 +2055,35 @@ export class SyncService {
|
|
|
1919
2055
|
code: "TOO_LARGE",
|
|
1920
2056
|
message: `File size ${skip.size} exceeds limit ${maxBytes}`,
|
|
1921
2057
|
});
|
|
2058
|
+
continue;
|
|
2059
|
+
}
|
|
2060
|
+
if (skip.unprovenPrefix || isUnprovenAbsenceCode(skip.reason)) {
|
|
2061
|
+
const message =
|
|
2062
|
+
skip.message ??
|
|
2063
|
+
`Directory availability refused descent (${skip.reason})`;
|
|
2064
|
+
await store
|
|
2065
|
+
.recordError({
|
|
2066
|
+
collection: collection.name,
|
|
2067
|
+
relPath: skip.relPath,
|
|
2068
|
+
code: skip.reason,
|
|
2069
|
+
message,
|
|
2070
|
+
})
|
|
2071
|
+
.catch(() => undefined);
|
|
2072
|
+
errors.push({
|
|
2073
|
+
relPath: skip.relPath,
|
|
2074
|
+
code: skip.reason,
|
|
2075
|
+
message,
|
|
2076
|
+
});
|
|
2077
|
+
const isSkip = isSourceAvailabilitySkip(skip.reason);
|
|
2078
|
+
fileResults.push({
|
|
2079
|
+
relPath: skip.relPath === "" ? "." : skip.relPath,
|
|
2080
|
+
status: isSkip ? "skipped" : "error",
|
|
2081
|
+
errorCode: skip.reason,
|
|
2082
|
+
errorMessage: message,
|
|
2083
|
+
});
|
|
2084
|
+
if (!isSkip) {
|
|
2085
|
+
errored += 1;
|
|
2086
|
+
}
|
|
1922
2087
|
}
|
|
1923
2088
|
}
|
|
1924
2089
|
|
|
@@ -1927,14 +2092,6 @@ export class SyncService {
|
|
|
1927
2092
|
1,
|
|
1928
2093
|
Math.min(MAX_CONCURRENCY, options.concurrency ?? DEFAULT_CONCURRENCY)
|
|
1929
2094
|
);
|
|
1930
|
-
|
|
1931
|
-
let added = 0;
|
|
1932
|
-
let updated = 0;
|
|
1933
|
-
let unchanged = 0;
|
|
1934
|
-
let errored = 0;
|
|
1935
|
-
let dynamicSkipped = 0;
|
|
1936
|
-
const fileResults: FileSyncResult[] = [];
|
|
1937
|
-
|
|
1938
2095
|
if (concurrency === 1) {
|
|
1939
2096
|
// Sequential processing with batched transactions (Windows perf)
|
|
1940
2097
|
for (let i = 0; i < entries.length; i += TX_BATCH_SIZE) {
|
|
@@ -2072,11 +2229,20 @@ export class SyncService {
|
|
|
2072
2229
|
});
|
|
2073
2230
|
} else {
|
|
2074
2231
|
const missingPaths = existingDocsResult.value
|
|
2075
|
-
.filter(
|
|
2076
|
-
(document)
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2232
|
+
.filter((document) => {
|
|
2233
|
+
if (!document.active) {
|
|
2234
|
+
return false;
|
|
2235
|
+
}
|
|
2236
|
+
const sourcePath = document.recordSourcePath ?? document.relPath;
|
|
2237
|
+
if (seenPaths.has(sourcePath)) {
|
|
2238
|
+
return false;
|
|
2239
|
+
}
|
|
2240
|
+
// Absence under an unenumerated/unproven prefix is not proven deletion.
|
|
2241
|
+
if (relPathUnderAnyPrefix(sourcePath, unprovenPrefixes)) {
|
|
2242
|
+
return false;
|
|
2243
|
+
}
|
|
2244
|
+
return true;
|
|
2245
|
+
})
|
|
2080
2246
|
.map((d) => d.relPath);
|
|
2081
2247
|
|
|
2082
2248
|
if (missingPaths.length > 0) {
|
|
@@ -2109,6 +2275,13 @@ export class SyncService {
|
|
|
2109
2275
|
errors.push(...(await this.projectTypedEdges(store, syncOptions)));
|
|
2110
2276
|
}
|
|
2111
2277
|
|
|
2278
|
+
const walkerSkippedCount = skipped.filter(
|
|
2279
|
+
(skip) =>
|
|
2280
|
+
skip.reason === "TOO_LARGE" ||
|
|
2281
|
+
skip.reason === "EXCLUDED" ||
|
|
2282
|
+
isSourceAvailabilitySkip(skip.reason)
|
|
2283
|
+
).length;
|
|
2284
|
+
|
|
2112
2285
|
return {
|
|
2113
2286
|
collection: collection.name,
|
|
2114
2287
|
filesProcessed: entries.length + inventoryErrored,
|
|
@@ -2116,7 +2289,7 @@ export class SyncService {
|
|
|
2116
2289
|
filesUpdated: updated,
|
|
2117
2290
|
filesUnchanged: unchanged,
|
|
2118
2291
|
filesErrored: errored + inventoryErrored,
|
|
2119
|
-
filesSkipped:
|
|
2292
|
+
filesSkipped: walkerSkippedCount + dynamicSkipped,
|
|
2120
2293
|
filesMarkedInactive: markedInactive,
|
|
2121
2294
|
durationMs: Date.now() - startTime,
|
|
2122
2295
|
files: fileResults,
|
package/src/ingestion/types.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
RecordAttachmentInventoryItem,
|
|
13
13
|
} from "../converters/types";
|
|
14
14
|
import type { EgressLineage } from "../core/egress-provenance";
|
|
15
|
+
import type { DirectoryAvailabilityPort } from "./source-availability/types";
|
|
15
16
|
|
|
16
17
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
18
|
// Walker Types
|
|
@@ -45,14 +46,46 @@ export interface WalkConfig {
|
|
|
45
46
|
exclude: string[];
|
|
46
47
|
/** Max file size in bytes (files larger are skipped) */
|
|
47
48
|
maxBytes: number;
|
|
49
|
+
/**
|
|
50
|
+
* Source availability mode for this walk.
|
|
51
|
+
* `any` (default) keeps Bun.Glob traversal unchanged.
|
|
52
|
+
* `local` refuses descent into unproven/dataless directories.
|
|
53
|
+
*/
|
|
54
|
+
sourceAvailability?: "any" | "local";
|
|
55
|
+
/**
|
|
56
|
+
* Optional injectable directory classifier (tests / SyncService wiring).
|
|
57
|
+
* When omitted, FileWalker builds one from `sourceAvailability`.
|
|
58
|
+
*/
|
|
59
|
+
directoryAvailability?: DirectoryAvailabilityPort;
|
|
48
60
|
}
|
|
49
61
|
|
|
50
|
-
/**
|
|
62
|
+
/** Skip reasons emitted by the walker (and mirrored into sync receipts). */
|
|
63
|
+
export type WalkSkipReason =
|
|
64
|
+
| "TOO_LARGE"
|
|
65
|
+
| "EXCLUDED"
|
|
66
|
+
| "DATALESS_DIRECTORY"
|
|
67
|
+
| "CLOUD_PLACEHOLDER"
|
|
68
|
+
| "CLOUD_PARTIAL"
|
|
69
|
+
| "SOURCE_AVAILABILITY_UNSUPPORTED"
|
|
70
|
+
| "SOURCE_AVAILABILITY_POLICY_FAILED"
|
|
71
|
+
| "SOURCE_AVAILABILITY_UNKNOWN"
|
|
72
|
+
| "PERMISSION"
|
|
73
|
+
| "NOT_FOUND"
|
|
74
|
+
| "NOT_FILE"
|
|
75
|
+
| "IO_ERROR";
|
|
76
|
+
|
|
77
|
+
/** Skipped file or directory-prefix entry (for error tracking / reconciliation) */
|
|
51
78
|
export interface SkippedEntry {
|
|
52
79
|
absPath: string;
|
|
53
80
|
relPath: string;
|
|
54
|
-
reason:
|
|
81
|
+
reason: WalkSkipReason;
|
|
55
82
|
size?: number;
|
|
83
|
+
/**
|
|
84
|
+
* When true, absence of descendants under this prefix is unproven —
|
|
85
|
+
* reconciliation must preserve previously indexed sources.
|
|
86
|
+
*/
|
|
87
|
+
unprovenPrefix?: boolean;
|
|
88
|
+
message?: string;
|
|
56
89
|
}
|
|
57
90
|
|
|
58
91
|
/** Walker port interface */
|
|
@@ -146,6 +179,11 @@ export interface SyncOptions {
|
|
|
146
179
|
contentTypeRulesFingerprint?: string;
|
|
147
180
|
/** Internal orchestration flag: defer graph projection to an outer sync. */
|
|
148
181
|
projectTypedEdges?: boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Optional run-level override for source availability (`any` | `local`).
|
|
184
|
+
* Wins over collection config when set. Distinct from egress policy.
|
|
185
|
+
*/
|
|
186
|
+
sourceAvailability?: "any" | "local";
|
|
149
187
|
}
|
|
150
188
|
|
|
151
189
|
export type ContentTypeSource =
|
|
@@ -292,9 +330,12 @@ const TRANSCRIPT_EXTENSION_BY_FORMAT = {
|
|
|
292
330
|
*/
|
|
293
331
|
export function collectionToWalkConfig(
|
|
294
332
|
collection: Collection,
|
|
295
|
-
maxBytes: number
|
|
333
|
+
maxBytes: number,
|
|
334
|
+
options?: Pick<SyncOptions, "sourceAvailability">
|
|
296
335
|
): WalkConfig {
|
|
297
336
|
const transcriptFormat = collection.recordAdapters?.transcript?.format;
|
|
337
|
+
const sourceAvailability =
|
|
338
|
+
options?.sourceAvailability ?? collection.sourceAvailability ?? undefined;
|
|
298
339
|
return {
|
|
299
340
|
root: collection.path,
|
|
300
341
|
pattern: collection.pattern,
|
|
@@ -304,5 +345,6 @@ export function collectionToWalkConfig(
|
|
|
304
345
|
: [],
|
|
305
346
|
exclude: collection.exclude,
|
|
306
347
|
maxBytes,
|
|
348
|
+
...(sourceAvailability ? { sourceAvailability } : {}),
|
|
307
349
|
};
|
|
308
350
|
}
|