@indigoai-us/hq-cloud 6.14.9 → 6.14.11

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 (45) hide show
  1. package/dist/cli/share.d.ts.map +1 -1
  2. package/dist/cli/share.js +89 -14
  3. package/dist/cli/share.js.map +1 -1
  4. package/dist/cli/share.test.js +131 -3
  5. package/dist/cli/share.test.js.map +1 -1
  6. package/dist/cli/sync.d.ts +1 -1
  7. package/dist/cli/sync.d.ts.map +1 -1
  8. package/dist/cli/sync.js +138 -41
  9. package/dist/cli/sync.js.map +1 -1
  10. package/dist/cli/sync.test.js +271 -25
  11. package/dist/cli/sync.test.js.map +1 -1
  12. package/dist/journal.d.ts +7 -1
  13. package/dist/journal.d.ts.map +1 -1
  14. package/dist/journal.js +22 -1
  15. package/dist/journal.js.map +1 -1
  16. package/dist/personal-vault-exclusions.d.ts +20 -4
  17. package/dist/personal-vault-exclusions.d.ts.map +1 -1
  18. package/dist/personal-vault-exclusions.js +37 -3
  19. package/dist/personal-vault-exclusions.js.map +1 -1
  20. package/dist/personal-vault-exclusions.test.js +19 -0
  21. package/dist/personal-vault-exclusions.test.js.map +1 -1
  22. package/dist/personal-vault.d.ts +6 -0
  23. package/dist/personal-vault.d.ts.map +1 -1
  24. package/dist/personal-vault.js +12 -0
  25. package/dist/personal-vault.js.map +1 -1
  26. package/dist/s3.d.ts +23 -1
  27. package/dist/s3.d.ts.map +1 -1
  28. package/dist/s3.js +74 -3
  29. package/dist/s3.js.map +1 -1
  30. package/dist/s3.test.js +71 -1
  31. package/dist/s3.test.js.map +1 -1
  32. package/dist/types.d.ts +13 -0
  33. package/dist/types.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/cli/share.test.ts +176 -3
  36. package/src/cli/share.ts +114 -15
  37. package/src/cli/sync.test.ts +330 -24
  38. package/src/cli/sync.ts +184 -50
  39. package/src/journal.ts +28 -0
  40. package/src/personal-vault-exclusions.test.ts +23 -0
  41. package/src/personal-vault-exclusions.ts +39 -3
  42. package/src/personal-vault.ts +15 -0
  43. package/src/s3.test.ts +91 -0
  44. package/src/s3.ts +88 -2
  45. package/src/types.ts +13 -0
