@indigoai-us/hq-cloud 6.15.21 → 6.15.22

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.
@@ -4,7 +4,7 @@
4
4
  * Broadcasts local file(s) to the company's S3 vault bucket.
5
5
  * Refuses to overwrite a newer remote version without prompting.
6
6
  */
7
- import type { EntityContext, VaultServiceConfig } from "../types.js";
7
+ import type { EntityContext, VaultServiceConfig, JournalEntry } from "../types.js";
8
8
  import type { UploadAuthor } from "../s3.js";
9
9
  import type { ConflictStrategy } from "./conflict.js";
10
10
  import type { SyncProgressEvent } from "./sync.js";
@@ -163,6 +163,7 @@ export declare const _testing: {
163
163
  resolveNamedPath: typeof resolveNamedPath;
164
164
  isWithinLexicalOrReal: typeof isWithinLexicalOrReal;
165
165
  defaultConsoleLogger: typeof defaultConsoleLogger;
166
+ statMatchesJournal: typeof statMatchesJournal;
166
167
  };
167
168
  /**
168
169
  * Stage-1 classification for a single local file in a push run. Pre-HEAD —
@@ -181,6 +182,7 @@ type PushPlanItem = {
181
182
  relativePath: string;
182
183
  localHash: string;
183
184
  size: number;
185
+ statAtHash?: StatSnapshot;
184
186
  } | {
185
187
  action: "upload";
186
188
  kind: "symlink";
@@ -199,9 +201,42 @@ type PushPlanItem = {
199
201
  relativePath: string;
200
202
  restamp?: {
201
203
  mtimeMs: number;
204
+ ctimeMs: number;
202
205
  size: number;
203
206
  };
204
207
  };
208
+ /**
209
+ * Pure Stage-1 pass for push: walk the candidate file list, hash each one,
210
+ * apply the size-limit and skip-unchanged gates, and return a classified
211
+ * plan plus aggregate counts. No S3 calls, no journal writes, no event
212
+ * emission.
213
+ *
214
+ * The conflict count is intentionally absent from the returned `PushPlan` —
215
+ * detecting a push conflict requires a remote HEAD that we defer to Stage 2.
216
+ * Consumers that want a conflict count get it from the `complete` event.
217
+ */
218
+ /** The three stat fields the skip gate compares. */
219
+ interface StatSnapshot {
220
+ size: number;
221
+ mtimeMs: number;
222
+ ctimeMs: number;
223
+ }
224
+ /**
225
+ * True when `absolutePath` provably has not been written since its journal
226
+ * entry was stamped, judged from stat alone.
227
+ *
228
+ * Fails closed. Anything unusual about the entry — no stat recorded, a
229
+ * tombstone, a pending local delete, a pull-kept divergence, a symlink record,
230
+ * or a local object that is no longer a plain file — returns false and sends
231
+ * the caller down the hashing path, which is the pre-fast-path behaviour.
232
+ *
233
+ * The three fields are compared exactly, on purpose. `mtimeMs` from `lstat`
234
+ * carries sub-millisecond precision that `utimes` cannot reproduce, so a
235
+ * restored timestamp differs from the original anyway — but that is an
236
+ * accident of precision, not a guarantee, and nothing here leans on it.
237
+ * `ctimeMs` is the field doing the real work.
238
+ */
239
+ declare function statMatchesJournal(absolutePath: string, entry: JournalEntry | undefined): boolean;
205
240
  export interface ShareOptions {
206
241
  /** Path(s) to share (files or directories) */
207
242
  paths: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../../src/cli/share.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"share.d.ts","sourceRoot":"","sources":["../../src/cli/share.ts"],"names":[],"mappings":"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"}
package/dist/cli/share.js CHANGED
@@ -236,17 +236,109 @@ export const _testing = {
236
236
  resolveNamedPath,
237
237
  isWithinLexicalOrReal,
238
238
  defaultConsoleLogger,
239
+ statMatchesJournal,
239
240
  };
