@indigoai-us/hq-cloud 6.11.10 → 6.11.12

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 (173) hide show
  1. package/dist/bin/sync-runner.d.ts +2 -0
  2. package/dist/bin/sync-runner.d.ts.map +1 -1
  3. package/dist/bin/sync-runner.js +231 -52
  4. package/dist/bin/sync-runner.js.map +1 -1
  5. package/dist/bin/sync-runner.test.js +330 -11
  6. package/dist/bin/sync-runner.test.js.map +1 -1
  7. package/dist/cli/reindex.d.ts.map +1 -1
  8. package/dist/cli/reindex.js +16 -1
  9. package/dist/cli/reindex.js.map +1 -1
  10. package/dist/cli/reindex.test.js +39 -1
  11. package/dist/cli/reindex.test.js.map +1 -1
  12. package/dist/cli/rescue-classify-ordering.test.js +58 -0
  13. package/dist/cli/rescue-classify-ordering.test.js.map +1 -1
  14. package/dist/cli/rescue-core.js +229 -15
  15. package/dist/cli/rescue-core.js.map +1 -1
  16. package/dist/cli/rescue-exec-bit-preserve.test.d.ts +2 -0
  17. package/dist/cli/rescue-exec-bit-preserve.test.d.ts.map +1 -0
  18. package/dist/cli/rescue-exec-bit-preserve.test.js +169 -0
  19. package/dist/cli/rescue-exec-bit-preserve.test.js.map +1 -0
  20. package/dist/cli/share.d.ts +2 -1
  21. package/dist/cli/share.d.ts.map +1 -1
  22. package/dist/cli/share.js +100 -32
  23. package/dist/cli/share.js.map +1 -1
  24. package/dist/cli/share.test.js +30 -0
  25. package/dist/cli/share.test.js.map +1 -1
  26. package/dist/cli/sync.d.ts +28 -1
  27. package/dist/cli/sync.d.ts.map +1 -1
  28. package/dist/cli/sync.js +188 -59
  29. package/dist/cli/sync.js.map +1 -1
  30. package/dist/cli/sync.test.js +487 -1
  31. package/dist/cli/sync.test.js.map +1 -1
  32. package/dist/cognito-auth.d.ts.map +1 -1
  33. package/dist/cognito-auth.js +55 -10
  34. package/dist/cognito-auth.js.map +1 -1
  35. package/dist/cognito-auth.test.js +61 -0
  36. package/dist/cognito-auth.test.js.map +1 -1
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/journal.d.ts.map +1 -1
  42. package/dist/journal.js +93 -6
  43. package/dist/journal.js.map +1 -1
  44. package/dist/journal.test.js +59 -0
  45. package/dist/journal.test.js.map +1 -1
  46. package/dist/machine-auth.test.js +60 -2
  47. package/dist/machine-auth.test.js.map +1 -1
  48. package/dist/object-io.d.ts +37 -1
  49. package/dist/object-io.d.ts.map +1 -1
  50. package/dist/object-io.js +148 -29
  51. package/dist/object-io.js.map +1 -1
  52. package/dist/object-io.test.js +121 -0
  53. package/dist/object-io.test.js.map +1 -1
  54. package/dist/operation-lock.d.ts +8 -8
  55. package/dist/operation-lock.d.ts.map +1 -1
  56. package/dist/operation-lock.js +99 -32
  57. package/dist/operation-lock.js.map +1 -1
  58. package/dist/operation-lock.test.js +51 -4
  59. package/dist/operation-lock.test.js.map +1 -1
  60. package/dist/personal-vault.d.ts +8 -0
  61. package/dist/personal-vault.d.ts.map +1 -1
  62. package/dist/personal-vault.js +17 -3
  63. package/dist/personal-vault.js.map +1 -1
  64. package/dist/personal-vault.test.js +34 -0
  65. package/dist/personal-vault.test.js.map +1 -1
  66. package/dist/prefix-coalesce.d.ts +20 -9
  67. package/dist/prefix-coalesce.d.ts.map +1 -1
  68. package/dist/prefix-coalesce.js +124 -28
  69. package/dist/prefix-coalesce.js.map +1 -1
  70. package/dist/prefix-coalesce.test.js +57 -2
  71. package/dist/prefix-coalesce.test.js.map +1 -1
  72. package/dist/remote-pull.d.ts +6 -1
  73. package/dist/remote-pull.d.ts.map +1 -1
  74. package/dist/remote-pull.js +62 -13
  75. package/dist/remote-pull.js.map +1 -1
  76. package/dist/remote-pull.test.js +189 -0
  77. package/dist/remote-pull.test.js.map +1 -1
  78. package/dist/s3.d.ts +2 -0
  79. package/dist/s3.d.ts.map +1 -1
  80. package/dist/s3.js +197 -116
  81. package/dist/s3.js.map +1 -1
  82. package/dist/s3.test.js +109 -0
  83. package/dist/s3.test.js.map +1 -1
  84. package/dist/scope-shrink.d.ts +3 -2
  85. package/dist/scope-shrink.d.ts.map +1 -1
  86. package/dist/scope-shrink.js +1 -1
  87. package/dist/scope-shrink.js.map +1 -1
  88. package/dist/skill-telemetry.d.ts +1 -1
  89. package/dist/skill-telemetry.d.ts.map +1 -1
  90. package/dist/skill-telemetry.js +69 -9
  91. package/dist/skill-telemetry.js.map +1 -1
  92. package/dist/skill-telemetry.test.js +86 -0
  93. package/dist/skill-telemetry.test.js.map +1 -1
  94. package/dist/sync/event-sync.d.ts +6 -0
  95. package/dist/sync/event-sync.d.ts.map +1 -1
  96. package/dist/sync/event-sync.js +34 -1
  97. package/dist/sync/event-sync.js.map +1 -1
  98. package/dist/sync/event-sync.test.js +73 -0
  99. package/dist/sync/event-sync.test.js.map +1 -1
  100. package/dist/sync/metrics.d.ts +17 -1
  101. package/dist/sync/metrics.d.ts.map +1 -1
  102. package/dist/sync/metrics.js +32 -1
  103. package/dist/sync/metrics.js.map +1 -1
  104. package/dist/sync/metrics.test.js +74 -1
  105. package/dist/sync/metrics.test.js.map +1 -1
  106. package/dist/sync/pull-scope.d.ts.map +1 -1
  107. package/dist/sync/pull-scope.js +15 -7
  108. package/dist/sync/pull-scope.js.map +1 -1
  109. package/dist/sync/push-receiver.d.ts +6 -5
  110. package/dist/sync/push-receiver.d.ts.map +1 -1
  111. package/dist/sync/push-receiver.js +13 -15
  112. package/dist/sync/push-receiver.js.map +1 -1
  113. package/dist/sync/push-receiver.test.js +36 -1
  114. package/dist/sync/push-receiver.test.js.map +1 -1
  115. package/dist/telemetry.d.ts +1 -1
  116. package/dist/telemetry.d.ts.map +1 -1
  117. package/dist/telemetry.js +59 -6
  118. package/dist/telemetry.js.map +1 -1
  119. package/dist/telemetry.test.js +74 -0
  120. package/dist/telemetry.test.js.map +1 -1
  121. package/dist/types.d.ts +8 -0
  122. package/dist/types.d.ts.map +1 -1
  123. package/dist/watcher.d.ts +36 -0
  124. package/dist/watcher.d.ts.map +1 -1
  125. package/dist/watcher.js +152 -30
  126. package/dist/watcher.js.map +1 -1
  127. package/dist/watcher.test.js +103 -0
  128. package/dist/watcher.test.js.map +1 -1
  129. package/package.json +1 -1
  130. package/src/bin/sync-runner.test.ts +396 -11
  131. package/src/bin/sync-runner.ts +254 -52
  132. package/src/cli/reindex.test.ts +47 -1
  133. package/src/cli/reindex.ts +17 -1
  134. package/src/cli/rescue-classify-ordering.test.ts +61 -0
  135. package/src/cli/rescue-core.ts +261 -15
  136. package/src/cli/rescue-exec-bit-preserve.test.ts +187 -0
  137. package/src/cli/share.test.ts +38 -0
  138. package/src/cli/share.ts +103 -34
  139. package/src/cli/sync.test.ts +594 -1
  140. package/src/cli/sync.ts +229 -65
  141. package/src/cognito-auth.test.ts +77 -0
  142. package/src/cognito-auth.ts +73 -11
  143. package/src/index.ts +8 -0
  144. package/src/journal.test.ts +72 -0
  145. package/src/journal.ts +95 -8
  146. package/src/machine-auth.test.ts +64 -2
  147. package/src/object-io.test.ts +142 -0
  148. package/src/object-io.ts +182 -30
  149. package/src/operation-lock.test.ts +63 -4
  150. package/src/operation-lock.ts +99 -31
  151. package/src/personal-vault.test.ts +42 -0
  152. package/src/personal-vault.ts +18 -3
  153. package/src/prefix-coalesce.test.ts +71 -1
  154. package/src/prefix-coalesce.ts +155 -30
  155. package/src/remote-pull.test.ts +205 -0
  156. package/src/remote-pull.ts +77 -14
  157. package/src/s3.test.ts +126 -0
  158. package/src/s3.ts +237 -122
  159. package/src/scope-shrink.ts +6 -3
  160. package/src/skill-telemetry.test.ts +109 -0
  161. package/src/skill-telemetry.ts +82 -14
  162. package/src/sync/event-sync.test.ts +75 -0
  163. package/src/sync/event-sync.ts +54 -1
  164. package/src/sync/metrics.test.ts +81 -0
  165. package/src/sync/metrics.ts +59 -4
  166. package/src/sync/pull-scope.ts +23 -7
  167. package/src/sync/push-receiver.test.ts +38 -1
  168. package/src/sync/push-receiver.ts +15 -18
  169. package/src/telemetry.test.ts +85 -0
  170. package/src/telemetry.ts +69 -6
  171. package/src/types.ts +8 -0
  172. package/src/watcher.test.ts +117 -0
  173. package/src/watcher.ts +209 -33