@@ -146,7 +146,7 @@ export type SyncProgressEvent = {
146
146
  path: string;
147
147
  journalEtag: string;
148
148
  remoteEtag: string;
149
- reason: "stale-etag" | "legacy-no-etag" | "bulk-asymmetry" | "divergent-local";
149
+ reason: "stale-etag" | "legacy-no-etag" | "bulk-asymmetry" | "divergent-local" | "missing-delete-intent" | "intent-changed";
150
150
  } | {
151
151
  /**
152
152
  * Emitted at most ONCE per `share()` call when the bulk-asymmetry
@@ -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,EAAe,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,wBAAwB,CAAC;AAwChC,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,uBAAuB,CAAC;AAU/B,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAe1E;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,iEAAiE;IACjE,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,aAAa,EAAE,MAAM,CAAC;CACvB,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC1B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,GACD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAChD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,UAAU,EAAE,kBAAkB,CAAC;CAChC,GACD;IACE;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CACvE,GACD;IACE;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,IAAI,EAAE,2BAA2B,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,YAAY,GAAG,gBAAgB,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;CAChF,GACD;IACE;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,IAAI,EAAE,+BAA+B,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,GACD;IACE;;;;;;;;;;;;;;;OAeG;IACH,IAAI,EAAE,8BAA8B,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B,GACD;IACE;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,GACD;IACE;;;;;;;;;OASG;IACH,IAAI,EAAE,iBAAiB,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB,GACD;IACE;;;;;;;;;;;OAWG;IACH,IAAI,EAAE,6BAA6B,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,GACD;IACE;;;;;;;;;;;;OAYG;IACH,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf,GACD;IACE;;;;;;;;;;;OAWG;IACH,IAAI,EAAE,yBAAyB,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEN,MAAM,WAAW,WAAW;IAC1B,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,2BAA2B;IAC3B,WAAW,EAAE,kBAAkB,CAAC;IAChC,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC7C;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC/B;;;;;;;;;;;;;;;OAeG;IACH,eAAe,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACrC;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;;;;;;;;;;;;;;OAiBG;IACH,iBAAiB,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;IAC7C;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACzC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB;;;;OAIG;IACH,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;;;;;;OASG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;;;;;OAOG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AA4DD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAM5C;AAiBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,kBAAkB,EAC/B,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,EACrE,eAAe,CAAC,EAAE,eAAe,GAAG,IAAI,GACvC,OAAO,CAAC,IAAI,CAAC,CA6Ef;AAoCD;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAOpE"}
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
@@ -14,6 +14,7 @@ import { localPathForVaultKey, vaultKeyForLocalPath } from "../local-path-codec.
14
14
  import { downloadFile, listRemoteFiles, headRemoteFile, primeObjectTransport, toPosixKey, classifyVaultKey, } from "../s3.js";
15
15
  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
16
  import { PERSONAL_VAULT_MANIFEST_KEY } from "../personal-vault.js";
17
+ import { isPersonalVaultExcluded } from "../personal-vault-exclusions.js";
17
18
  import { buildScopeShrinkPlan, applyScopeShrink, ScopeShrinkBlockedError, ScopeShrinkLargePruneError, } from "../scope-shrink.js";
18
19
  import { coalescePrefixes, isCoveredByAny, } from "../prefix-coalesce.js";
19
20
  import { createIgnoreFilter } from "../ignore.js";
@@ -461,12 +462,19 @@ async function executeConflictExecutor(run, plan, scopeRun, counters) {
461
462
  }
462
463
  function executeFileTombstoneDelete(run, item, counters) {
463
464
  const tombstoneKey = item.remoteFile.key;
464
- const tombstonePath = resolveContainedVaultPath(run.companyRoot, tombstoneKey);
465
- if (tombstonePath === null)
465
+ const tombstonePath = item.localPath;
466
+ if (!isDownloadWritePathStillContained(run.companyRoot, tombstoneKey, tombstonePath)) {
467
+ run.emit({
468
+ type: "error",
469
+ path: tombstoneKey,
470
+ message: "tombstone-suppress unlink skipped: local parent escaped the sync root",
471
+ });
466
472
  return;
473
+ }
467
474
  try {
468
475
  const lstat = fs.lstatSync(tombstonePath);
469
- if (tombstoneTargetDiverged(run.journal, tombstoneKey, tombstonePath, lstat)) {
476
+ if (!matchesLocalSnapshot(tombstonePath, item.localSnapshot)) {
477
+ reportLocalSnapshotConflict(run, counters, tombstoneKey);
470
478
  return;
471
479
  }
472
480
  if (lstat.isSymbolicLink() || lstat.isFile()) {
@@ -501,7 +509,13 @@ async function executeConflictItem(run, plan, scopeRun, counters, downloadItems,
501
509
  // the `.conflict-` mirror loop AND the journal false-stamp (remote etag over
502
510
  // divergent local content) that the keep path produced for these files.
503
511
  if (isCloudAuthoritative(vaultKeyForLocalPath(run.hqRoot, localPath))) {
504
- downloadItems.push({ action: "download", remoteFile, localPath, isNew: false });
512
+ downloadItems.push({
513
+ action: "download",
514
+ remoteFile,
515
+ localPath,
516
+ isNew: false,
517
+ localSnapshot: item.localSnapshot,
518
+ });
505
519
  run.emit({ type: "reconciled", path: remoteFile.key, direction: "pull" });
506
520
  return null;
507
521
  }
@@ -547,7 +561,7 @@ async function executeConflictItem(run, plan, scopeRun, counters, downloadItems,
547
561
  /* best-effort cleanup; a stray identical mirror is harmless */
548
562
  }
549
563
  }
550
- updateEntry(run.journal, remoteFile.key, item.localHash, item.localSize, "down", remoteFile.etag, item.localMtime.getTime());
564
+ updateEntry(run.journal, remoteFile.key, item.localHash, item.localSize, "down", remoteFile.etag, item.localMtime.getTime(), item.localSnapshot.kind === "symlink" ? "symlink" : "file");
551
565
  run.emit({ type: "reconciled", path: remoteFile.key, direction: "pull" });
