@indigoai-us/hq-cloud 6.14.22 → 6.14.24
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/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +20 -0
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-events.test.js +22 -0
- package/dist/bin/sync-runner-events.test.js.map +1 -1
- package/dist/bin/sync-runner-planning.d.ts +11 -0
- package/dist/bin/sync-runner-planning.d.ts.map +1 -1
- package/dist/bin/sync-runner-planning.js +37 -2
- package/dist/bin/sync-runner-planning.js.map +1 -1
- package/dist/bin/sync-runner-planning.test.js +1 -0
- package/dist/bin/sync-runner-planning.test.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +17 -1
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +33 -1
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +28 -3
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +206 -2
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/doctor.d.ts +119 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +485 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/doctor.test.d.ts +13 -0
- package/dist/cli/doctor.test.d.ts.map +1 -0
- package/dist/cli/doctor.test.js +485 -0
- package/dist/cli/doctor.test.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/share.js +6 -1
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +1 -0
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts +54 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +223 -3
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +394 -1
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/cognito-auth.test.js +7 -0
- package/dist/cognito-auth.test.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/conflict-file.d.ts.map +1 -1
- package/dist/lib/conflict-file.js +5 -1
- package/dist/lib/conflict-file.js.map +1 -1
- package/dist/lib/conflict-index.d.ts +7 -0
- package/dist/lib/conflict-index.d.ts.map +1 -1
- package/dist/lib/conflict-index.js +10 -2
- package/dist/lib/conflict-index.js.map +1 -1
- package/dist/lib/conflict.test.js +31 -0
- package/dist/lib/conflict.test.js.map +1 -1
- package/dist/lib/machine-id.test.js +14 -0
- package/dist/lib/machine-id.test.js.map +1 -1
- package/dist/local-path-codec.d.ts +18 -0
- package/dist/local-path-codec.d.ts.map +1 -1
- package/dist/local-path-codec.js +63 -0
- package/dist/local-path-codec.js.map +1 -1
- package/dist/local-path-codec.test.d.ts +14 -0
- package/dist/local-path-codec.test.d.ts.map +1 -0
- package/dist/local-path-codec.test.js +102 -0
- package/dist/local-path-codec.test.js.map +1 -0
- package/dist/machine-auth.test.js +7 -0
- package/dist/machine-auth.test.js.map +1 -1
- package/dist/s3.d.ts +5 -0
- package/dist/s3.d.ts.map +1 -1
- package/dist/s3.js +129 -3
- package/dist/s3.js.map +1 -1
- package/dist/s3.symlink-materialize.test.d.ts +18 -0
- package/dist/s3.symlink-materialize.test.d.ts.map +1 -0
- package/dist/s3.symlink-materialize.test.js +394 -0
- package/dist/s3.symlink-materialize.test.js.map +1 -0
- package/dist/s3.test.js +19 -0
- package/dist/s3.test.js.map +1 -1
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +23 -2
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/skill-telemetry.test.js +17 -0
- package/dist/skill-telemetry.test.js.map +1 -1
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +10 -1
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +28 -0
- package/dist/watcher.test.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner-company.ts +18 -0
- package/src/bin/sync-runner-events.test.ts +31 -0
- package/src/bin/sync-runner-planning.test.ts +1 -0
- package/src/bin/sync-runner-planning.ts +46 -3
- package/src/bin/sync-runner-watch-loop.ts +19 -1
- package/src/bin/sync-runner.test.ts +236 -1
- package/src/bin/sync-runner.ts +57 -2
- package/src/cli/doctor.test.ts +581 -0
- package/src/cli/doctor.ts +640 -0
- package/src/cli/index.ts +14 -0
- package/src/cli/share.test.ts +1 -0
- package/src/cli/share.ts +6 -1
- package/src/cli/sync.test.ts +473 -1
- package/src/cli/sync.ts +310 -3
- package/src/cognito-auth.test.ts +5 -0
- package/src/index.ts +10 -0
- package/src/lib/conflict-file.ts +6 -1
- package/src/lib/conflict-index.ts +18 -2
- package/src/lib/conflict.test.ts +33 -0
- package/src/lib/machine-id.test.ts +10 -0
- package/src/local-path-codec.test.ts +138 -0
- package/src/local-path-codec.ts +66 -0
- package/src/machine-auth.test.ts +5 -0
- package/src/s3.symlink-materialize.test.ts +492 -0
- package/src/s3.test.ts +24 -0
- package/src/s3.ts +148 -4
- package/src/skill-telemetry.test.ts +19 -0
- package/src/skill-telemetry.ts +31 -2
- package/src/watcher.test.ts +33 -0
- package/src/watcher.ts +10 -1
package/dist/cli/sync.d.ts
CHANGED
|
@@ -217,6 +217,31 @@ export type SyncProgressEvent = {
|
|
|
217
217
|
type: "scope-excluded";
|
|
218
218
|
count: number;
|
|
219
219
|
samplePaths: string[];
|
|
220
|
+
} | {
|
|
221
|
+
/**
|
|
222
|
+
* Emitted at most ONCE per PULL leg when the leg ran under a
|
|
223
|
+
* membership-scoped `syncMode` (`"shared"` or `"custom"` — never `"all"`)
|
|
224
|
+
* AND one or more remote keys were skipped as out-of-scope
|
|
225
|
+
* (`filesOutOfScope > 0`). This turns the silent shared-vs-all gap into a
|
|
226
|
+
* VISIBLE, actionable surface. The reporter of feedback_d2082110 lost
|
|
227
|
+
* files across devices precisely because four memberships defaulted to
|
|
228
|
+
* `shared` rather than `all`, so not all vault content materialized on the
|
|
229
|
+
* second device — and nothing told them; they had to notice the gap and
|
|
230
|
+
* flip each membership to `all` by hand. This event names the gap and the
|
|
231
|
+
* lever (raise the membership's access level to `all`) so FULL
|
|
232
|
+
* materialization is a deliberate, visible choice rather than a silent
|
|
233
|
+
* omission.
|
|
234
|
+
*
|
|
235
|
+
* `count` is the number of remote keys skipped as out-of-scope on this
|
|
236
|
+
* leg; `samplePaths` carries up to 10 company-relative keys for display;
|
|
237
|
+
* `syncMode` is the active scoped mode. Distinct from the push-side
|
|
238
|
+
* `scope-excluded` (which reports what a grantee could not PUSH). Not
|
|
239
|
+
* emitted in `all` mode or when `count === 0` — no gap, no noise.
|
|
240
|
+
*/
|
|
241
|
+
type: "scope-materialization-gap";
|
|
242
|
+
count: number;
|
|
243
|
+
samplePaths: string[];
|
|
244
|
+
syncMode: SyncMode;
|
|
220
245
|
} | {
|
|
221
246
|
/**
|
|
222
247
|
* Emitted at most ONCE per `share()` push leg when the base ignore
|
|
@@ -282,6 +307,26 @@ export type SyncProgressEvent = {
|
|
|
282
307
|
path: string;
|
|
283
308
|
/** Server-compatible invalid-key code, such as INVALID_KEY_CONTROL_CHARS. */
|
|
284
309
|
errorCode?: string;
|
|
310
|
+
} | {
|
|
311
|
+
/**
|
|
312
|
+
* Emitted by the PULL leg once per remote key skipped because its
|
|
313
|
+
* spelling percent-decodes to the same logical path as an
|
|
314
|
+
* already-journaled key with a DIFFERENT spelling — the mixed-version
|
|
315
|
+
* amplifier (a pre-codec peer minting `%3A`/`%253A` twins of a
|
|
316
|
+
* canonical `:` key). Downloading the twin would materialize a junk
|
|
317
|
+
* local dir and fork the journal into a second key family for one
|
|
318
|
+
* logical path, so the established spelling wins and the twin is
|
|
319
|
+
* skipped. Like `skip-size-limit`, this is deliberately NOT
|
|
320
|
+
* `type: "error"` (policy hq-sync-deliberate-skip-not-fatal-error-exit2:
|
|
321
|
+
* a recurring benign per-file skip emitted as an error lands in the
|
|
322
|
+
* runner's `errors[]`, forces exit 2 on every pass, and the menubar
|
|
323
|
+
* Sentry-alerts each time). The US-003 vault doctor is the cleanup
|
|
324
|
+
* path that collapses the junk family itself.
|
|
325
|
+
*/
|
|
326
|
+
type: "skip-junk-key-spelling";
|
|
327
|
+
path: string;
|
|
328
|
+
/** The already-journaled spelling that owns this logical path. */
|
|
329
|
+
journaledKey: string;
|
|
285
330
|
};
|
|
286
331
|
export interface SyncOptions {
|
|
287
332
|
/** Company slug or UID (defaults to active company from config) */
|
|
@@ -526,4 +571,13 @@ export declare function reportNewFilesToNotify(vaultConfig: VaultServiceConfig,
|
|
|
526
571
|
* Sync (pull) all allowed files from the entity vault.
|
|
527
572
|
*/
|
|
528
573
|
export declare function sync(options: SyncOptions): Promise<SyncResult>;
|
|
574
|
+
/**
|
|
575
|
+
* win32 only: true when an existing local symlink's TARGET exists but the
|
|
576
|
+
* link cannot be traversed as that target's type — the file-flavor-link-at-
|
|
577
|
+
* a-directory shape minted while the target was absent (or by an older
|
|
578
|
+
* client). A dangling link is NOT broken-flavored (there is nothing to
|
|
579
|
+
* repair until the target appears). POSIX links are flavorless: always
|
|
580
|
+
* false off win32.
|
|
581
|
+
*/
|
|
582
|
+
export declare function win32SymlinkFlavorBroken(localPath: string, win32?: boolean): boolean;
|
|
529
583
|
//# sourceMappingURL=sync.d.ts.map
|
package/dist/cli/sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/cli/sync.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAEV,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAA+B,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/cli/sync.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/sync.js
CHANGED
|
@@ -10,7 +10,8 @@ import { VaultAuthError, VaultClient } from "../vault-client.js";
|
|
|
10
10
|
import { emitCloudTelemetry, } from "../telemetry-events.js";
|
|
11
11
|
import { resolveEntityContext, isExpiringSoon, refreshEntityContext } from "../context.js";
|
|
12
12
|
import { createSyncProgressRecorder } from "../sync-progress.js";
|
|
13
|
-
import { localPathForVaultKey, vaultKeyForLocalPath } from "../local-path-codec.js";
|
|
13
|
+
import { canonicalVaultKeySpelling, localPathForVaultKey, vaultKeyForLocalPath, } from "../local-path-codec.js";
|
|
14
|
+
import { SKILLS_KEY_PREFIX } from "./doctor.js";
|
|
14
15
|
import { downloadFile, listRemoteFiles, headRemoteFile, primeObjectTransport, toPosixKey, classifyVaultKey, DanglingSymlinkParentError, } from "../s3.js";
|
|
15
16
|
import { readJournal, writeJournal, hashFile, hashSymlinkTarget, updateEntry, removeEntry, getEntry, normalizeEtag, migrateToV2, gcTombstones, isTombstone, tombstoneEntry, lastPullRecord, appendPullRecord, generatePullId, PERSONAL_VAULT_JOURNAL_SLUG, migratePersonalVaultJournal, } from "../journal.js";
|
|
16
17
|
import { isGeneratedCoreMirrorKey, PERSONAL_VAULT_MANIFEST_KEY, } from "../personal-vault.js";
|
|
@@ -195,6 +196,7 @@ async function syncWithOperationLockHeld(options) {
|
|
|
195
196
|
await emitAndReportNewFiles(run, plan);
|
|
196
197
|
await verifyPlannedJournalTombstones(run, plan);
|
|
197
198
|
executeJournalTombstoneDeletes(run, plan, counters);
|
|
199
|
+
emitScopeMaterializationGap(run, plan, counters);
|
|
198
200
|
return finalizePullRun(run, plan, scopeRun, counters);
|
|
199
201
|
}
|
|
200
202
|
async function buildPullContext(options) {
|
|
@@ -434,7 +436,8 @@ async function executeConflictExecutor(run, plan, scopeRun, counters) {
|
|
|
434
436
|
item.action === "skip-personal-mode" ||
|
|
435
437
|
item.action === "skip-unchanged" ||
|
|
436
438
|
item.action === "skip-local-only" ||
|
|
437
|
-
item.action === "skip-stale-overlay-marker"
|
|
439
|
+
item.action === "skip-stale-overlay-marker" ||
|
|
440
|
+
item.action === "skip-overlay-marker-with-children") {
|
|
438
441
|
counters.filesSkipped++;
|
|
439
442
|
continue;
|
|
440
443
|
}
|
|
@@ -445,6 +448,18 @@ async function executeConflictExecutor(run, plan, scopeRun, counters) {
|
|
|
445
448
|
counters.filesOutOfScope++;
|
|
446
449
|
continue;
|
|
447
450
|
}
|
|
451
|
+
if (item.action === "skip-junk-key-spelling") {
|
|
452
|
+
// Deliberate, recoverable skip — counted as skipped and surfaced via a
|
|
453
|
+
// dedicated warning event, never `type: "error"` (see the event doc /
|
|
454
|
+
// policy hq-sync-deliberate-skip-not-fatal-error-exit2).
|
|
455
|
+
counters.filesSkipped++;
|
|
456
|
+
run.emit({
|
|
457
|
+
type: "skip-junk-key-spelling",
|
|
458
|
+
path: item.remoteFile.key,
|
|
459
|
+
journaledKey: item.journaledKey,
|
|
460
|
+
});
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
448
463
|
if (item.action === "tombstone-delete") {
|
|
449
464
|
executeFileTombstoneDelete(run, item, counters);
|
|
450
465
|
continue;
|
|
@@ -971,6 +986,34 @@ function finalizePullRun(run, plan, scopeRun, counters) {
|
|
|
971
986
|
changedPaths: [...counters.changedPaths, ...scopeRun.changedPaths],
|
|
972
987
|
};
|
|
973
988
|
}
|
|
989
|
+
/**
|
|
990
|
+
* Surface the shared-vs-all materialization gap (feedback_d2082110). When a
|
|
991
|
+
* pull leg ran under a membership-scoped `syncMode` and skipped one or more
|
|
992
|
+
* remote keys as out-of-scope, emit a single summary event so the operator
|
|
993
|
+
* SEES that not all vault content materialized on this device and knows the
|
|
994
|
+
* lever — raise the membership's access level to `all`. Silent in `all` mode
|
|
995
|
+
* (nothing is scoped away) and when nothing fell out of scope: no gap, no
|
|
996
|
+
* noise. Deliberately AFTER the transfer executors, so `filesOutOfScope` is
|
|
997
|
+
* final; sample keys are read off the plan's `skip-out-of-scope` items (pure,
|
|
998
|
+
* no I/O). This is the visible complement to the previously-silent
|
|
999
|
+
* `SyncResult.filesOutOfScope` count.
|
|
1000
|
+
*/
|
|
1001
|
+
function emitScopeMaterializationGap(run, plan, counters) {
|
|
1002
|
+
if (run.syncMode === "all")
|
|
1003
|
+
return;
|
|
1004
|
+
if (counters.filesOutOfScope <= 0)
|
|
1005
|
+
return;
|
|
1006
|
+
const samplePaths = plan.items
|
|
1007
|
+
.filter((item) => item.action === "skip-out-of-scope")
|
|
1008
|
+
.slice(0, 10)
|
|
1009
|
+
.map((item) => item.remoteFile.key);
|
|
1010
|
+
run.emit({
|
|
1011
|
+
type: "scope-materialization-gap",
|
|
1012
|
+
count: counters.filesOutOfScope,
|
|
1013
|
+
samplePaths,
|
|
1014
|
+
syncMode: run.syncMode,
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
974
1017
|
/**
|
|
975
1018
|
* Decide whether a remote object present in the LIST is a GENUINE RE-CREATE
|
|
976
1019
|
* written AFTER a FILE_TOMBSTONE — in which case the tombstone is stale and the
|
|
@@ -1126,6 +1169,39 @@ class LocalSnapshotChangedError extends Error {
|
|
|
1126
1169
|
// an intentional delete.
|
|
1127
1170
|
const PULL_INTENTIONAL_DELETE_MIN_ABS = 10;
|
|
1128
1171
|
const PULL_INTENTIONAL_DELETE_RATIO = 0.1;
|
|
1172
|
+
/**
|
|
1173
|
+
* win32 only: true when an existing local symlink's TARGET exists but the
|
|
1174
|
+
* link cannot be traversed as that target's type — the file-flavor-link-at-
|
|
1175
|
+
* a-directory shape minted while the target was absent (or by an older
|
|
1176
|
+
* client). A dangling link is NOT broken-flavored (there is nothing to
|
|
1177
|
+
* repair until the target appears). POSIX links are flavorless: always
|
|
1178
|
+
* false off win32.
|
|
1179
|
+
*/
|
|
1180
|
+
export function win32SymlinkFlavorBroken(localPath, win32 = process.platform === "win32") {
|
|
1181
|
+
if (!win32)
|
|
1182
|
+
return false;
|
|
1183
|
+
let target;
|
|
1184
|
+
try {
|
|
1185
|
+
target = fs.readlinkSync(localPath);
|
|
1186
|
+
}
|
|
1187
|
+
catch {
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
const resolved = path.resolve(path.dirname(localPath), target);
|
|
1191
|
+
let targetIsDir;
|
|
1192
|
+
try {
|
|
1193
|
+
targetIsDir = fs.statSync(resolved).isDirectory();
|
|
1194
|
+
}
|
|
1195
|
+
catch {
|
|
1196
|
+
return false; // dangling target — nothing to repair yet
|
|
1197
|
+
}
|
|
1198
|
+
try {
|
|
1199
|
+
return fs.statSync(localPath).isDirectory() !== targetIsDir;
|
|
1200
|
+
}
|
|
1201
|
+
catch {
|
|
1202
|
+
return true; // target exists but the link cannot traverse: wrong flavor
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1129
1205
|
function computePullPlan(remoteFiles, journal, companyRoot, shouldSync, personalMode, teamSyncedSlugs,
|
|
1130
1206
|
// Coalesced, company-relative prefixes the pull is scoped to (US-005).
|
|
1131
1207
|
// `[""]` (the `all`-mode value) covers everything via `isCoveredByAny`, so
|
|
@@ -1152,6 +1228,45 @@ excludePrefixes = []) {
|
|
|
1152
1228
|
// Clean/current/non-divergent files missing locally — collected here, then
|
|
1153
1229
|
// resolved post-loop by the bulk-asymmetry decision (tombstone vs. restore).
|
|
1154
1230
|
const intentionalDeleteCandidates = [];
|
|
1231
|
+
// Junk-spelling guard (mixed-version amplifier): index every journaled key
|
|
1232
|
+
// by its canonical (percent-decode fixpoint) form so a remote twin minted
|
|
1233
|
+
// under a different spelling is recognized before it materializes. When
|
|
1234
|
+
// several journaled spellings already share one canonical form (a
|
|
1235
|
+
// pre-guard fork), the exactly-canonical spelling is preferred as the
|
|
1236
|
+
// representative; entries journaled under their own exact spelling are
|
|
1237
|
+
// never gated by this map (see the guard below), so pre-existing families
|
|
1238
|
+
// keep syncing untouched until the vault doctor (US-003) collapses them.
|
|
1239
|
+
const journaledKeyByCanonical = new Map();
|
|
1240
|
+
for (const journaledKey of Object.keys(journal.files)) {
|
|
1241
|
+
if (!journaledKey.startsWith(SKILLS_KEY_PREFIX))
|
|
1242
|
+
continue;
|
|
1243
|
+
const canonical = canonicalVaultKeySpelling(journaledKey);
|
|
1244
|
+
const existing = journaledKeyByCanonical.get(canonical);
|
|
1245
|
+
if (existing === undefined || journaledKey === canonical) {
|
|
1246
|
+
journaledKeyByCanonical.set(canonical, journaledKey);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
// Remote keys that ALSO appear as an ancestor of another remote key — i.e.
|
|
1250
|
+
// keys that carry child objects under `${key}/…` in this same LIST. The
|
|
1251
|
+
// vault stores a directory OVERLAY (a symlink into another tree, e.g. a
|
|
1252
|
+
// company skill mirrored from a shared repo) as a symlink RECORD at the
|
|
1253
|
+
// directory's own key AND stores the directory's contents as separate child
|
|
1254
|
+
// objects beneath it. A REAL directory, by contrast, only ever syncs as
|
|
1255
|
+
// those child objects — never as a single object AT its own key. So a single
|
|
1256
|
+
// remote object sitting at a key that also has children is unambiguously a
|
|
1257
|
+
// directory-overlay marker, not a regular file. Precomputed once (pure path
|
|
1258
|
+
// derivation, no I/O — the planner is synchronous) so the dir-vs-object
|
|
1259
|
+
// collision branch can tell an inert overlay marker apart from a genuine
|
|
1260
|
+
// file-vs-directory structural collision. feedback_d2082110.
|
|
1261
|
+
const remoteKeysWithChildren = new Set();
|
|
1262
|
+
for (const rf of remoteFiles) {
|
|
1263
|
+
const key = toPosixKey(rf.key);
|
|
1264
|
+
let slash = key.indexOf("/");
|
|
1265
|
+
while (slash !== -1) {
|
|
1266
|
+
remoteKeysWithChildren.add(key.slice(0, slash));
|
|
1267
|
+
slash = key.indexOf("/", slash + 1);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1155
1270
|
for (const remoteFile of remoteFiles) {
|
|
1156
1271
|
const invalidKey = classifyVaultKey(remoteFile.key, personalMode ? "personal" : "company");
|
|
1157
1272
|
if (invalidKey !== null) {
|
|
@@ -1273,6 +1388,38 @@ excludePrefixes = []) {
|
|
|
1273
1388
|
items.push({ action: "skip-excluded-policy", remoteFile, localPath });
|
|
1274
1389
|
continue;
|
|
1275
1390
|
}
|
|
1391
|
+
// Junk-spelling guard — SKILL-FARM KEYS ONLY. The amplifier is specific
|
|
1392
|
+
// to `.claude/skills/<ns>:<skill>` wrapper spellings; outside that prefix,
|
|
1393
|
+
// keys whose percent-decode fixpoints collide (`notes/a b.md` vs a genuine
|
|
1394
|
+
// `notes/a%20b.md`) are DISTINCT files that must both pull.
|
|
1395
|
+
// Within the farm: a remote key that is NOT journaled under its own
|
|
1396
|
+
// spelling but whose percent-decode fixpoint matches an already-journaled
|
|
1397
|
+
// key with a different spelling is a mixed-version twin (`%3A`/`%253A`
|
|
1398
|
+
// generations of a canonical `:` key). Materializing it would mint junk
|
|
1399
|
+
// local dirs and fork the journal into a second key family for the same
|
|
1400
|
+
// logical path — skip it (loudly, never fatally; see the
|
|
1401
|
+
// `skip-junk-key-spelling` event doc) and leave the journaled spelling
|
|
1402
|
+
// authoritative for this machine.
|
|
1403
|
+
if (journalEntry === undefined &&
|
|
1404
|
+
remoteFile.key.startsWith(SKILLS_KEY_PREFIX) &&
|
|
1405
|
+
// Gate NON-canonical remote spellings only. A remote key that already
|
|
1406
|
+
// IS the canonical spelling must always be downloadable — after the
|
|
1407
|
+
// doctor collapses the vault, a machine whose journal still holds only
|
|
1408
|
+
// the junk spelling would otherwise skip the canonical object forever
|
|
1409
|
+
// (and the delete-resync twin protection keeps that stale junk entry),
|
|
1410
|
+
// deadlocking it out of every future update.
|
|
1411
|
+
canonicalVaultKeySpelling(remoteFile.key) !== remoteFile.key) {
|
|
1412
|
+
const journaledTwin = journaledKeyByCanonical.get(canonicalVaultKeySpelling(remoteFile.key));
|
|
1413
|
+
if (journaledTwin !== undefined && journaledTwin !== remoteFile.key) {
|
|
1414
|
+
items.push({
|
|
1415
|
+
action: "skip-junk-key-spelling",
|
|
1416
|
+
remoteFile,
|
|
1417
|
+
localPath,
|
|
1418
|
+
journaledKey: journaledTwin,
|
|
1419
|
+
});
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1276
1423
|
// ── FILE_TOMBSTONE consult (delete-resync) ───────────────────────────────
|
|
1277
1424
|
// A remote object present in the LIST may be an intentionally-deleted key
|
|
1278
1425
|
// (a peer re-pushed it, or its delete-marker hasn't propagated to this
|
|
@@ -1386,6 +1533,28 @@ excludePrefixes = []) {
|
|
|
1386
1533
|
});
|
|
1387
1534
|
continue;
|
|
1388
1535
|
}
|
|
1536
|
+
// Directory-overlay marker vs. a locally-materialized directory:
|
|
1537
|
+
// the remote object at this key carries CHILD objects under it in the
|
|
1538
|
+
// same LIST (`remoteKeysWithChildren`), so it is a symlink/overlay
|
|
1539
|
+
// record for a directory — e.g. the DPP vault's `skills/cody-copywriter`
|
|
1540
|
+
// overlay whose 13 child objects are already materialized locally. The
|
|
1541
|
+
// marker can never be renamed over the real directory, and its children
|
|
1542
|
+
// carry the actual content and materialize via their own plan items, so
|
|
1543
|
+
// the marker is a benign, no-action skip. The generic "rm -rf the local
|
|
1544
|
+
// directory to pull" advice below would tell the operator to DELETE the
|
|
1545
|
+
// materialized (and possibly locally-authored) contents to install an
|
|
1546
|
+
// inert link, and it recurred on EVERY sync cycle. feedback_d2082110.
|
|
1547
|
+
if (remoteKeysWithChildren.has(toPosixKey(remoteFile.key))) {
|
|
1548
|
+
console.error(` Note: ${remoteFile.key} is a directory-overlay marker whose ` +
|
|
1549
|
+
`contents already exist locally as a directory; keeping the ` +
|
|
1550
|
+
`local directory and ignoring the marker (no action needed).`);
|
|
1551
|
+
items.push({
|
|
1552
|
+
action: "skip-overlay-marker-with-children",
|
|
1553
|
+
remoteFile,
|
|
1554
|
+
localPath,
|
|
1555
|
+
});
|
|
1556
|
+
continue;
|
|
1557
|
+
}
|
|
1389
1558
|
console.error(` Warning: ${remoteFile.key} exists locally as a directory; ` +
|
|
1390
1559
|
`cloud has a single object at this key. Skipping; manual ` +
|
|
1391
1560
|
`reconciliation required (rm -rf the local directory to pull).`);
|
|
@@ -1481,6 +1650,23 @@ excludePrefixes = []) {
|
|
|
1481
1650
|
continue;
|
|
1482
1651
|
}
|
|
1483
1652
|
if (journalEntry && !localChanged && !remoteChanged) {
|
|
1653
|
+
// Flavor-health repair (win32 skill farm): a link journaled while its
|
|
1654
|
+
// target was absent was minted file-flavor; once the target directory
|
|
1655
|
+
// exists the hashes still match (hash = sha256(target)) so this
|
|
1656
|
+
// branch would skip it forever, leaving the wrapper broken. Re-download
|
|
1657
|
+
// instead — the materializer's flavor-aware path recreates it 'dir'.
|
|
1658
|
+
if (isLocalSymlink &&
|
|
1659
|
+
remoteFile.key.startsWith(SKILLS_KEY_PREFIX) &&
|
|
1660
|
+
win32SymlinkFlavorBroken(localPath)) {
|
|
1661
|
+
items.push({
|
|
1662
|
+
action: "download",
|
|
1663
|
+
remoteFile,
|
|
1664
|
+
localPath,
|
|
1665
|
+
isNew: false,
|
|
1666
|
+
localSnapshot: plannedLocalSnapshot,
|
|
1667
|
+
});
|
|
1668
|
+
continue;
|
|
1669
|
+
}
|
|
1484
1670
|
items.push({ action: "skip-unchanged", remoteFile, localPath });
|
|
1485
1671
|
continue;
|
|
1486
1672
|
}
|
|
@@ -1669,8 +1855,20 @@ excludePrefixes = []) {
|
|
|
1669
1855
|
// as a local delete + journal removal. Symmetric to the push side's
|
|
1670
1856
|
// `propagateDeletes` plan in share.ts.
|
|
1671
1857
|
const remoteKeySet = new Set();
|
|
1672
|
-
|
|
1858
|
+
// Skill-farm twin protection (companion to the junk-spelling pull guard):
|
|
1859
|
+
// when the LIST holds only a junk spelling of a journaled canonical key,
|
|
1860
|
+
// the logical object is still remotely present — a raw has() would class
|
|
1861
|
+
// the canonical journal entry as remote-deleted, unlink the good local
|
|
1862
|
+
// wrapper, and drop the journal entry (after which the next pull would
|
|
1863
|
+
// materialize the junk spelling the guard just refused). Track canonical
|
|
1864
|
+
// forms of listed skill keys and consult them below.
|
|
1865
|
+
const remoteCanonicalSkillKeys = new Set();
|
|
1866
|
+
for (const rf of remoteFiles) {
|
|
1673
1867
|
remoteKeySet.add(rf.key);
|
|
1868
|
+
if (rf.key.startsWith(SKILLS_KEY_PREFIX)) {
|
|
1869
|
+
remoteCanonicalSkillKeys.add(canonicalVaultKeySpelling(rf.key));
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1674
1872
|
const tombstones = [];
|
|
1675
1873
|
for (const key of Object.keys(journal.files)) {
|
|
1676
1874
|
// Compare membership in POSIX space. A pre-5.47.2 Windows journal key can
|
|
@@ -1681,6 +1879,16 @@ excludePrefixes = []) {
|
|
|
1681
1879
|
const posixKey = toPosixKey(key);
|
|
1682
1880
|
if (remoteKeySet.has(posixKey))
|
|
1683
1881
|
continue;
|
|
1882
|
+
if (posixKey.startsWith(SKILLS_KEY_PREFIX) &&
|
|
1883
|
+
remoteCanonicalSkillKeys.has(canonicalVaultKeySpelling(posixKey)) &&
|
|
1884
|
+
// An EXACT FILE_TOMBSTONE for the journaled key is an authoritative
|
|
1885
|
+
// delete and wins over the twin protection — a lingering junk
|
|
1886
|
+
// spelling in the LIST must not keep a deliberately-deleted wrapper
|
|
1887
|
+
// alive forever. (The key is absent from the LIST here, so there is
|
|
1888
|
+
// no newer re-create to defer to.)
|
|
1889
|
+
fileTombstones.get(posixKey) === undefined) {
|
|
1890
|
+
continue;
|
|
1891
|
+
}
|
|
1684
1892
|
const localPath = resolveContainedVaultPath(companyRoot, key);
|
|
1685
1893
|
if (localPath === null)
|
|
1686
1894
|
continue;
|
|
@@ -1844,6 +2052,15 @@ function defaultConsoleLogger(event) {
|
|
|
1844
2052
|
console.log(` ... and ${event.count - event.samplePaths.length} more`);
|
|
1845
2053
|
}
|
|
1846
2054
|
}
|
|
2055
|
+
else if (event.type === "scope-materialization-gap") {
|
|
2056
|
+
console.warn(` ! ${event.count} item${event.count === 1 ? "" : "s"} in this vault did NOT sync to this device — your access level is "${event.syncMode}", so only shared paths materialize. To pull everything you have access to, set this membership's access level to "all":`);
|
|
2057
|
+
for (const p of event.samplePaths) {
|
|
2058
|
+
console.warn(` · ${p}`);
|
|
2059
|
+
}
|
|
2060
|
+
if (event.count > event.samplePaths.length) {
|
|
2061
|
+
console.warn(` ... and ${event.count - event.samplePaths.length} more`);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
1847
2064
|
else if (event.type === "ignore-excluded") {
|
|
1848
2065
|
console.warn(` ! ${event.count} path${event.count === 1 ? "" : "s"} were EXCLUDED from sync by an ignore rule and did NOT reach the vault (review in case this is unintended):`);
|
|
1849
2066
|
for (const p of event.samplePaths) {
|
|
@@ -1860,5 +2077,8 @@ function defaultConsoleLogger(event) {
|
|
|
1860
2077
|
else if (event.type === "skip-invalid-scoped-key") {
|
|
1861
2078
|
console.warn(` ! ${event.path} skipped — invalid vault key (${event.errorCode ?? "INVALID_KEY_COMPANIES_SCOPED"})`);
|
|
1862
2079
|
}
|
|
2080
|
+
else if (event.type === "skip-junk-key-spelling") {
|
|
2081
|
+
console.warn(` ! ${event.path} skipped — junk key spelling; this logical path is already synced as ${event.journaledKey} (mixed-version peer artifact; the vault doctor collapses the family)`);
|
|
2082
|
+
}
|
|
1863
2083
|
}
|
|
1864
2084
|
//# sourceMappingURL=sync.js.map
|