241
+ /** Snapshot of `absolutePath`, or null when it is missing or not a plain file. */
242
+ function statSnapshot(absolutePath) {
243
+ try {
244
+ const lstat = fs.lstatSync(absolutePath);
245
+ if (!lstat.isFile())
246
+ return null;
247
+ return { size: lstat.size, mtimeMs: lstat.mtimeMs, ctimeMs: lstat.ctimeMs };
248
+ }
249
+ catch {
250
+ return null;
251
+ }
252
+ }
253
+ function sameStatSnapshot(a, b) {
254
+ return (a !== null &&
255
+ b !== null &&
256
+ a.size === b.size &&
257
+ a.mtimeMs === b.mtimeMs &&
258
+ a.ctimeMs === b.ctimeMs);
259
+ }
260
+ /**
261
+ * The `ctimeMs` it is safe to stamp alongside a hash, or undefined.
262
+ *
263
+ * `hashedStat` is the stat the hash was computed against. If the file has moved
264
+ * since, the hash in hand describes bytes that are no longer on disk, and
265
+ * pairing it with the current timestamps would let every later pass skip the
266
+ * new body on stat alone. Returning undefined leaves the entry without a ctime,
267
+ * which the skip gate refuses — so the next pass hashes it.
268
+ */
269
+ function ctimeToStamp(hashedStat, lstat) {
270
+ if (!hashedStat)
271
+ return undefined;
272
+ const current = {
273
+ size: lstat.size,
274
+ mtimeMs: lstat.mtimeMs,
275
+ ctimeMs: lstat.ctimeMs,
276
+ };
277
+ return sameStatSnapshot(hashedStat, current) ? lstat.ctimeMs : undefined;
278
+ }
279
+ /**
280
+ * True when a timestamp pair is too coarse to be evidence of anything.
281
+ *
282
+ * FAT/exFAT, some FUSE mounts and cached network filesystems round timestamps
283
+ * to whole seconds (FAT's mtime granularity is two), and may synthesize ctime
284
+ * from mtime. On such a volume a same-size rewrite inside one tick leaves both
285
+ * fields equal to the journal's, and the skip gate would strand the edit
286
+ * forever. Every filesystem this gate can safely run on — APFS, ext4, NTFS —
287
+ * records sub-second precision, so a pair sitting exactly on the second is
288
+ * treated as untrustworthy and sent back to hashing.
289
+ *
290
+ * The cost of the false positive is one hash: on a real filesystem a file whose
291
+ * mtime AND ctime both land exactly on a second boundary is rare, and it is
292
+ * simply hashed as it was before the fast path existed.
293
+ */
294
+ function timestampsTooCoarseToTrust(mtimeMs, ctimeMs) {
295
+ return mtimeMs % 1000 === 0 && ctimeMs % 1000 === 0;
296
+ }
240
297
  /**
241
- * Pure Stage-1 pass for push: walk the candidate file list, hash each one,
242
- * apply the size-limit and skip-unchanged gates, and return a classified
243
- * plan plus aggregate counts. No S3 calls, no journal writes, no event
244
- * emission.
298
+ * True when `absolutePath` provably has not been written since its journal
299
+ * entry was stamped, judged from stat alone.
300
+ *
301
+ * Fails closed. Anything unusual about the entry — no stat recorded, a
302
+ * tombstone, a pending local delete, a pull-kept divergence, a symlink record,
303
+ * or a local object that is no longer a plain file — returns false and sends
304
+ * the caller down the hashing path, which is the pre-fast-path behaviour.
245
305
  *
246
- * The conflict count is intentionally absent from the returned `PushPlan`
247
- * detecting a push conflict requires a remote HEAD that we defer to Stage 2.
248
- * Consumers that want a conflict count get it from the `complete` event.
306
+ * The three fields are compared exactly, on purpose. `mtimeMs` from `lstat`
307
+ * carries sub-millisecond precision that `utimes` cannot reproduce, so a
308
+ * restored timestamp differs from the original anyway but that is an
309
+ * accident of precision, not a guarantee, and nothing here leans on it.
310
+ * `ctimeMs` is the field doing the real work.
249
311
  */