552
566
  counters.filesSkipped++;
553
567
  return null;
@@ -620,7 +634,7 @@ async function executeConflictItem(run, plan, scopeRun, counters, downloadItems,
620
634
  }
621
635
  if (resolution === "keep" || resolution === "skip") {
622
636
  counters.filesSkipped++;
623
- updateEntry(run.journal, remoteFile.key, item.localHash, item.localSize, "down", remoteFile.etag, item.localMtime.getTime());
637
+ updateEntry(run.journal, remoteFile.key, item.localHash, item.localSize, "down", remoteFile.etag, item.localMtime.getTime(), item.localSnapshot.kind === "symlink" ? "symlink" : "file");
624
638
  // Journal-honesty: we recorded the remote etag so this conflict can't
625
639
  // re-fire (#137), but the KEPT local copy diverges from that remote — it
626
640
  // never matched it. Flag the entry so the currency-gated delete planner
@@ -637,6 +651,7 @@ async function executeConflictItem(run, plan, scopeRun, counters, downloadItems,
637
651
  remoteFile,
638
652
  localPath,
639
653
  isNew: false,
654
+ localSnapshot: item.localSnapshot,
640
655
  });
641
656
  return null;
642
657
  }
@@ -685,7 +700,13 @@ async function downloadOne(run, downloadItem, counters) {
685
700
  return;
686
701
  }
