@indigoai-us/hq-cloud 5.35.0 → 5.37.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/dist/s3.js CHANGED
@@ -150,6 +150,60 @@ export const SYMLINK_BODY_PREFIX = "hq-symlink:";
150
150
  * leaves the umask default in place, matching pre-fix behavior.
151
151
  */
152
152
  export const FILE_MODE_META_KEY = "hq-mode";
153
+ /**
154
+ * S3 user-metadata key carrying the source-side file modification time
155
+ * (mtimeMs) as an integer-millisecond epoch string ("1700000000000"). On
156
+ * download, downloadFile parses this with a strict-numeric regex BEFORE
157
+ * parseInt, then applies it via `fs.utimesSync(localPath, mtimeDate,
158
+ * mtimeDate)` after the byte write.
159
+ *
160
+ * 5.37.0 symmetric to the 5.34.0 Bug #5 mode preservation: a file's
161
+ * modification time should follow it across machines instead of resetting
162
+ * to "the time of sync." Without this metadata, every receiver's mtime is
163
+ * wall-clock-now at write-time — making "newer than" comparisons,
164
+ * mtime-keyed caches, and reproducible builds break across sync.
165
+ *
166
+ * Symlinks: skipped at upload time (symlink mtime is OS-controlled and
167
+ * `lstat` on a symlink already returns the symlink's own times — but we
168
+ * don't stamp them because the symlink record wire body is `hq-symlink:`
169
+ * + target string, not real file content, so its mtime isn't user-
170
+ * meaningful) and skipped on download (`fs.utimesSync` follows symlinks
171
+ * and would mutate the target's mtime instead; `lutimesSync` is not in
172
+ * stable Node).
173
+ *
174
+ * Composition with 5.36.0 lstat fast-path: the journal stamp is captured
175
+ * AFTER utimesSync runs (the share/sync call sites lstat AFTER
176
+ * downloadFile returns), so the journal's mtimeMs matches the post-utimes
177
+ * lstat. The next sync's fast-path correctly skips re-hashing.
178
+ *
179
+ * Clock skew: a peer with a wrong clock pushes file with mtimeMs=<wrong>;
180
+ * receivers apply <wrong>. This is the same trade git's "file from the
181
+ * future" warning makes — silent in our case, deliberately. Clock skew
182
+ * is the user's problem, not the sync engine's.
183
+ *
184
+ * Back-compat: legacy uploads (pre-5.37.0) have no `hq-mtime` header —
185
+ * the receiver leaves the on-disk mtime at write-time, matching pre-
186
+ * 5.37.0 behavior. Forward-compat: pre-5.37.0 pullers ignore `hq-mtime`
187
+ * and keep their current "mtime = write-time" behavior. Both work; only
188
+ * the receiver upgrade unlocks the feature.
189
+ */
190
+ export const FILE_MTIME_META_KEY = "hq-mtime";
191
+ /**
192
+ * S3 user-metadata key carrying the source-side file birthtime (birthtimeMs)
193
+ * as an integer-millisecond epoch string. Stamped on upload ONLY when
194
+ * `birthtimeMs > 0 && birthtimeMs !== mtimeMs` — many filesystems (Linux
195
+ * ext4 historically, tmpfs, some FUSE mounts) return 0 (unsupported) or
196
+ * the same value as mtime (no separate creation time tracking). The
197
+ * filter keeps the metadata header free of noise on those platforms.
198
+ *
199
+ * Pull: NO-OP for now. Node has no API to set birthtime on POSIX as of
200
+ * v24 (no `lbirthtime`, no `birthtimeSync`). The push side stamps it
201
+ * anyway so a future receiver upgrade — once Node lands the API — can
202
+ * apply it without a server-side data migration.
203
+ *
204
+ * Symlinks: skipped on both sides for the same reasons as `hq-mtime`.
205
+ */
206
+ export const FILE_BTIME_META_KEY = "hq-btime";
153
207
  /**
154
208
  * Encode/decode the symlink wire body. Kept as exported helpers so the
155
209
  * format is centrally defined and tests can probe both sides without
@@ -165,15 +219,60 @@ export async function uploadFile(ctx, localPath, key, author) {
165
219
  // FILE_MODE_META_KEY doc. Best-effort: lstat failure (raced rm, EPERM)
166
220
  // falls through to "no mode header" and the receiver keeps its umask
167
221
  // default — same as the legacy back-compat path.
222
+ //
223
+ // 5.37.0: same lstat call also yields mtimeMs + birthtimeMs for the
224
+ // hq-mtime / hq-btime metadata headers. Single lstat keeps the syscall
225
+ // budget identical to 5.36.0; the additional metadata fields are pure
226
+ // in-memory work on the lstat result. Symlinks skip ALL THREE stamps
227
+ // — symlink mode is OS-controlled, symlink mtime isn't user-meaningful
228
+ // because the wire body is `hq-symlink:` + target (not real file
229
+ // content), and fs.utimesSync follows links so applying it on receive
230
+ // would mutate the target's mtime instead of the link's.
168
231
  let modeOctal;
232
+ let mtimeMsStamp;
233
+ let btimeMsStamp;
169
234
  try {
170
235
  const lstat = fs.lstatSync(localPath);
171
236
  if (!lstat.isSymbolicLink()) {
172
237
  modeOctal = (lstat.mode & 0o777).toString(8);
238
+ // Math.floor truncates the sub-millisecond fractional component
239
+ // some filesystems report (APFS returns full ms+fraction; ext4
240
+ // is integer-ms). String(int) on the read side matches the
241
+ // strict-numeric regex `^-?[0-9]{1,16}$` — optional leading `-`,
242
+ // no leading zeros, no decimals, no exponents.
243
+ //
244
+ // Codex PR #27 P2: accept the full finite range, including 0
245
+ // (Unix epoch) and negatives (pre-epoch / reproducible-build
246
+ // clamping). Earlier `> 0` filter silently dropped legitimate
247
+ // timestamps and broke the round-trip guarantee for that subset.
248
+ const mtimeFloor = Math.floor(lstat.mtimeMs);
249
+ if (Number.isFinite(lstat.mtimeMs)) {
250
+ mtimeMsStamp = String(mtimeFloor);
251
+ }
252
+ // birthtimeMs filter: only stamp when the filesystem actually
253
+ // tracks a separate creation time. ext4 historically returns 0
254
+ // (unsupported) or equals mtimeMs (no distinct tracking); tmpfs
255
+ // and some FUSE mounts behave similarly. Filtering at the source
256
+ // keeps the metadata header free of noise — the receiver can
257
+ // assume hq-btime, if present, carries real signal.
258
+ //
259
+ // Compare the floored values (not raw lstat.birthtimeMs vs
260
+ // lstat.mtimeMs) because APFS exposes sub-millisecond fractions —
261
+ // two timestamps representing the "same moment" for sync purposes
262
+ // can differ by < 1 ms and pass a strict `!==` check while serializing
263
+ // to the same integer-ms string. Comparing floor-to-floor matches
264
+ // what we actually emit on the wire.
265
+ const btimeFloor = Math.floor(lstat.birthtimeMs);
266
+ if (Number.isFinite(lstat.birthtimeMs) &&
267
+ btimeFloor > 0 &&
268
+ btimeFloor !== mtimeFloor) {
269
+ btimeMsStamp = String(btimeFloor);
270
+ }
173
271
  }
174
272
  }
175
273
  catch {
176
- // Leave modeOctal undefined; receiver applies its umask default.
274
+ // Leave stamps undefined; receiver applies its umask default and
275
+ // leaves mtime at write-time (the legacy back-compat path).
177
276
  }
178
277
  // Preserve the original `created-at` across re-uploads when the object
179
278
  // already exists with author metadata — same convention the hq-console
@@ -196,6 +295,8 @@ export async function uploadFile(ctx, localPath, key, author) {
196
295
  const Metadata = {
197
296
  ...(author ? buildAuthorMetadata(author, createdAt) : {}),
198
297
  ...(modeOctal ? { [FILE_MODE_META_KEY]: modeOctal } : {}),
298
+ ...(mtimeMsStamp ? { [FILE_MTIME_META_KEY]: mtimeMsStamp } : {}),
299
+ ...(btimeMsStamp ? { [FILE_BTIME_META_KEY]: btimeMsStamp } : {}),
199
300
  };
200
301
  const response = await client.send(new PutObjectCommand({
201
302
  Bucket: ctx.bucketName,
@@ -394,6 +495,54 @@ export async function downloadFile(ctx, key, localPath) {
394
495
  }
395
496
  }
396
497
  }
498
+ // 5.37.0 — apply source-side mtime after the byte write (and after the
499
+ // chmod above; ordering between chmod and utimes doesn't matter, but
500
+ // both must run AFTER writeFileSync because writeFileSync resets mtime
501
+ // to wall-clock-now). See FILE_MTIME_META_KEY for the metadata contract.
502
+ //
503
+ // Strict-numeric regex BEFORE parseInt — same Codex P2 lesson as hq-mode.
504
+ // `^-?[0-9]{1,16}$` rejects partial-prefix garbage ("175junk" → 175),
505
+ // empty, double-signed, decimals, whitespace, and oversized strings. A
506
+ // single optional leading `-` is allowed so pre-epoch / reproducible-build
507
+ // timestamps round-trip (Codex PR #27 P2 — `mtimeMs === 0` and negative
508
+ // epoch values are legitimate). 16 digits comfortably covers any plausible
509
+ // epoch-ms value (year ~5138 is 16 digits; we'll cross that bridge later).
510
+ //
511
+ // fs.utimesSync FOLLOWS symlinks — that's fine here because we're on
512
+ // the regular-file branch (the symlink branch above already returned
513
+ // before we reach this code).
514
+ //
515
+ // Composition with the 5.36.0 lstat fast-path: the journal stamp at the
516
+ // share/sync call sites runs AFTER downloadFile returns, so the lstat
517
+ // it captures sees the post-utimes mtime. Verified in cli/sync.ts
518
+ // (downloadFile → lstatSync → updateEntry) and cli/share.ts (pull path
519
+ // similarly lstats after downloadFile). If a future caller stamps the
520
+ // journal BEFORE downloadFile completes, the fast-path will stale and
521
+ // re-hash every sync forever — keep the call-site invariant intact.
522
+ const mtimeRaw = response.Metadata?.[FILE_MTIME_META_KEY];
523
+ if (typeof mtimeRaw === "string" && /^-?[0-9]{1,16}$/.test(mtimeRaw)) {
524
+ const mtimeMs = parseInt(mtimeRaw, 10);
525
+ if (Number.isFinite(mtimeMs)) {
526
+ try {
527
+ // utimesSync accepts seconds OR Date; use Date(ms) for precision.
528
+ // atime = mtime is fine — many filesystems are mounted noatime
529
+ // and distinguishing "access" vs "modification" time doesn't
530
+ // matter for sync semantics. Setting both keeps the on-disk
531
+ // state deterministic across receivers.
532
+ const mtimeDate = new Date(mtimeMs);
533
+ fs.utimesSync(localPath, mtimeDate, mtimeDate);
534
+ }
535
+ catch {
536
+ // EPERM / read-only FS / file just unlinked → non-fatal. The
537
+ // file is materialized at write-time mtime; the source-of-truth
538
+ // can re-sync next pass.
539
+ }
540
+ }
541
+ }
542
+ // TODO: stamp hq-btime once Node lands lbirthtime (or birthtimeSync).
543
+ // The push side already emits hq-btime when the source FS tracks a
544
+ // distinct creation time, so a future receiver upgrade picks it up
545
+ // automatically without a server-side data migration.
397
546
  return { metadata: response.Metadata };
398
547
  }
399
548
  export async function listRemoteFiles(ctx, prefix) {
package/dist/s3.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"s3.js","sourceRoot":"","sources":["../src/s3.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"s3.js","sourceRoot":"","sources":["../src/s3.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAG5B;;;;GAIG;AACH,SAAS,WAAW,CAAC,GAAkB;IACrC,OAAO,IAAI,QAAQ,CAAC;QAClB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,WAAW,EAAE;YACX,WAAW,EAAE,GAAG,CAAC,WAAW,CAAC,WAAW;YACxC,eAAe,EAAE,GAAG,CAAC,WAAW,CAAC,eAAe;YAChD,YAAY,EAAE,GAAG,CAAC,WAAW,CAAC,YAAY;SAC3C;KACF,CAAC,CAAC;AACL,CAAC;AAcD;;;;;;;GAOG;AACH,SAAS,mBAAmB,CAC1B,MAAoB,EACpB,SAAiB;IAEjB,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,IAAI,CAAC,gBAAgB,CAAC,GAAG,GAAG,CAAC;IAC/B,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,IAAI,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC;IAC7B,CAAC;IACD,IAAI,SAAS,IAAI,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,mBAAmB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,MAAc;IACvD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACzD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAa;IACtD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC/D,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAE,CAAC;YAC/D,OAAO,OAAO,CAAC;QACjB,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;AAE9C;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;AAE9C;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,MAAM,CAAC,IAAI,CAAC,mBAAmB,GAAG,MAAM,EAAE,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,GAAkB,EAClB,SAAiB,EACjB,GAAW,EACX,MAAqB;IAErB,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;IAExC,wEAAwE;IACxE,uEAAuE;IACvE,qEAAqE;IACrE,iDAAiD;IACjD,EAAE;IACF,oEAAoE;IACpE,uEAAuE;IACvE,sEAAsE;IACtE,qEAAqE;IACrE,uEAAuE;IACvE,iEAAiE;IACjE,sEAAsE;IACtE,yDAAyD;IACzD,IAAI,SAA6B,CAAC;IAClC,IAAI,YAAgC,CAAC;IACrC,IAAI,YAAgC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;YAC5B,SAAS,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC7C,gEAAgE;YAChE,+DAA+D;YAC/D,2DAA2D;YAC3D,iEAAiE;YACjE,+CAA+C;YAC/C,EAAE;YACF,6DAA6D;YAC7D,6DAA6D;YAC7D,8DAA8D;YAC9D,iEAAiE;YACjE,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7C,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBACnC,YAAY,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;YACpC,CAAC;YACD,8DAA8D;YAC9D,+DAA+D;YAC/D,gEAAgE;YAChE,iEAAiE;YACjE,6DAA6D;YAC7D,oDAAoD;YACpD,EAAE;YACF,2DAA2D;YAC3D,kEAAkE;YAClE,kEAAkE;YAClE,uEAAuE;YACvE,kEAAkE;YAClE,qCAAqC;YACrC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;YACjD,IACE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC;gBAClC,UAAU,GAAG,CAAC;gBACd,UAAU,KAAK,UAAU,EACzB,CAAC;gBACD,YAAY,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iEAAiE;QACjE,4DAA4D;IAC9D,CAAC;IAED,uEAAuE;IACvE,uEAAuE;IACvE,0EAA0E;IAC1E,yEAAyE;IACzE,iDAAiD;IACjD,IAAI,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACzC,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAC5B,IAAI,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAC5D,CAAC;YACF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,CAAC;YAC/C,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxD,SAAS,GAAG,QAAQ,CAAC;YACvB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,yDAAyD;QAC3D,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAA2B;QACvC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,kBAAkB,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,mBAAmB,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,mBAAmB,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACjE,CAAC;IAEF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAChC,IAAI,gBAAgB,CAAC;QACnB,MAAM,EAAE,GAAG,CAAC,UAAU;QACtB,GAAG,EAAE,GAAG;QACR,IAAI,EAAE,IAAI;QACV,WAAW,EAAE,WAAW,CAAC,GAAG,CAAC;QAC7B,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1D,CAAC,CACH,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AACvC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAkB,EAClB,MAAc,EACd,GAAW,EACX,MAAqB;IAErB,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAEhC,qEAAqE;IACrE,qEAAqE;IACrE,0BAA0B;IAC1B,IAAI,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACzC,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAC5B,IAAI,iBAAiB,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAC5D,CAAC;YACF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,YAAY,CAAC,CAAC;YAC/C,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxD,SAAS,GAAG,QAAQ,CAAC;YACvB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,yDAAyD;QAC3D,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAA2B;QACvC,+DAA+D;QAC/D,0DAA0D;QAC1D,+DAA+D;QAC/D,8BAA8B;QAC9B,CAAC,uBAAuB,CAAC,EAAE,yBAAyB;QACpD,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,mBAAmB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1D,CAAC;IAEF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAChC,IAAI,gBAAgB,CAAC;QACnB,MAAM,EAAE,GAAG,CAAC,UAAU;QACtB,GAAG,EAAE,GAAG;QACR,kEAAkE;QAClE,+DAA+D;QAC/D,8DAA8D;QAC9D,iEAAiE;QACjE,yDAAyD;QACzD,kDAAkD;QAClD,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC;QAC/B,WAAW,EAAE,0BAA0B;QACvC,QAAQ;KACT,CAAC,CACH,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;AACvC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,GAAkB,EAClB,GAAW,EACX,SAAiB;IAEjB,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAEhC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAChC,IAAI,gBAAgB,CAAC;QACnB,MAAM,EAAE,GAAG,CAAC,UAAU;QACtB,GAAG,EAAE,GAAG;KACT,CAAC,CACH,CAAC;IAEF,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACpC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,mEAAmE;IACnE,kEAAkE;IAClE,+DAA+D;IAC/D,2DAA2D;IAC3D,EAAE;IACF,6DAA6D;IAC7D,2DAA2D;IAC3D,mEAAmE;IACnE,MAAM,aAAa,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC,uBAAuB,CAAC,CAAC;IACnE,MAAM,eAAe,GACnB,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC;IAChE,IAAI,eAAe,EAAE,CAAC;QACpB,gEAAgE;QAChE,yDAAyD;QACzD,6DAA6D;QAC7D,8DAA8D;QAC9D,8DAA8D;QAC9D,4DAA4D;QAC5D,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAiC,CAAC;QAC1D,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAClC,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAE3D,IAAI,aAAqB,CAAC;QAC1B,IAAI,UAAU,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAC/C,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC/D,CAAC;aAAM,CAAC;YACN,4DAA4D;YAC5D,2DAA2D;YAC3D,2DAA2D;YAC3D,2DAA2D;YAC3D,sDAAsD;YACtD,aAAa,GAAG,0BAA0B,CAAC,aAAa,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CACb,sBAAsB,GAAG,yBAAyB,UAAU,CAAC,MAAM,mBAAmB,aAAa,GAAG,CACvG,CAAC;QACJ,CAAC;QAED,oEAAoE;QACpE,mEAAmE;QACnE,oEAAoE;QACpE,oEAAoE;QACpE,IAAI,CAAC;YACH,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,IACE,CAAC,GAAG;gBACJ,OAAO,GAAG,KAAK,QAAQ;gBACvB,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC;gBACf,GAAyB,CAAC,IAAI,KAAK,QAAQ,EAC5C,CAAC;gBACD,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;QACD,EAAE,CAAC,WAAW,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QACzC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC;IACzC,CAAC;IAED,qEAAqE;IACrE,qEAAqE;IACrE,sEAAsE;IACtE,+DAA+D;IAC/D,iEAAiE;IACjE,iEAAiE;IACjE,iEAAiE;IACjE,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,QAAQ,CAAC,cAAc,EAAE,EAAE,CAAC;YAC9B,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,mEAAmE;QACnE,IACE,GAAG;YACH,OAAO,GAAG,KAAK,QAAQ;YACvB,MAAM,IAAI,GAAG;YACZ,GAAyB,CAAC,IAAI,KAAK,QAAQ,EAC5C,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAiC,CAAC;IAC1D,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QACjC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAClC,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAEnD,4DAA4D;IAC5D,oEAAoE;IACpE,qEAAqE;IACrE,8DAA8D;IAC9D,oEAAoE;IACpE,2DAA2D;IAC3D,EAAE;IACF,sEAAsE;IACtE,oEAAoE;IACpE,qEAAqE;IACrE,+DAA+D;IAC/D,qEAAqE;IACrE,iEAAiE;IACjE,sDAAsD;IACtD,MAAM,SAAS,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC,kBAAkB,CAAC,CAAC;IAC1D,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACpE,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;QACtC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;YAC9D,IAAI,CAAC;gBACH,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YAClC,CAAC;YAAC,MAAM,CAAC;gBACP,8DAA8D;gBAC9D,4DAA4D;gBAC5D,4DAA4D;gBAC5D,8CAA8C;YAChD,CAAC;QACH,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,qEAAqE;IACrE,uEAAuE;IACvE,yEAAyE;IACzE,EAAE;IACF,0EAA0E;IAC1E,sEAAsE;IACtE,uEAAuE;IACvE,2EAA2E;IAC3E,wEAAwE;IACxE,2EAA2E;IAC3E,2EAA2E;IAC3E,EAAE;IACF,qEAAqE;IACrE,qEAAqE;IACrE,8BAA8B;IAC9B,EAAE;IACF,wEAAwE;IACxE,sEAAsE;IACtE,kEAAkE;IAClE,uEAAuE;IACvE,sEAAsE;IACtE,sEAAsE;IACtE,oEAAoE;IACpE,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC,mBAAmB,CAAC,CAAC;IAC1D,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,iBAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrE,MAAM,OAAO,GAAG,QAAQ,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACvC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,IAAI,CAAC;gBACH,kEAAkE;gBAClE,+DAA+D;gBAC/D,6DAA6D;gBAC7D,4DAA4D;gBAC5D,wCAAwC;gBACxC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;gBACpC,EAAE,CAAC,UAAU,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;YACjD,CAAC;YAAC,MAAM,CAAC;gBACP,6DAA6D;gBAC7D,gEAAgE;gBAChE,yBAAyB;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,sEAAsE;IACtE,mEAAmE;IACnE,mEAAmE;IACnE,sDAAsD;IAEtD,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC;AACzC,CAAC;AASD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAkB,EAClB,MAAe;IAEf,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,IAAI,iBAAqC,CAAC;IAE1C,GAAG,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAChC,IAAI,oBAAoB,CAAC;YACvB,MAAM,EAAE,GAAG,CAAC,UAAU;YACtB,MAAM,EAAE,MAAM;YACd,iBAAiB,EAAE,iBAAiB;SACrC,CAAC,CACH,CAAC;QAEF,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;YAC1C,uEAAuE;YACvE,oEAAoE;YACpE,oEAAoE;YACpE,+DAA+D;YAC/D,0BAA0B;YAC1B,IAAI,CAAC,GAAG,CAAC,GAAG;gBAAE,SAAS;YACvB,mEAAmE;YACnE,mEAAmE;YACnE,kEAAkE;YAClE,kEAAkE;YAClE,mEAAmE;YACnE,iEAAiE;YACjE,+DAA+D;YAC/D,8CAA8C;YAC9C,EAAE;YACF,iEAAiE;YACjE,mEAAmE;YACnE,+DAA+D;YAC/D,iEAAiE;YACjE,8DAA8D;YAC9D,iEAAiE;YACjE,iEAAiE;YACjE,uDAAuD;YACvD,EAAE;YACF,gEAAgE;YAChE,8DAA8D;YAC9D,iBAAiB;YACjB,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC;gBAAE,SAAS;YAE7D,KAAK,CAAC,IAAI,CAAC;gBACT,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC;gBACnB,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE;gBAC5C,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE;aACrB,CAAC,CAAC;QACL,CAAC;QAED,iBAAiB,GAAG,QAAQ,CAAC,qBAAqB,CAAC;IACrD,CAAC,QAAQ,iBAAiB,EAAE;IAE5B,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAkB,EAClB,GAAW;IAEX,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAEhC,MAAM,MAAM,CAAC,IAAI,CACf,IAAI,mBAAmB,CAAC;QACtB,MAAM,EAAE,GAAG,CAAC,UAAU;QACtB,GAAG,EAAE,GAAG;KACT,CAAC,CACH,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAkB,EAClB,GAAW;IAEX,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAChC,IAAI,iBAAiB,CAAC;YACpB,MAAM,EAAE,GAAG,CAAC,UAAU;YACtB,GAAG,EAAE,GAAG;SACT,CAAC,CACH,CAAC;QACF,OAAO;YACL,YAAY,EAAE,QAAQ,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE;YACjD,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,EAAE;YACzB,IAAI,EAAE,QAAQ,CAAC,aAAa,IAAI,CAAC;YACjC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;SAC5B,CAAC;IACJ,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC/E,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,QAAgB;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IACjD,MAAM,SAAS,GAA2B;QACxC,KAAK,EAAE,eAAe;QACtB,OAAO,EAAE,kBAAkB;QAC3B,OAAO,EAAE,WAAW;QACpB,MAAM,EAAE,WAAW;QACnB,KAAK,EAAE,iBAAiB;QACxB,KAAK,EAAE,iBAAiB;QACxB,MAAM,EAAE,YAAY;QACpB,OAAO,EAAE,WAAW;QACpB,MAAM,EAAE,UAAU;QAClB,MAAM,EAAE,WAAW;QACnB,MAAM,EAAE,YAAY;QACpB,OAAO,EAAE,YAAY;QACrB,MAAM,EAAE,eAAe;QACvB,MAAM,EAAE,iBAAiB;KAC1B,CAAC;IACF,OAAO,SAAS,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAC;AACtD,CAAC"}
package/dist/s3.test.js CHANGED
@@ -88,7 +88,7 @@ vi.mock("@aws-sdk/client-s3", () => {
88
88
  DeleteObjectCommand,
89
89
  };
90
90
  });
91
- import { uploadFile, uploadSymlink, downloadFile, listRemoteFiles, SYMLINK_BODY_PREFIX, SYMLINK_MARKER_META_VALUE, encodeSymlinkMetadataValue, decodeSymlinkMetadataValue, } from "./s3.js";
91
+ import { uploadFile, uploadSymlink, downloadFile, listRemoteFiles, SYMLINK_BODY_PREFIX, SYMLINK_MARKER_META_VALUE, encodeSymlinkMetadataValue, decodeSymlinkMetadataValue, FILE_MTIME_META_KEY, FILE_BTIME_META_KEY, } from "./s3.js";
92
92
  function makeCtx() {
93
93
  return {
94
94
  uid: "cmp_TEST",
@@ -202,6 +202,57 @@ describe("uploadFile", () => {
202
202
  expect(meta?.["hq-mode"]).toBe(mode.toString(8));
203
203
  }
204
204
  });
205
+ it("stamps source file mtimeMs as hq-mtime metadata (5.37.0 — preserve mtime)", async () => {
206
+ // Symmetric to Bug #5's mode preservation. A file's modification time
207
+ // should follow it across machines instead of resetting to "the time of
208
+ // sync." Push stamps `hq-mtime` = integer-ms epoch string; pull applies
209
+ // it via utimesSync. See FILE_MTIME_META_KEY doc.
210
+ const knownMs = 1_700_000_000_000; // 2023-11-14T22:13:20Z, comfortably finite
211
+ const knownDate = new Date(knownMs);
212
+ fs.utimesSync(tmpFile, knownDate, knownDate);
213
+ await uploadFile(makeCtx(), tmpFile, "stamped.md");
214
+ const put = sentCommands.find((c) => c.name === "PutObjectCommand");
215
+ expect(put).toBeDefined();
216
+ const meta = put.input.Metadata;
217
+ expect(meta?.[FILE_MTIME_META_KEY]).toBe(String(knownMs));
218
+ });
219
+ it("stamps hq-btime only when birthtime is supported AND distinct from mtime", async () => {
220
+ // Many filesystems (Linux/ext4 historically, tmpfs, some FUSE mounts)
221
+ // return birthtimeMs === 0 or birthtimeMs === mtimeMs. Stamping in
222
+ // those cases would be pure noise; the upload side filters it out.
223
+ // We don't control the test filesystem's birthtime semantics, so the
224
+ // assertion is shape-only: IF hq-btime is present, it's a numeric
225
+ // string AND it's distinct from hq-mtime.
226
+ await uploadFile(makeCtx(), tmpFile, "btime.md");
227
+ const put = sentCommands.find((c) => c.name === "PutObjectCommand");
228
+ const meta = put.input.Metadata;
229
+ const btime = meta?.[FILE_BTIME_META_KEY];
230
+ const mtime = meta?.[FILE_MTIME_META_KEY];
231
+ if (typeof btime === "string") {
232
+ expect(btime).toMatch(/^[0-9]{1,16}$/);
233
+ expect(btime).not.toBe(mtime);
234
+ }
235
+ });
236
+ it("does NOT stamp hq-mtime / hq-btime for symlinks (uploadFile branch)", async () => {
237
+ // uploadFile is the regular-file path; uploadSymlink is the symlink
238
+ // path. uploadFile's lstat.isSymbolicLink() guard skips ALL three of
239
+ // mode + mtime + btime stamping for symlinks, mirroring the existing
240
+ // hq-mode policy: symlink times are OS-controlled and fs.utimesSync
241
+ // would follow the link and mutate the target. The link is set up
242
+ // pointing at a real target so fs.readFileSync (which follows links)
243
+ // doesn't ENOENT before the metadata stamping branch even runs.
244
+ const targetPath = path.join(os.tmpdir(), `s3-link-target-${Date.now()}-${Math.random()}.txt`);
245
+ fs.writeFileSync(targetPath, "real-bytes");
246
+ const linkPath = path.join(os.tmpdir(), `s3-link-test-${Date.now()}-${Math.random()}.lnk`);
247
+ fs.symlinkSync(targetPath, linkPath);
248
+ await uploadFile(makeCtx(), linkPath, "link.md");
249
+ const put = sentCommands.find((c) => c.name === "PutObjectCommand");
250
+ const meta = put.input.Metadata;
251
+ expect(meta?.[FILE_MTIME_META_KEY]).toBeUndefined();
252
+ expect(meta?.[FILE_BTIME_META_KEY]).toBeUndefined();
253
+ fs.unlinkSync(linkPath);
254
+ fs.unlinkSync(targetPath);
255
+ });
205
256
  it("elides non-ASCII or empty author fields rather than throwing", async () => {
206
257
  // S3 user-defined metadata must be ASCII-only and total ≤ 2KB. Partial
207
258
  // attribution beats hard failure — values that fail the printable check
@@ -677,6 +728,215 @@ describe("downloadFile", () => {
677
728
  // No assertion on mode — receiver default is whatever umask set.
678
729
  expect(fs.readFileSync(localPath, "utf-8")).toBe("legacy");
679
730
  });
731
+ it("applies hq-mtime via utimesSync after byte write (5.37.0 — preserve mtime)", async () => {
732
+ // Round-trip pair to the s3.upload mtime stamp test. The receiver must
733
+ // set the local file's mtime to the metadata-stamped value AFTER the
734
+ // byte write, mirroring the chmod-after-write shape from Bug #5.
735
+ const knownMs = 1_700_000_000_000;
736
+ nextGetObjectResponse = {
737
+ Body: (async function* () {
738
+ yield new Uint8Array([116, 105, 109, 101]); // "time"
739
+ })(),
740
+ Metadata: { [FILE_MTIME_META_KEY]: String(knownMs) },
741
+ };
742
+ const localPath = path.join(tmpRoot, "mtime-applied.bin");
743
+ await downloadFile(makeCtx(), "mtime-applied.bin", localPath);
744
+ const stat = fs.statSync(localPath);
745
+ // ±50ms tolerance accommodates ms-precision filesystems and the
746
+ // utimesSync→stat round-trip rounding. macOS APFS reports full ms;
747
+ // some Linux filesystems round to seconds — guard with tolerance.
748
+ expect(Math.abs(stat.mtimeMs - knownMs)).toBeLessThanOrEqual(50);
749
+ });
750
+ it("rejects malformed hq-mtime metadata via strict-numeric regex", async () => {
751
+ // Symmetric to the hq-mode Codex P2 lesson: parseInt accepts partial-
752
+ // prefix garbage ("175junk" → 175). A strict `^[0-9]{1,16}$` regex
753
+ // BEFORE parseInt rejects empty / signed / decimals / whitespace /
754
+ // oversized / non-numeric and falls through to "no utimes call,"
755
+ // leaving the file at write-time mtime — the legacy back-compat path.
756
+ // NOTE: `-100` is intentionally NOT in this list — Codex PR #27 P2 pointed
757
+ // out that legitimate epoch values include 0 (Unix epoch) and negatives
758
+ // (e.g. reproducible-build clamping pre-1970). Negative-but-well-formed
759
+ // strings are now accepted; see the dedicated round-trip tests below.
760
+ const malformed = [
761
+ "175junk", // trailing garbage — parseInt parses 175 pre-fix
762
+ "abc", // non-numeric
763
+ "", // empty
764
+ " 100 ", // whitespace
765
+ "--100", // double-sign — only ONE optional leading "-" allowed
766
+ "+100", // explicit plus rejected (regex only allows optional "-")
767
+ "3.14", // decimal
768
+ "99999999999999999", // >16 digits
769
+ ];
770
+ for (const bad of malformed) {
771
+ // Establish a known pre-write mtime by writing the file first.
772
+ const localPath = path.join(tmpRoot, `bad-mtime-${malformed.indexOf(bad)}.bin`);
773
+ nextGetObjectResponse = {
774
+ Body: (async function* () {
775
+ yield new Uint8Array([98]); // "b"
776
+ })(),
777
+ Metadata: { [FILE_MTIME_META_KEY]: bad },
778
+ };
779
+ const before = Date.now();
780
+ await downloadFile(makeCtx(), `bad-mtime-${malformed.indexOf(bad)}.bin`, localPath);
781
+ const stat = fs.statSync(localPath);
782
+ // The file's mtime MUST be wall-clock-now (write-time), NOT the
783
+ // partial-parse value. For "175junk", that means mtime is in the
784
+ // current millis range, not ms=175 (1970-01-01T00:00:00.175Z).
785
+ // Generic check: mtime is near `before` (within 5s).
786
+ expect(Math.abs(stat.mtimeMs - before)).toBeLessThan(5000);
787
+ // Specific check: never the partial-parse epoch (~175ms after epoch).
788
+ expect(stat.mtimeMs).toBeGreaterThan(1_000_000_000_000);
789
+ }
790
+ });
791
+ it("round-trips mtimeMs === 0 (Unix epoch — Codex PR #27 P2)", async () => {
792
+ // Codex flagged that filtering `mtimeMs > 0` drops legitimate epoch-0
793
+ // timestamps (and reproducible-build clamping uses exactly 0). The
794
+ // receiver must accept "0", parse it, and stamp `new Date(0)` on disk.
795
+ const sourcePath = path.join(tmpRoot, "epoch-zero-src.bin");
796
+ fs.writeFileSync(sourcePath, "epoch-bytes");
797
+ fs.utimesSync(sourcePath, new Date(0), new Date(0));
798
+ sentCommands.length = 0;
799
+ await uploadFile(makeCtx(), sourcePath, "epoch-zero.bin");
800
+ const put = sentCommands.find((c) => c.name === "PutObjectCommand");
801
+ const stampedMeta = put.input.Metadata;
802
+ const stampedBody = put.input.Body;
803
+ expect(stampedMeta[FILE_MTIME_META_KEY]).toBe("0");
804
+ fs.unlinkSync(sourcePath);
805
+ nextGetObjectResponse = {
806
+ Body: (async function* () {
807
+ yield new Uint8Array(stampedBody);
808
+ })(),
809
+ Metadata: stampedMeta,
810
+ };
811
+ await downloadFile(makeCtx(), "epoch-zero.bin", sourcePath);
812
+ const stat = fs.statSync(sourcePath);
813
+ // 0 is integer-clean — exact match expected, but allow 50ms tolerance
814
+ // for any underlying FS rounding shenanigans.
815
+ expect(Math.abs(stat.mtimeMs - 0)).toBeLessThanOrEqual(50);
816
+ expect(fs.readFileSync(sourcePath, "utf-8")).toBe("epoch-bytes");
817
+ });
818
+ it("round-trips a negative mtime (pre-epoch — Codex PR #27 P2)", async () => {
819
+ // Some filesystems / reproducible-build pipelines clamp mtime to values
820
+ // before the Unix epoch (negative ms). `new Date(-86400000)` is a legal
821
+ // Date; `fs.utimesSync` accepts it. The receiver MUST NOT drop these.
822
+ const sourceMs = -86_400_000; // 1969-12-31T00:00:00Z, one day before epoch
823
+ const sourcePath = path.join(tmpRoot, "pre-epoch-src.bin");
824
+ fs.writeFileSync(sourcePath, "pre-epoch-bytes");
825
+ fs.utimesSync(sourcePath, new Date(sourceMs), new Date(sourceMs));
826
+ sentCommands.length = 0;
827
+ await uploadFile(makeCtx(), sourcePath, "pre-epoch.bin");
828
+ const put = sentCommands.find((c) => c.name === "PutObjectCommand");
829
+ const stampedMeta = put.input.Metadata;
830
+ const stampedBody = put.input.Body;
831
+ expect(stampedMeta[FILE_MTIME_META_KEY]).toBe(String(sourceMs));
832
+ fs.unlinkSync(sourcePath);
833
+ nextGetObjectResponse = {
834
+ Body: (async function* () {
835
+ yield new Uint8Array(stampedBody);
836
+ })(),
837
+ Metadata: stampedMeta,
838
+ };
839
+ await downloadFile(makeCtx(), "pre-epoch.bin", sourcePath);
840
+ const stat = fs.statSync(sourcePath);
841
+ expect(Math.abs(stat.mtimeMs - sourceMs)).toBeLessThanOrEqual(50);
842
+ expect(fs.readFileSync(sourcePath, "utf-8")).toBe("pre-epoch-bytes");
843
+ });
844
+ it("accepts well-formed negative hq-mtime (e.g. '-100') on download", async () => {
845
+ // Migration of the old rejection-list case for `-100`. Codex PR #27 P2:
846
+ // negative epoch-ms values are legitimate; the regex now allows a single
847
+ // optional leading `-`, and utimesSync handles it.
848
+ const localPath = path.join(tmpRoot, "negative-mtime.bin");
849
+ nextGetObjectResponse = {
850
+ Body: (async function* () {
851
+ yield new Uint8Array([110]); // "n"
852
+ })(),
853
+ Metadata: { [FILE_MTIME_META_KEY]: "-100" },
854
+ };
855
+ await downloadFile(makeCtx(), "negative-mtime.bin", localPath);
856
+ const stat = fs.statSync(localPath);
857
+ // mtime should be ~ -100 ms, NOT wall-clock-now. The old code would have
858
+ // left this at write-time; the new code applies the negative stamp.
859
+ expect(Math.abs(stat.mtimeMs - -100)).toBeLessThanOrEqual(50);
860
+ });
861
+ it("downloads with default mtime when hq-mtime metadata is absent (back-compat)", async () => {
862
+ // Legacy uploads (pre-5.37.0) carry no hq-mtime header. The receiver
863
+ // must NOT crash and must NOT alter the on-disk mtime away from the
864
+ // default `fs.writeFileSync` wall-clock-now behavior. Mirrors the
865
+ // hq-mode back-compat test.
866
+ nextGetObjectResponse = {
867
+ Body: (async function* () {
868
+ yield new Uint8Array([108, 101, 103]); // "leg"
869
+ })(),
870
+ Metadata: {},
871
+ };
872
+ const before = Date.now();
873
+ const localPath = path.join(tmpRoot, "legacy-mtime.bin");
874
+ await downloadFile(makeCtx(), "legacy-mtime.bin", localPath);
875
+ const stat = fs.statSync(localPath);
876
+ expect(Math.abs(stat.mtimeMs - before)).toBeLessThan(5000);
877
+ expect(fs.readFileSync(localPath, "utf-8")).toBe("leg");
878
+ });
879
+ it("does NOT call utimesSync on the symlink branch (5.37.0)", async () => {
880
+ // fs.utimesSync follows symlinks (mutates the target's mtime, not the
881
+ // link's). On the symlink branch we must skip the utimes block entirely
882
+ // so we don't accidentally mutate an unrelated target file. The
883
+ // symlink record's hq-mtime, if any, is ignored — symlink mtime is
884
+ // OS-controlled and Node has no stable lutimes API. Easiest verifier:
885
+ // the link is materialized AND the test doesn't crash.
886
+ const targetPath = path.join(tmpRoot, "untouched-target.txt");
887
+ fs.writeFileSync(targetPath, "do-not-touch");
888
+ const knownTargetMs = 1_500_000_000_000;
889
+ fs.utimesSync(targetPath, new Date(knownTargetMs), new Date(knownTargetMs));
890
+ const linkPath = path.join(tmpRoot, "the-link");
891
+ nextGetObjectResponse = {
892
+ Body: (async function* () {
893
+ yield Buffer.from(SYMLINK_BODY_PREFIX + "untouched-target.txt");
894
+ })(),
895
+ Metadata: {
896
+ "hq-symlink-target": SYMLINK_MARKER_META_VALUE,
897
+ // Even if a misbehaving uploader stamped hq-mtime on a symlink
898
+ // record, the downloader's symlink branch returns BEFORE the
899
+ // utimes block, so the target's mtime stays at knownTargetMs.
900
+ [FILE_MTIME_META_KEY]: "1_900_000_000_000",
901
+ },
902
+ };
903
+ await downloadFile(makeCtx(), "the-link", linkPath);
904
+ expect(fs.lstatSync(linkPath).isSymbolicLink()).toBe(true);
905
+ expect(fs.readlinkSync(linkPath)).toBe("untouched-target.txt");
906
+ // Target's mtime is unchanged — utimes block was skipped.
907
+ const targetStat = fs.statSync(targetPath);
908
+ expect(Math.abs(targetStat.mtimeMs - knownTargetMs)).toBeLessThanOrEqual(50);
909
+ });
910
+ it("round-trips mtime across upload + download (5.37.0 user-visible promise)", async () => {
911
+ // The whole point of the feature: a file's mtime should follow it
912
+ // across the wire. Push captures source mtime → metadata; pull applies
913
+ // metadata → local mtime. This test wires upload's PutObject capture
914
+ // into download's GetObject response so the contract round-trips end-
915
+ // to-end through s3.ts without bringing in journal/CLI state.
916
+ const sourceMs = 1_650_000_000_000; // 2022-04-15T03:33:20Z
917
+ const sourcePath = path.join(tmpRoot, "round-trip-src.bin");
918
+ fs.writeFileSync(sourcePath, "round-trip-bytes");
919
+ fs.utimesSync(sourcePath, new Date(sourceMs), new Date(sourceMs));
920
+ sentCommands.length = 0;
921
+ await uploadFile(makeCtx(), sourcePath, "round-trip.bin");
922
+ const put = sentCommands.find((c) => c.name === "PutObjectCommand");
923
+ const stampedMeta = put.input.Metadata;
924
+ const stampedBody = put.input.Body;
925
+ // Wipe the local copy and pull via download — simulating a different
926
+ // machine receiving the file.
927
+ fs.unlinkSync(sourcePath);
928
+ nextGetObjectResponse = {
929
+ Body: (async function* () {
930
+ yield new Uint8Array(stampedBody);
931
+ })(),
932
+ Metadata: stampedMeta,
933
+ };
934
+ await downloadFile(makeCtx(), "round-trip.bin", sourcePath);
935
+ const stat = fs.statSync(sourcePath);
936
+ // Within 100ms — covers FS rounding on both ends of the round-trip.
937
+ expect(Math.abs(stat.mtimeMs - sourceMs)).toBeLessThanOrEqual(100);
938
+ expect(fs.readFileSync(sourcePath, "utf-8")).toBe("round-trip-bytes");
939
+ });
680
940
  it("returns the object's user-metadata (including created-by) for a regular file", async () => {
681
941
  nextGetObjectResponse = {
682
942
  Body: (async function* () {