312
+ function statMatchesJournal(absolutePath, entry) {
313
+ if (!entry)
314
+ return false;
315
+ if (!entry.hash)
316
+ return false;
317
+ if (entry.kind === "symlink")
318
+ return false;
319
+ if (entry.localDiverges)
320
+ return false;
321
+ if (entry.removedAt !== undefined)
322
+ return false;
323
+ if (entry.localDeleteIntent !== undefined)
324
+ return false;
325
+ if (entry.mtimeMs === undefined || entry.ctimeMs === undefined)
326
+ return false;
327
+ if (timestampsTooCoarseToTrust(entry.mtimeMs, entry.ctimeMs))
328
+ return false;
329
+ let lstat;
330
+ try {
331
+ lstat = fs.lstatSync(absolutePath);
332
+ }
333
+ catch {
334
+ return false;
335
+ }
336
+ if (!lstat.isFile())
337
+ return false;
338
+ return (lstat.size === entry.size &&
339
+ lstat.mtimeMs === entry.mtimeMs &&
340
+ lstat.ctimeMs === entry.ctimeMs);
341
+ }
250
342
  function computePushPlan(filesToShare, journal, skipUnchanged, onHeartbeat) {
251
343
  const items = [];
252
344
  for (const entry of filesToShare) {
@@ -287,10 +379,37 @@ function computePushPlan(filesToShare, journal, skipUnchanged, onHeartbeat) {
287
379
  items.push({ action: "skip-size-limit", absolutePath, relativePath });
288
380
  continue;
289
381
  }
290
- // Stat metadata is not content identity: tools can rewrite a file with
291
- // the same byte length and restore its mtime. Hash every regular file so
292
- // such an edit cannot be skipped indefinitely.
382
+ // Stat fast path. Reading and hashing every regular file every pass costs
383
+ // one full read of the vault hundreds of thousands of files on a real
384
+ // install to answer a question stat can usually answer for free.
385
+ //
386
+ // The gate is size + mtime + ctime, and ctime is what makes it safe. mtime
387
+ // is forgeable: `cp -p`, `touch -r` and `rsync --times` put an old mtime
388
+ // back on new bytes, so a size+mtime check would skip such an edit forever
389
+ // — the failure this code previously hashed everything to avoid. ctime is
390
+ // the inode-change stamp and userspace cannot set it; the very utimes call
391
+ // that restores mtime moves ctime to now. A file that clears all three has
392
+ // not been written since we hashed it.
393
+ //
394
+ // Deliberately NOT skipped here:
395
+ // - entries with no recorded mtime/ctime (written before stat tracking,
396
+ // or by a downloader that did not stamp) — hashed once, then stamped
397
+ // by the restamp below so the next pass is free;
398
+ // - `localDiverges` entries — see the hash gate below for why equality
399
+ // proves nothing about them.
400
+ if (skipUnchanged && statMatchesJournal(absolutePath, journal.files[relativePath])) {
401
+ items.push({ action: "skip-unchanged", absolutePath, relativePath });
402
+ continue;
403
+ }
404
+ // Snapshot stat either side of the read. If the two differ the file was
405
+ // rewritten while we hashed it, so `localHash` may describe bytes that are
406
+ // already gone — and nothing downstream may stamp a timestamp against it.
407
+ const statBeforeHash = statSnapshot(absolutePath);
293
408
  const localHash = hashFile(absolutePath);
409
+ const statAfterHash = statSnapshot(absolutePath);
410
+ const statAtHash = sameStatSnapshot(statBeforeHash, statAfterHash)
411
+ ? statAfterHash ?? undefined
412
+ : undefined;
294
413
  if (skipUnchanged) {
295
414
  const existing = journal.files[relativePath];
296
415
  // A `localDiverges` entry records the hash of the KEPT LOCAL body, so
@@ -308,15 +427,11 @@ function computePushPlan(filesToShare, journal, skipUnchanged, onHeartbeat) {
308
427
  // journal write on an already-current entry). Stat failure → no
309
428
  // restamp; the skip still stands.
310
429
  let restamp;
311
- try {
312
- const lstat = fs.lstatSync(absolutePath);
313
- if (lstat.isFile() &&
314
- (existing.mtimeMs !== lstat.mtimeMs || existing.size !== lstat.size)) {
315
- restamp = { mtimeMs: lstat.mtimeMs, size: lstat.size };
316
- }
317
- }
318
- catch {
319
- /* best-effort; a stat error just forgoes the fast-path refresh */
430
+ if (statAtHash &&
431
+ (existing.mtimeMs !== statAtHash.mtimeMs ||
432
+ existing.ctimeMs !== statAtHash.ctimeMs ||
433
+ existing.size !== statAtHash.size)) {
434
+ restamp = statAtHash;
320
435
  }
321
436
  items.push({ action: "skip-unchanged", absolutePath, relativePath, restamp });
322
437
  continue;
@@ -330,6 +445,7 @@ function computePushPlan(filesToShare, journal, skipUnchanged, onHeartbeat) {
330
445
  relativePath,
331
446
  localHash,
332
447
  size,
448
+ ...(statAtHash ? { statAtHash } : {}),
333
449
  });
334
450
  }
335
451
  let filesToUpload = 0;
@@ -863,6 +979,7 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
863
979
  const existing = run.journal.files[item.relativePath];
864
980
  if (existing && existing.hash) {
865
981
  existing.mtimeMs = item.restamp.mtimeMs;
982
+ existing.ctimeMs = item.restamp.ctimeMs;
866
983
  existing.size = item.restamp.size;
867
984
  }
868
985
  }
@@ -979,6 +1096,7 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
979
1096
  updateEntry(run.journal, relativePath, localHash, lstat.size, "up", absolutePath, {
980
1097
  remoteEtag: remoteMeta.etag,
981
1098
  mtimeMs: lstat.mtimeMs,
1099
+ ctimeMs: ctimeToStamp(item.kind === "file" ? item.statAtHash : undefined, lstat),
982
1100
  kind: item.kind,
983
1101
  });
984
1102
  // Same checkpoint contract as an actual upload. After repeated
@@ -1071,10 +1189,11 @@ async function executeUploads(run, pushPlan, counters, conflictPaths) {
1071
1189
  const lstat = fs.lstatSync(absolutePath);
1072
1190
  const size = isSymlinkUpload ? 0 : lstat.size;
1073
1191
  const mtimeMs = lstat.mtimeMs;
1192
+ const ctimeMs = ctimeToStamp(item.kind === "file" ? item.statAtHash : undefined, lstat);
1074
1193
  const { etag } = isSymlinkUpload
1075
1194
  ? await uploadSymlink(run.ctx, item.target, relativePath, run.options.author, pc)
1076
1195
  : await uploadFile(run.ctx, absolutePath, relativePath, run.options.author, pc);
1077
- updateEntry(run.journal, relativePath, localHash, size, "up", absolutePath, { remoteEtag: etag, mtimeMs, kind: item.kind });
1196
+ updateEntry(run.journal, relativePath, localHash, size, "up", absolutePath, { remoteEtag: etag, mtimeMs, ctimeMs, kind: item.kind });
1078
1197
  if (run.message) {
1079
1198
  run.journal.files[relativePath] = {
1080
1199
  ...run.journal.files[relativePath],