687
702
  try {
688
- const { metadata, contentHash, contentSize, modeWarnings } = await downloadFile(run.ctx, remoteFile.key, localPath);
703
+ const { metadata, contentHash, contentSize, modeWarnings } = await downloadFile(run.ctx, remoteFile.key, localPath, {
704
+ beforeReplace: () => {
705
+ if (!matchesLocalSnapshot(localPath, downloadItem.localSnapshot)) {
706
+ throw new LocalSnapshotChangedError(remoteFile.key);
707
+ }
708
+ },
709
+ });
689
710
  reportModeWarnings(run, modeWarnings);
690
711
  const author = metadata?.["created-by"] ?? null;
691
712
  const createdBySub = metadata?.["created-by-sub"];
@@ -695,7 +716,7 @@ async function downloadOne(run, downloadItem, counters) {
695
716
  ? hashSymlinkTarget(fs.readlinkSync(localPath))
696
717
  : (contentHash ?? hashFile(localPath));
697
718
  const size = isLocalSymlink ? 0 : (contentSize ?? fs.statSync(localPath).size);
698
- updateEntry(run.journal, remoteFile.key, hash, size, "down", remoteFile.etag, localLstat.mtimeMs, createdBySub);
719
+ updateEntry(run.journal, remoteFile.key, hash, size, "down", remoteFile.etag, localLstat.mtimeMs, createdBySub, isLocalSymlink ? "symlink" : "file");
699
720
  const priorEntry = getEntry(run.journal, remoteFile.key);
700
721
  const remoteJournalMessage = priorEntry?.message;
701
722
  run.emit({
@@ -710,7 +731,10 @@ async function downloadOne(run, downloadItem, counters) {
710
731
  counters.changedPaths.push(remoteFile.key);
711
732
  }
712
733
  catch (err) {
713
- if (isAccessDenied(err)) {
734
+ if (err instanceof LocalSnapshotChangedError) {
735
+ reportLocalSnapshotConflict(run, counters, remoteFile.key);
736
+ }
737
+ else if (isAccessDenied(err)) {
714
738
  counters.filesSkipped++;
715
739
  }
716
740
  else {
@@ -779,12 +803,12 @@ async function verifyPlannedJournalTombstones(run, plan) {
779
803
  // the push leg ever re-uploading it (tombstone-not-reupload).
780
804
  const autoVerified = [];
781
805
  const toHeadVerify = [];
782
- for (const key of plan.tombstones) {
783
- if (run.options.personalMode !== true && key.startsWith("companies/")) {
784
- autoVerified.push(key);
806
+ for (const tombstone of plan.tombstones) {
807
+ if (run.options.personalMode !== true && tombstone.key.startsWith("companies/")) {
808
+ autoVerified.push(tombstone);
785
809
  }
786
810
  else {
787
- toHeadVerify.push(key);
811
+ toHeadVerify.push(tombstone);
788
812
  }
789
813
  }
790
814
  plan.tombstones = toHeadVerify;
@@ -792,43 +816,50 @@ async function verifyPlannedJournalTombstones(run, plan) {
792
816
  plan.tombstones = autoVerified;
793
817
  return;
794
818
  }
795
- await primeObjectTransport(run.ctx, "get", plan.tombstones);
819
+ await primeObjectTransport(run.ctx, "get", plan.tombstones.map((t) => t.key));
796
820
  const HEAD_VERIFY_CONCURRENCY = 5;
797
821
  const verified = [];
798
822
  for (let i = 0; i < plan.tombstones.length; i += HEAD_VERIFY_CONCURRENCY) {
799
823
  const batch = plan.tombstones.slice(i, i + HEAD_VERIFY_CONCURRENCY);
800
- const results = await Promise.all(batch.map(async (key) => {
824
+ const results = await Promise.all(batch.map(async (tombstone) => {
801
825
  try {
802
- const head = await headRemoteFile(run.ctx, key);
803
- return head === null ? key : null;
826
+ const head = await headRemoteFile(run.ctx, tombstone.key);
827
+ return head === null ? tombstone : null;
804
828
  }
805
829
  catch (err) {
806
830
  if (isAccessDenied(err))
807
831
  return null;
808
832
  run.emit({
809
833
  type: "error",
810
- path: key,
834
+ path: tombstone.key,
811
835
  message: `tombstone HEAD verify failed (deferring): ${err instanceof Error ? err.message : String(err)}`,
812
836
  });
813
837
  return null;
814
838
  }
815
839
  }));
816
- for (const k of results) {
817
- if (k !== null)
818
- verified.push(k);
840
+ for (const tombstone of results) {
841
+ if (tombstone !== null)
842
+ verified.push(tombstone);
819
843
  }
820
844
  }
821
845
  plan.tombstones = [...autoVerified, ...verified];
822
846
  }
823
847
  function executeJournalTombstoneDeletes(run, plan, counters) {
824
- for (const key of plan.tombstones) {
825
- const localPath = resolveContainedVaultPath(run.companyRoot, key);
826
- if (localPath === null)
848
+ for (const tombstone of plan.tombstones) {
849
+ const { key, localPath, localSnapshot } = tombstone;
850
+ if (!isDownloadWritePathStillContained(run.companyRoot, key, localPath)) {
851
+ run.emit({
852
+ type: "error",
853
+ path: key,
854
+ message: "tombstone unlink skipped: local parent escaped the sync root",
855
+ });
827
856
  continue;
857
+ }
828
858
  let removedSomething = false;
829
859
  try {
830
860
  const lstat = fs.lstatSync(localPath);
831
- if (tombstoneTargetDiverged(run.journal, key, localPath, lstat)) {
861
+ if (!matchesLocalSnapshot(localPath, localSnapshot)) {
862
+ reportLocalSnapshotConflict(run, counters, key);
832
863
  continue;
833
864
  }
834
865
  if (lstat.isSymbolicLink() || lstat.isFile()) {
@@ -1007,23 +1038,52 @@ function isDownloadWritePathStillContained(root, key, localPath) {
1007
1038
  const resolved = resolveContainedVaultPath(root, key);
1008
1039
  return resolved !== null && path.resolve(resolved) === path.resolve(localPath);
1009
1040
  }
1010
- function tombstoneTargetDiverged(journal, key, localPath, lstat) {
1011
- const journalEntry = journal.files[key];
1012
- if (!journalEntry?.hash) {
1013
- return lstat.isSymbolicLink() || lstat.isFile();
1014
- }
1041
+ function snapshotLocalState(localPath) {
1015
1042
  try {
1043
+ const lstat = fs.lstatSync(localPath);
1016
1044
  if (lstat.isSymbolicLink()) {
1017
- return hashSymlinkTarget(fs.readlinkSync(localPath)) !== journalEntry.hash;
1045
+ return { kind: "symlink", hash: hashSymlinkTarget(fs.readlinkSync(localPath)) };
1018
1046
  }
1019
1047
  if (lstat.isFile()) {
1020
- return hashFile(localPath) !== journalEntry.hash;
1048
+ return { kind: "file", hash: hashFile(localPath) };
1021
1049
  }
1050
+ if (lstat.isDirectory())
1051
+ return { kind: "directory" };
1052
+ return { kind: "other" };
1022
1053
  }
1023
- catch {
1024
- return true;
1054
+ catch (err) {
1055
+ const code = err && typeof err === "object" && "code" in err
1056
+ ? err.code
1057
+ : undefined;
1058
+ return code === "ENOENT" ? { kind: "absent" } : null;
1059
+ }
1060
+ }
1061
+ function matchesLocalSnapshot(localPath, expected) {
1062
+ const actual = snapshotLocalState(localPath);
1063
+ if (actual === null || actual.kind !== expected.kind)
1064
+ return false;
1065
+ return ((actual.kind !== "file" && actual.kind !== "symlink") ||
1066
+ actual.hash === expected.hash);
1067
+ }
1068
+ function hasCurrentLocalDeleteIntent(entry) {
1069
+ const intent = entry?.localDeleteIntent;
1070
+ return !!(intent &&
1071
+ intent.version === 1 &&
1072
+ entry.remoteEtag &&
1073
+ entry.kind &&
1074
+ intent.remoteEtag === entry.remoteEtag &&
1075
+ intent.localHash === entry.hash &&
1076
+ intent.localKind === entry.kind);
1077
+ }
1078
+ function reportLocalSnapshotConflict(run, counters, key) {
1079
+ counters.conflicts++;
1080
+ counters.conflictPaths.push(key);
1081
+ run.emit({ type: "conflict", path: key, direction: "pull", resolution: "skip" });
1082
+ }
1083
+ class LocalSnapshotChangedError extends Error {
1084
+ constructor(key) {
1085
+ super(`local state changed while downloading ${key}; replanning required`);
1025
1086
  }
1026
- return false;
1027
1087
  }
1028
1088
  /**
1029
1089
  * Stage-1 planning pass: classify every remote file into download / skip /
@@ -1122,6 +1182,15 @@ excludePrefixes = []) {
1122
1182
  items.push({ action: "skip-excluded-policy", remoteFile, localPath });
1123
1183
  continue;
1124
1184
  }
1185
+ // Personal-vault policy must be symmetric across both transfer legs.
1186
+ // The push walker refuses derived/machine-local/sensitive paths, but old
1187
+ // objects can remain in the bucket from clients that predate an exclusion.
1188
+ // Refuse those objects here too so a pull cannot rematerialize them (or,
1189
+ // for generated skill wrappers, fight reindex over a directory link).
1190
+ if (personalMode && isPersonalVaultExcluded(remoteFile.key)) {
1191
+ items.push({ action: "skip-excluded-policy", remoteFile, localPath });
1192
+ continue;
1193
+ }
1125
1194
  if (personalMode &&
1126
1195
  remoteFile.key.startsWith("companies/") &&
1127
1196
  // EXEMPTION: companies/manifest.yaml is the routing source-of-truth
@@ -1213,6 +1282,7 @@ excludePrefixes = []) {
1213
1282
  // path below — never to delete a file that is present locally.
1214
1283
  const localTombstoneSuppresses = journalEntry !== undefined &&
1215
1284
  isTombstone(journalEntry) &&
1285
+ hasCurrentLocalDeleteIntent(journalEntry) &&
1216
1286
  journalEntry.removedAt !== undefined &&
1217
1287
  !isRemoteRecreateAfterTombstone(remoteFile, {
1218
1288
  deletedAt: journalEntry.removedAt,
@@ -1230,6 +1300,7 @@ excludePrefixes = []) {
1230
1300
  // dangling link on every run.
1231
1301
  // 3. A regular file: indistinguishable from current behaviour.
1232
1302
  let localLstat = null;
1303
+ let plannedLocalSnapshot = { kind: "absent" };
1233
1304
  let localPathBlockedByFileAncestor = false;
1234
1305
  try {
1235
1306
  localLstat = fs.lstatSync(localPath);
@@ -1327,6 +1398,10 @@ excludePrefixes = []) {
1327
1398
  : hashFile(localPath);
1328
1399
  }
1329
1400
  const localChanged = !!journalEntry && journalEntry.hash !== localHash;
1401
+ plannedLocalSnapshot = {
1402
+ kind: isLocalSymlink ? "symlink" : "file",
1403
+ hash: localHash,
1404
+ };
1330
1405
  const remoteChanged = !!journalEntry && hasRemoteChanged(remoteFile, journalEntry);
1331
1406
  // ── Authoritative delete reaches a machine that still holds the file ──
1332
1407
  // A FILE_TOMBSTONE marks this key deleted and the remote object is not a
@@ -1347,10 +1422,16 @@ excludePrefixes = []) {
1347
1422
  localHash,
1348
1423
  localMtime: localLstat.mtime,
1349
1424
  localSize: isLocalSymlink ? 0 : localLstat.size,
1425
+ localSnapshot: plannedLocalSnapshot,
1350
1426
  });
1351
1427
  }
1352
1428
  else {
1353
- items.push({ action: "tombstone-delete", remoteFile, localPath });
1429
+ items.push({
1430
+ action: "tombstone-delete",
1431
+ remoteFile,
1432
+ localPath,
1433
+ localSnapshot: plannedLocalSnapshot,
1434
+ });
1354
1435
  }
1355
1436
  continue;
1356
1437
  }
@@ -1375,6 +1456,7 @@ excludePrefixes = []) {
1375
1456
  // every sync.
1376
1457
  localMtime: localLstat.mtime,
1377
1458
  localSize: isLocalSymlink ? 0 : localLstat.size,
1459
+ localSnapshot: plannedLocalSnapshot,
1378
1460
  });
1379
1461
  continue;
1380
1462
  }
@@ -1432,6 +1514,7 @@ excludePrefixes = []) {
1432
1514
  localHash,
1433
1515
  localMtime: localLstat.mtime,
1434
1516
  localSize: localLstat.size,
1517
+ localSnapshot: plannedLocalSnapshot,
1435
1518
  });
1436
1519
  continue;
1437
1520
  }
@@ -1460,7 +1543,12 @@ excludePrefixes = []) {
1460
1543
  if (journalEntry?.removedReason === "local-delete") {
1461
1544
  continue;
1462
1545
  }
1463
- items.push({ action: "tombstone-delete", remoteFile, localPath });
1546
+ items.push({
1547
+ action: "tombstone-delete",
1548
+ remoteFile,
1549
+ localPath,
1550
+ localSnapshot: plannedLocalSnapshot,
1551
+ });
1464
1552
  continue;
1465
1553
  }
1466
1554
  // Intentional-delete producer: a clean, CURRENT (etag matches), non-divergent
@@ -1471,13 +1559,18 @@ excludePrefixes = []) {
1471
1559
  if (!localExists &&
1472
1560
  journalEntry !== undefined &&
1473
1561
  !isTombstone(journalEntry) &&
1474
- !journalEntry.localDiverges &&
1475
- journalEntry.remoteEtag !== undefined &&
1562
+ hasCurrentLocalDeleteIntent(journalEntry) &&
1476
1563
  normalizeEtag(remoteFile.etag) === journalEntry.remoteEtag) {
1477
1564
  intentionalDeleteCandidates.push({ remoteFile, localPath });
1478
1565
  continue;
1479
1566
  }
1480
- items.push({ action: "download", remoteFile, localPath, isNew: !localExists });
1567
+ items.push({
1568
+ action: "download",
1569
+ remoteFile,
1570
+ localPath,
1571
+ isNew: !localExists,
1572
+ localSnapshot: plannedLocalSnapshot,
1573
+ });
1481
1574
  }
1482
1575
  // ── Intentional-delete bulk-asymmetry decision (pull-leg producer) ─────────
1483
1576
  // Below the breaker → stamp a persisting `local-delete` tombstone (the
@@ -1497,6 +1590,7 @@ excludePrefixes = []) {
1497
1590
  remoteFile: cand.remoteFile,
1498
1591
  localPath: cand.localPath,
1499
1592
  isNew: true,
1593
+ localSnapshot: { kind: "absent" },
1500
1594
  });
1501
1595
  }
1502
1596
  else {
@@ -1648,7 +1742,10 @@ excludePrefixes = []) {
1648
1742
  if (code !== "ENOENT")
1649
1743
  continue;
1650
1744
  }
1651
- tombstones.push(key);
1745
+ const localSnapshot = snapshotLocalState(localPath);
1746
+ if (localSnapshot === null)
1747
+ continue;
1748
+ tombstones.push({ key, localPath, localSnapshot });
1652
1749
  }
1653
1750
  return {
1654
1751
  items,