package/src/cli/share.ts CHANGED
@@ -46,7 +46,11 @@ import {
46
46
  fetchCompanyTombstones,
47
47
  type CompanyTombstone,
48
48
  } from "./tombstones.js";
49
- import { isCoveredByAny, isDirInScope } from "../prefix-coalesce.js";
49
+ import {
50
+ isCoveredByAny,
51
+ isDirInScope,
52
+ type ScopePrefixInput,
53
+ } from "../prefix-coalesce.js";
50
54
  import {
51
55
  buildConflictId,
52
56
  buildConflictPath,
@@ -637,7 +641,7 @@ export interface ShareOptions {
637
641
  * filter — full access. An empty array means "no granted prefixes" → every
638
642
  * path is out of scope (mirrors the pull side's `isCoveredByAny([])`).
639
643
  */
640
- prefixSet?: string[];
644
+ prefixSet?: ScopePrefixInput[];
641
645
  /**
642
646
  * Pre-fetched FILE_TOMBSTONE map (POSIX key → tombstone) for the push-side
643
647
  * delete-resync consult. When omitted, share() fetches it itself via
@@ -778,7 +782,7 @@ function isPreconditionFailed(err: unknown): boolean {
778
782
  function wrapFilterWithScope(
779
783
  underlying: (absPath: string, isDir?: boolean) => boolean,
780
784
  syncRoot: string,
781
- prefixSet: readonly string[],
785
+ prefixSet: readonly ScopePrefixInput[],
782
786
  onScopeExcluded: (rel: string) => void,
783
787
  ): (absPath: string, isDir?: boolean) => boolean {
784
788
  return (absPath: string, isDir?: boolean) => {
@@ -1279,13 +1283,15 @@ export async function share(options: ShareOptions): Promise<ShareResult> {
1279
1283
 
1280
1284
  let isFreshCollision = false;
1281
1285
  let multipartConverged = false;
1282
- if (!journalEntry && item.kind === "file") {
1286
+ if (!journalEntry && item.kind === "symlink") {
1287
+ // A HEAD on an existing object does not expose the symlink target.
1288
+ // On a first sync, treat the existing remote as a fresh collision
1289
+ // instead of replacing it with the local link record.
1290
+ isFreshCollision = true;
1291
+ } else if (!journalEntry && item.kind === "file") {
1283
1292
  // Single-part S3 PUT etag is MD5 of the body. Multipart uploads
1284
- // produce \`<md5>-<partCount>\`. Symlink records (\`kind: "symlink"\`)
1285
- // skip the check entirely the wire body shape (\`hq-symlink:\`
1286
- // prefix + target) isn't a pure byte mirror and would mis-
1287
- // classify; symlink overwrites are rare and an audit pass after
1288
- // the broader bug-cleanup wave can extend coverage if needed.
1293
+ // produce `<md5>-<partCount>`. Symlink records cannot be classified
1294
+ // from HEAD alone, so the branch above fails closed.
1289
1295
  const remoteEtagNormalized = normalizeEtag(remoteMeta.etag);
1290
1296
  const isMultipart = /-\d+$/.test(remoteEtagNormalized);
1291
1297
  if (!isMultipart) {
@@ -1392,17 +1398,25 @@ export async function share(options: ShareOptions): Promise<ShareResult> {
1392
1398
  machineId,
1393
1399
  );
1394
1400
  const conflictAbs = path.join(hqRoot, conflictRelative);
1395
- await downloadFile(ctx, relativePath, conflictAbs);
1396
- appendConflictEntry(hqRoot, {
1397
- id: buildConflictId(originalRelative, detectedAt),
1398
- originalPath: originalRelative,
1399
- conflictPath: conflictRelative,
1400
- detectedAt,
1401
- side: "push",
1402
- machineId,
1403
- localHash,
1404
- remoteHash: normalizeEtag(remoteMeta.etag),
1405
- });
1401
+ if (!isMaterializationPathStillContained(syncRoot, conflictAbs)) {
1402
+ emit({
1403
+ type: "error",
1404
+ path: relativePath,
1405
+ message: "conflict mirror skipped: local parent escaped the sync root",
1406
+ });
1407
+ } else {
1408
+ await downloadFile(ctx, relativePath, conflictAbs);
1409
+ appendConflictEntry(hqRoot, {
1410
+ id: buildConflictId(originalRelative, detectedAt),
1411
+ originalPath: originalRelative,
1412
+ conflictPath: conflictRelative,
1413
+ detectedAt,
1414
+ side: "push",
1415
+ machineId,
1416
+ localHash,
1417
+ remoteHash: normalizeEtag(remoteMeta.etag),
1418
+ });
1419
+ }
1406
1420
  } catch (mirrorErr) {
1407
1421
  emit({
1408
1422
  type: "error",
@@ -1539,20 +1553,28 @@ export async function share(options: ShareOptions): Promise<ShareResult> {
1539
1553
  machineId,
1540
1554
  );
1541
1555
  const conflictAbs = path.join(hqRoot, conflictRelative);
1542
- await downloadFile(ctx, relativePath, conflictAbs);
1543
- appendConflictEntry(hqRoot, {
1544
- id: buildConflictId(originalRelative, detectedAt),
1545
- originalPath: originalRelative,
1546
- conflictPath: conflictRelative,
1547
- detectedAt,
1548
- side: "push",
1549
- machineId,
1550
- localHash,
1551
- // remoteMeta (if any) predates the racing write that fired the
1552
- // fence — record what we knew ("" when the key was believed
1553
- // absent); the mirror file carries the authoritative remote bytes.
1554
- remoteHash: remoteMeta ? normalizeEtag(remoteMeta.etag) : "",
1555
- });
1556
+ if (!isMaterializationPathStillContained(syncRoot, conflictAbs)) {
1557
+ emit({
1558
+ type: "error",
1559
+ path: relativePath,
1560
+ message: "conflict mirror skipped: local parent escaped the sync root",
1561
+ });
1562
+ } else {
1563
+ await downloadFile(ctx, relativePath, conflictAbs);
1564
+ appendConflictEntry(hqRoot, {
1565
+ id: buildConflictId(originalRelative, detectedAt),
1566
+ originalPath: originalRelative,
1567
+ conflictPath: conflictRelative,
1568
+ detectedAt,
1569
+ side: "push",
1570
+ machineId,
1571
+ localHash,
1572
+ // remoteMeta (if any) predates the racing write that fired the
1573
+ // fence — record what we knew ("" when the key was believed
1574
+ // absent); the mirror file carries the authoritative remote bytes.
1575
+ remoteHash: remoteMeta ? normalizeEtag(remoteMeta.etag) : "",
1576
+ });
1577
+ }
1556
1578
  } catch (mirrorErr) {
1557
1579
  emit({
1558
1580
  type: "error",
@@ -2082,6 +2104,11 @@ function isWithin(parent: string, child: string): boolean {
2082
2104
  return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
2083
2105
  }
2084
2106
 
2107
+ function isPathWithin(parent: string, child: string): boolean {
2108
+ const rel = path.relative(parent, child);
2109
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
2110
+ }
2111
+
2085
2112
  function realpathSafe(p: string): string {
2086
2113
  try {
2087
2114
  return fs.realpathSync.native(p);
@@ -2090,6 +2117,48 @@ function realpathSafe(p: string): string {
2090
2117
  }
2091
2118
  }
2092
2119
 
2120
+ function deepestExistingAncestor(start: string): string | null {
2121
+ let current = start;
2122
+ for (;;) {
2123
+ try {
2124
+ fs.lstatSync(current);
2125
+ return current;
2126
+ } catch (err: unknown) {
2127
+ const code =
2128
+ err && typeof err === "object" && "code" in err
2129
+ ? (err as { code?: string }).code
2130
+ : undefined;
2131
+ if (code !== "ENOENT" && code !== "ENOTDIR") return null;
2132
+ }
2133
+
2134
+ const parent = path.dirname(current);
2135
+ if (parent === current) return null;
2136
+ current = parent;
2137
+ }
2138
+ }
2139
+
2140
+ function isMaterializationPathStillContained(root: string, localPath: string): boolean {
2141
+ const resolvedRoot = path.resolve(root);
2142
+ const resolvedLocal = path.resolve(localPath);
2143
+ if (!isPathWithin(resolvedRoot, resolvedLocal)) return false;
2144
+
2145
+ let realRoot: string;
2146
+ try {
2147
+ realRoot = fs.realpathSync.native(resolvedRoot);
2148
+ } catch {
2149
+ return false;
2150
+ }
2151
+
2152
+ const existingAncestor = deepestExistingAncestor(path.dirname(resolvedLocal));
2153
+ if (existingAncestor === null) return false;
2154
+ try {
2155
+ const realAncestor = fs.realpathSync.native(existingAncestor);
2156
+ return isPathWithin(realRoot, realAncestor);
2157
+ } catch {
2158
+ return false;
2159
+ }
2160
+ }
2161
+
2093
2162
  /**
2094
2163
  * Containment check tailored for symlinks. Canonicalizes the link's
2095
2164
  * PARENT DIR (which is a real dir, not the link), then compares the