@ricsam/r5d-worker 0.0.123 → 0.0.125

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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/cjs/command-launcher.cjs +6 -2
  3. package/dist/cjs/main.cjs +496 -238
  4. package/dist/cjs/package.json +1 -1
  5. package/dist/cjs/project-checkout-garbage.cjs +32 -3
  6. package/dist/cjs/project-workspace-state.cjs +51 -35
  7. package/dist/cjs/project-worktrees.cjs +79 -34
  8. package/dist/cjs/recovery-journal-protocol.cjs +56 -0
  9. package/dist/cjs/recovery-journal-runtime.cjs +133 -0
  10. package/dist/cjs/recovery-journal-thread.cjs +9 -0
  11. package/dist/cjs/recovery-journal.cjs +735 -0
  12. package/dist/cjs/recovery-store.cjs +8 -0
  13. package/dist/cjs/session-file-mutations.cjs +61 -0
  14. package/dist/cjs/working-tree-mirror.cjs +1 -0
  15. package/dist/cjs/workspace-command-sync-policy.cjs +37 -8
  16. package/dist/cjs/workspace-filesystem-executor-thread.cjs +36 -0
  17. package/dist/cjs/workspace-filesystem-executor.cjs +327 -0
  18. package/dist/cjs/workspace-filesystem-job-types.cjs +134 -0
  19. package/dist/cjs/workspace-filesystem-jobs.cjs +57 -0
  20. package/dist/cjs/workspace-git-sync.cjs +275 -201
  21. package/dist/cjs/workspace-mount-hold-fence.cjs +120 -0
  22. package/dist/mjs/command-launcher.mjs +6 -2
  23. package/dist/mjs/main.mjs +499 -244
  24. package/dist/mjs/package.json +1 -1
  25. package/dist/mjs/project-checkout-garbage.mjs +30 -2
  26. package/dist/mjs/project-workspace-state.mjs +51 -35
  27. package/dist/mjs/project-worktrees.mjs +74 -34
  28. package/dist/mjs/recovery-journal-protocol.mjs +30 -0
  29. package/dist/mjs/recovery-journal-runtime.mjs +112 -0
  30. package/dist/mjs/recovery-journal-thread.mjs +8 -0
  31. package/dist/mjs/recovery-journal.mjs +687 -0
  32. package/dist/mjs/recovery-store.mjs +8 -0
  33. package/dist/mjs/session-file-mutations.mjs +37 -0
  34. package/dist/mjs/working-tree-mirror.mjs +1 -0
  35. package/dist/mjs/workspace-command-sync-policy.mjs +37 -8
  36. package/dist/mjs/workspace-filesystem-executor-thread.mjs +38 -0
  37. package/dist/mjs/workspace-filesystem-executor.mjs +287 -0
  38. package/dist/mjs/workspace-filesystem-job-types.mjs +106 -0
  39. package/dist/mjs/workspace-filesystem-jobs.mjs +51 -0
  40. package/dist/mjs/workspace-git-sync.mjs +264 -202
  41. package/dist/mjs/workspace-mount-hold-fence.mjs +95 -0
  42. package/dist/types/command-launcher.d.ts +2 -1
  43. package/dist/types/main.d.ts +30 -19
  44. package/dist/types/project-checkout-garbage.d.ts +19 -2
  45. package/dist/types/project-workspace-state.d.ts +9 -9
  46. package/dist/types/project-worktrees.d.ts +49 -5
  47. package/dist/types/recovery-journal-protocol.d.ts +35 -0
  48. package/dist/types/recovery-journal-runtime.d.ts +12 -0
  49. package/dist/types/recovery-journal-stall-fixture.d.ts +1 -0
  50. package/dist/types/recovery-journal-thread.d.ts +1 -0
  51. package/dist/types/recovery-journal.d.ts +246 -0
  52. package/dist/types/recovery-store.d.ts +6 -0
  53. package/dist/types/session-file-mutations.d.ts +22 -0
  54. package/dist/types/workspace-command-sync-policy.d.ts +22 -7
  55. package/dist/types/workspace-filesystem-executor-thread.d.ts +1 -0
  56. package/dist/types/workspace-filesystem-executor.d.ts +123 -0
  57. package/dist/types/workspace-filesystem-job-types.d.ts +246 -0
  58. package/dist/types/workspace-filesystem-jobs.d.ts +7 -0
  59. package/dist/types/workspace-git-sync.d.ts +113 -7
  60. package/dist/types/workspace-mount-hold-fence.d.ts +42 -0
  61. package/package.json +1 -1
  62. package/dist/cjs/project-snapshot-recovery-runner.cjs +0 -171
  63. package/dist/mjs/project-snapshot-recovery-runner.mjs +0 -135
  64. package/dist/types/project-snapshot-recovery-runner.d.ts +0 -10
@@ -40,9 +40,20 @@ __export(workspace_git_sync_exports, {
40
40
  hydrateWorkspaceGitMounts: () => hydrateWorkspaceGitMounts,
41
41
  recoverWorkspaceGitHydration: () => recoverWorkspaceGitHydration,
42
42
  resetWorkspaceGit: () => resetWorkspaceGit,
43
+ runCheckoutDurabilityRecoveryJob: () => runCheckoutDurabilityRecoveryJob,
44
+ runCheckoutTransitionBeginJob: () => runCheckoutTransitionBeginJob,
45
+ runCheckoutTransitionCompleteJob: () => runCheckoutTransitionCompleteJob,
46
+ runHydrationRawJob: () => runHydrationRawJob,
47
+ runHydrationRecoveryJob: () => runHydrationRecoveryJob,
48
+ runHydrationTransactionJob: () => runHydrationTransactionJob,
49
+ runOuterCheckoutFsyncJob: () => runOuterCheckoutFsyncJob,
50
+ runProjectionMirrorJob: () => runProjectionMirrorJob,
51
+ runProjectionOuterApplyJob: () => runProjectionOuterApplyJob,
52
+ runProjectionReceiptJob: () => runProjectionReceiptJob,
43
53
  synchronizeWorkspaceGit: () => synchronizeWorkspaceGit,
44
54
  workspaceGitHydrationIsCurrent: () => workspaceGitHydrationIsCurrent,
45
55
  workspaceGitHydrationIsRecoverable: () => workspaceGitHydrationIsRecoverable,
56
+ workspaceGitMountData: () => workspaceGitMountData,
46
57
  workspaceGitMountsForRemediation: () => workspaceGitMountsForRemediation,
47
58
  workspaceGitSyncTestHarness: () => workspaceGitSyncTestHarness
48
59
  });
@@ -51,6 +62,7 @@ var import_node_fs = __toESM(require("node:fs"), 1);
51
62
  var import_node_path = __toESM(require("node:path"), 1);
52
63
  var import_git_process_environment = require("./git-process-environment.cjs");
53
64
  var import_workspace_merge_projection = require("./workspace-merge-projection.cjs");
65
+ var import_workspace_filesystem_executor = require("./workspace-filesystem-executor.cjs");
54
66
  var import_working_tree_mirror = require("./working-tree-mirror.cjs");
55
67
  var import_workspace_mount_boundary = require("./workspace-mount-boundary.cjs");
56
68
  const WORKSPACE_GIT_BRANCH = "main";
@@ -60,6 +72,22 @@ const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
60
72
  const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
61
73
  const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
62
74
  const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
75
+ function workspaceGitMountData(mount) {
76
+ return {
77
+ id: mount.id,
78
+ hydrationIncarnationKey: mount.hydrationIncarnationKey,
79
+ sourcePath: mount.sourcePath,
80
+ durabilityRootPath: mount.durabilityRootPath,
81
+ workspaceRelativePath: mount.workspaceRelativePath,
82
+ sourceMode: mount.sourceMode,
83
+ hydrateDeletionMode: mount.hydrateDeletionMode,
84
+ ...mount.preserveLocalOnInitialOuterAbsence === void 0 ? {} : { preserveLocalOnInitialOuterAbsence: mount.preserveLocalOnInitialOuterAbsence },
85
+ ...mount.preserveLocalOnHydrationBasisChange === void 0 ? {} : { preserveLocalOnHydrationBasisChange: mount.preserveLocalOnHydrationBasisChange },
86
+ ...mount.initialPublicationSourceRelativePath === void 0 ? {} : { initialPublicationSourceRelativePath: mount.initialPublicationSourceRelativePath },
87
+ ...mount.deleteWhenSourceMissing === void 0 ? {} : { deleteWhenSourceMissing: mount.deleteWhenSourceMissing },
88
+ ...mount.lastProjectedMutationToken === void 0 ? {} : { lastProjectedMutationToken: mount.lastProjectedMutationToken }
89
+ };
90
+ }
63
91
  function workspaceGitMountsForRemediation(mounts) {
64
92
  return mounts.map((mount) => ({ ...mount, busy: mount.busyForRecovery ?? mount.busy }));
65
93
  }
@@ -151,9 +179,6 @@ async function revParseAsync(workspacePath, revision) {
151
179
  const result = await gitResultAsync(workspacePath, ["rev-parse", "--verify", revision]);
152
180
  return result.exitCode === 0 ? result.stdout : null;
153
181
  }
154
- function yieldToWorkerEventLoop() {
155
- return new Promise((resolve) => setImmediate(resolve));
156
- }
157
182
  function workspaceCommitsExist(workspacePath, heads) {
158
183
  const uniqueHeads = [...new Set(heads)];
159
184
  if (uniqueHeads.length === 0) return true;
@@ -418,20 +443,35 @@ function readHydratedWorkspaceReceipt(workspacePath) {
418
443
  }
419
444
  return unverified.receipt;
420
445
  }
446
+ async function readHydratedWorkspaceReceiptUnverifiedAsync(workspacePath) {
447
+ const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
448
+ let status;
449
+ try {
450
+ status = await import_node_fs.default.promises.lstat(receiptPath);
451
+ } catch (error) {
452
+ if (error.code === "ENOENT") return null;
453
+ throw error;
454
+ }
455
+ if (!status.isFile() || status.isSymbolicLink()) {
456
+ throw new Error(`Workspace hydration receipt is not a regular file: ${receiptPath}`);
457
+ }
458
+ const content = await import_node_fs.default.promises.readFile(receiptPath, "utf8");
459
+ return { ...parseWorkspaceHydrationReceiptContentUnverified(receiptPath, content), receiptPath };
460
+ }
421
461
  async function readHydratedWorkspaceReceiptAsync(workspacePath) {
422
- const unverified = readHydratedWorkspaceReceiptUnverified(workspacePath);
462
+ const unverified = await readHydratedWorkspaceReceiptUnverifiedAsync(workspacePath);
423
463
  if (!unverified) return null;
424
464
  if (!await workspaceCommitsExistAsync(workspacePath, unverified.headsToVerify)) {
425
465
  throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${unverified.receiptPath}`);
426
466
  }
427
467
  return unverified.receipt;
428
468
  }
429
- function hydratedWorkspaceReceiptFileIsV3(workspacePath) {
469
+ async function hydratedWorkspaceReceiptFileIsV3(workspacePath) {
430
470
  const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
431
- const status = lstatIfExists(receiptPath);
432
- if (!status?.isFile() || status.isSymbolicLink()) return false;
433
471
  try {
434
- return JSON.parse(import_node_fs.default.readFileSync(receiptPath, "utf8")).version === 3;
472
+ const status = await import_node_fs.default.promises.lstat(receiptPath);
473
+ if (!status.isFile() || status.isSymbolicLink()) return false;
474
+ return JSON.parse(await import_node_fs.default.promises.readFile(receiptPath, "utf8")).version === 3;
435
475
  } catch {
436
476
  return false;
437
477
  }
@@ -528,24 +568,6 @@ function runWorkspaceRefUpdateBatch(workspacePath, instructions, action) {
528
568
  throw new Error(`${action}: ${updated.stderr.toString().trim() || `git exited ${updated.exitCode}`}`);
529
569
  }
530
570
  }
531
- async function runWorkspaceRefUpdateBatchAsync(workspacePath, instructions, action) {
532
- if (instructions.length === 0) return;
533
- const subprocess = Bun.spawn(gitCommandArgs(["update-ref", "--stdin"]), {
534
- cwd: workspacePath,
535
- stdin: Buffer.from([...instructions, ""].join("\n")),
536
- stdout: "pipe",
537
- stderr: "pipe",
538
- env: (0, import_git_process_environment.workerGitProcessEnvironment)()
539
- });
540
- const [, stderr, exitCode] = await Promise.all([
541
- new Response(subprocess.stdout).text(),
542
- new Response(subprocess.stderr).text(),
543
- subprocess.exited
544
- ]);
545
- if (exitCode !== 0) {
546
- throw new Error(`${action}: ${stderr.trim() || `git exited ${exitCode}`}`);
547
- }
548
- }
549
571
  function workspaceBasisRefTransaction(receipt, stableRefs) {
550
572
  const desired = desiredWorkspaceBasisRefs(receipt);
551
573
  const transaction = ["start"];
@@ -568,24 +590,6 @@ function updateWorkspaceBasisRefs(workspacePath, receipt) {
568
590
  "remove staged workspace hydration basis refs"
569
591
  );
570
592
  }
571
- async function updateWorkspaceBasisRefsAsync(workspacePath, receipt) {
572
- const stableRefs = (await gitAsync(workspacePath, ["for-each-ref", "--format=%(refname)", WORKSPACE_GIT_BASIS_REF_PREFIX], "list workspace basis refs")).split("\n").filter(Boolean).sort();
573
- await runWorkspaceRefUpdateBatchAsync(
574
- workspacePath,
575
- workspaceBasisRefTransaction(receipt, stableRefs),
576
- "Synchronize workspace hydration basis refs"
577
- );
578
- const stagedRefs = (await gitAsync(
579
- workspacePath,
580
- ["for-each-ref", "--format=%(refname)", WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX],
581
- "list staged workspace basis refs"
582
- )).split("\n").filter(Boolean).sort();
583
- await runWorkspaceRefUpdateBatchAsync(
584
- workspacePath,
585
- stagedRefs.map((ref) => `delete ${ref}`),
586
- "remove staged workspace hydration basis refs"
587
- );
588
- }
589
593
  function assertCanonicalHydrationReceiptHeads(receipt) {
590
594
  for (const mount of receipt.mounts) {
591
595
  if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(mount.head)) {
@@ -599,20 +603,15 @@ function throwMissingHydrationReceiptCommit(workspacePath, receipt) {
599
603
  }
600
604
  function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
601
605
  assertCanonicalHydrationReceiptHeads(receipt);
602
- if (!workspaceCommitsExist(workspacePath, receipt.mounts.map(({ head }) => head))) {
606
+ if (!workspaceCommitsExist(
607
+ workspacePath,
608
+ receipt.mounts.map(({ head }) => head)
609
+ )) {
603
610
  throwMissingHydrationReceiptCommit(workspacePath, receipt);
604
611
  }
605
612
  readHydratedWorkspaceReceipt(workspacePath);
606
613
  writeHydratedWorkspaceReceiptCore(workspacePath, receipt);
607
614
  }
608
- async function updateHydratedWorkspaceReceiptAsync(workspacePath, receipt) {
609
- assertCanonicalHydrationReceiptHeads(receipt);
610
- if (!await workspaceCommitsExistAsync(workspacePath, receipt.mounts.map(({ head }) => head))) {
611
- throwMissingHydrationReceiptCommit(workspacePath, receipt);
612
- }
613
- await readHydratedWorkspaceReceiptAsync(workspacePath);
614
- await writeHydratedWorkspaceReceiptCoreAsync(workspacePath, receipt);
615
- }
616
615
  function prepareHydratedWorkspaceReceiptWrite(workspacePath) {
617
616
  const gitDirectory = import_node_path.default.join(workspacePath, ".git");
618
617
  const gitDirectoryStatus = import_node_fs.default.lstatSync(gitDirectory);
@@ -667,17 +666,6 @@ function writeHydratedWorkspaceReceiptCore(workspacePath, receipt) {
667
666
  publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths);
668
667
  updateWorkspaceBasisRefs(workspacePath, receipt);
669
668
  }
670
- async function writeHydratedWorkspaceReceiptCoreAsync(workspacePath, receipt) {
671
- const paths = prepareHydratedWorkspaceReceiptWrite(workspacePath);
672
- const stagingPrefix = `${WORKSPACE_GIT_BASIS_STAGING_REF_PREFIX}${crypto.randomUUID()}/`;
673
- await runWorkspaceRefUpdateBatchAsync(
674
- workspacePath,
675
- stagedWorkspaceBasisRefInstructions(receipt, stagingPrefix),
676
- "stage workspace hydration basis refs"
677
- );
678
- publishHydratedWorkspaceReceiptFile(workspacePath, receipt, paths);
679
- await updateWorkspaceBasisRefsAsync(workspacePath, receipt);
680
- }
681
669
  function hydrationTransactionPath(workspacePath) {
682
670
  return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATION_TRANSACTION.split("/"));
683
671
  }
@@ -772,8 +760,21 @@ function readWorkspaceCheckoutDurabilityRecord(workspacePath) {
772
760
  return unverified.record;
773
761
  }
774
762
  async function readWorkspaceCheckoutDurabilityRecordAsync(workspacePath) {
775
- const unverified = readWorkspaceCheckoutDurabilityRecordUnverified(workspacePath);
776
- if (!unverified) return null;
763
+ const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
764
+ let status;
765
+ try {
766
+ status = await import_node_fs.default.promises.lstat(recordPath);
767
+ } catch (error) {
768
+ if (error.code === "ENOENT") return null;
769
+ throw error;
770
+ }
771
+ if (!status.isFile() || status.isSymbolicLink()) {
772
+ throw new Error(`Workspace checkout durability record is not a regular file: ${recordPath}`);
773
+ }
774
+ const unverified = {
775
+ record: parseWorkspaceCheckoutDurabilityRecordUnverified(recordPath, await import_node_fs.default.promises.readFile(recordPath, "utf8")),
776
+ recordPath
777
+ };
777
778
  if (unverified.record.head && !await tryGitAsync(workspacePath, ["cat-file", "-e", `${unverified.record.head}^{commit}`])) {
778
779
  throw new Error(`Workspace checkout durability record names a missing commit: ${unverified.recordPath}`);
779
780
  }
@@ -800,13 +801,6 @@ function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
800
801
  readWorkspaceCheckoutDurabilityRecord(workspacePath);
801
802
  writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record);
802
803
  }
803
- async function updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, record) {
804
- if (record.head && !await tryGitAsync(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
805
- throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
806
- }
807
- await readWorkspaceCheckoutDurabilityRecordAsync(workspacePath);
808
- writeWorkspaceCheckoutDurabilityRecordFile(workspacePath, record);
809
- }
810
804
  function fsyncWorkspaceCheckoutTree(workspacePath) {
811
805
  const status = import_node_fs.default.lstatSync(workspacePath);
812
806
  if (!status.isDirectory() || status.isSymbolicLink()) {
@@ -818,40 +812,6 @@ function fsyncWorkspaceCheckoutTree(workspacePath) {
818
812
  }
819
813
  fsyncDirectory(workspacePath);
820
814
  }
821
- const WORKSPACE_FSYNC_YIELD_INTERVAL = 64;
822
- async function fsyncWorkspaceCheckoutTreeYielding(workspacePath) {
823
- const status = import_node_fs.default.lstatSync(workspacePath);
824
- if (!status.isDirectory() || status.isSymbolicLink()) {
825
- throw new Error(`Workspace clone root is not a regular directory: ${workspacePath}`);
826
- }
827
- let entriesSinceYield = 0;
828
- const syncEntry = async (entryPath) => {
829
- const entryStatus = import_node_fs.default.lstatSync(entryPath);
830
- if (entryStatus.isSymbolicLink()) return;
831
- if (entryStatus.isDirectory()) {
832
- for (const name of import_node_fs.default.readdirSync(entryPath)) await syncEntry(import_node_path.default.join(entryPath, name));
833
- fsyncDirectory(entryPath);
834
- return;
835
- }
836
- if (!entryStatus.isFile()) return;
837
- const descriptor = import_node_fs.default.openSync(entryPath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
838
- try {
839
- import_node_fs.default.fsyncSync(descriptor);
840
- } finally {
841
- import_node_fs.default.closeSync(descriptor);
842
- }
843
- entriesSinceYield += 1;
844
- if (entriesSinceYield >= WORKSPACE_FSYNC_YIELD_INTERVAL) {
845
- entriesSinceYield = 0;
846
- await yieldToWorkerEventLoop();
847
- }
848
- };
849
- for (const name of import_node_fs.default.readdirSync(workspacePath)) {
850
- if (name === ".git") continue;
851
- await syncEntry(import_node_path.default.join(workspacePath, name));
852
- }
853
- fsyncDirectory(workspacePath);
854
- }
855
815
  function workspaceRebaseInProgress(workspacePath) {
856
816
  const gitDirectory = import_node_path.default.join(workspacePath, ".git");
857
817
  return import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, "rebase-merge")) || import_node_fs.default.existsSync(import_node_path.default.join(gitDirectory, "rebase-apply"));
@@ -920,22 +880,36 @@ async function workspaceCheckoutDurabilityIsCurrentAsync(workspacePath) {
920
880
  const record = await readWorkspaceCheckoutDurabilityRecordAsync(workspacePath);
921
881
  return Boolean(record?.state === "durable" && record.head === await revParseAsync(workspacePath, "HEAD"));
922
882
  }
923
- function beginWorkspaceCheckoutTransition(workspacePath) {
883
+ function runCheckoutTransitionBeginJob(input) {
884
+ const workspacePath = import_node_path.default.resolve(input.workspacePath);
924
885
  recoverWorkspaceCheckoutDurability(workspacePath);
925
886
  const head = revParse(workspacePath, "HEAD");
926
887
  updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "transition", head });
888
+ return null;
889
+ }
890
+ function runCheckoutTransitionCompleteJob(input) {
891
+ const workspacePath = import_node_path.default.resolve(input.workspacePath);
892
+ fsyncWorkspaceCheckoutTree(workspacePath);
893
+ updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "durable", head: revParse(workspacePath, "HEAD") });
894
+ return null;
895
+ }
896
+ function runCheckoutDurabilityRecoveryJob(input) {
897
+ recoverWorkspaceCheckoutDurability(import_node_path.default.resolve(input.workspacePath), input.preserveResolutionInProgress);
898
+ return null;
899
+ }
900
+ function runOuterCheckoutFsyncJob(input) {
901
+ fsyncWorkspaceCheckoutTree(import_node_path.default.resolve(input.workspacePath));
902
+ return null;
903
+ }
904
+ async function beginWorkspaceCheckoutTransition(workspacePath) {
905
+ await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("checkout_transition_begin", { workspacePath });
927
906
  }
928
907
  async function completeWorkspaceCheckoutTransition(workspacePath) {
929
- await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
930
- await updateWorkspaceCheckoutDurabilityRecordAsync(workspacePath, {
931
- version: 1,
932
- state: "durable",
933
- head: await revParseAsync(workspacePath, "HEAD")
934
- });
908
+ await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("checkout_transition_complete", { workspacePath });
935
909
  }
936
- function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
910
+ async function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
937
911
  try {
938
- recoverWorkspaceCheckoutDurability(workspacePath);
912
+ await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("checkout_durability_recovery", { workspacePath, preserveResolutionInProgress: false });
939
913
  } catch (recoveryError) {
940
914
  throw new AggregateError(
941
915
  [error, recoveryError],
@@ -945,12 +919,12 @@ function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
945
919
  throw error;
946
920
  }
947
921
  async function runWorkspaceCheckoutTransition(workspacePath, mutate) {
948
- beginWorkspaceCheckoutTransition(workspacePath);
922
+ await beginWorkspaceCheckoutTransition(workspacePath);
949
923
  try {
950
924
  await mutate();
951
925
  await completeWorkspaceCheckoutTransition(workspacePath);
952
926
  } catch (error) {
953
- throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
927
+ await throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
954
928
  }
955
929
  }
956
930
  function parseHydrationTransactionManifest(transactionPath) {
@@ -1332,8 +1306,16 @@ async function configureWorkspaceRepository(input) {
1332
1306
  await gitAsync(input.workspacePath, ["remote", "add", "origin", input.remoteUrl], "configure workspace origin");
1333
1307
  }
1334
1308
  await tryGitAsync(input.workspacePath, ["config", "--local", "--unset-all", "remote.origin.pushurl"]);
1335
- await gitAsync(input.workspacePath, ["config", "--local", "--replace-all", "credential.helper", ""], "reset workspace credential helpers");
1336
- await gitAsync(input.workspacePath, ["config", "--local", "credential.useHttpPath", "false"], "configure workspace credential path matching");
1309
+ await gitAsync(
1310
+ input.workspacePath,
1311
+ ["config", "--local", "--replace-all", "credential.helper", ""],
1312
+ "reset workspace credential helpers"
1313
+ );
1314
+ await gitAsync(
1315
+ input.workspacePath,
1316
+ ["config", "--local", "credential.useHttpPath", "false"],
1317
+ "configure workspace credential path matching"
1318
+ );
1337
1319
  if (input.credentialUsername) {
1338
1320
  await gitAsync(
1339
1321
  input.workspacePath,
@@ -1431,19 +1413,24 @@ async function ensureWorkspaceGitClone(input) {
1431
1413
  input.credentialHelper,
1432
1414
  input.credentialUsername
1433
1415
  );
1434
- const clone = Bun.spawnSync(cloneCommand, {
1416
+ const clone = Bun.spawn(cloneCommand, {
1417
+ stdin: "ignore",
1435
1418
  stdout: "pipe",
1436
1419
  stderr: "pipe",
1437
1420
  env: (0, import_git_process_environment.workerGitProcessEnvironment)()
1438
1421
  });
1439
- if (clone.exitCode !== 0) {
1422
+ const [, , cloneExitCode] = await Promise.all([new Response(clone.stdout).text(), new Response(clone.stderr).text(), clone.exited]);
1423
+ if (cloneExitCode !== 0) {
1440
1424
  import_node_fs.default.rmSync(workspacePath, { recursive: true, force: true });
1441
1425
  import_node_fs.default.mkdirSync(workspacePath, { recursive: true });
1442
1426
  git(workspacePath, ["init", `--initial-branch=${WORKSPACE_GIT_BRANCH}`], "initialize workspace clone");
1443
1427
  }
1444
1428
  }
1445
1429
  await configureWorkspaceRepository({ ...input, workspacePath });
1446
- recoverWorkspaceCheckoutDurability(workspacePath, input.preserveResolutionInProgress);
1430
+ await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("checkout_durability_recovery", {
1431
+ workspacePath,
1432
+ preserveResolutionInProgress: input.preserveResolutionInProgress === true
1433
+ });
1447
1434
  const previousRemoteHead = await revParseAsync(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
1448
1435
  const localHeadBeforeFetch = await revParseAsync(workspacePath, "HEAD");
1449
1436
  if (!await revParseAsync(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && await tryGitAsync(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
@@ -1722,7 +1709,7 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
1722
1709
  };
1723
1710
  };
1724
1711
  const noopCandidate = computeSelection();
1725
- if (noopCandidate.hydrationMounts.length === 0 && noopCandidate.advancedDurabilityMounts.length === 0 && noopCandidate.targetReceipt && lstatIfExists(hydrationTransactionPath(workspacePath)) === null && workspaceHydrationReceiptsEqual(receiptBeforeTransaction, noopCandidate.targetReceipt) && hydratedWorkspaceReceiptFileIsV3(workspacePath) && await workspaceBasisRefsAreCurrentAsync(workspacePath, noopCandidate.targetReceipt)) {
1712
+ if (noopCandidate.hydrationMounts.length === 0 && noopCandidate.advancedDurabilityMounts.length === 0 && noopCandidate.targetReceipt && lstatIfExists(hydrationTransactionPath(workspacePath)) === null && workspaceHydrationReceiptsEqual(receiptBeforeTransaction, noopCandidate.targetReceipt) && await hydratedWorkspaceReceiptFileIsV3(workspacePath) && await workspaceBasisRefsAreCurrentAsync(workspacePath, noopCandidate.targetReceipt)) {
1726
1713
  return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds };
1727
1714
  }
1728
1715
  const hooks = input.hydrationHooks;
@@ -1735,47 +1722,92 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
1735
1722
  try {
1736
1723
  const selection = computeSelection();
1737
1724
  const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = selection;
1738
- const { manifest, durabilityScopes } = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
1725
+ const heldMountIds = [...new Set([...hydrationMounts, ...advancedDurabilityMounts].map(({ id }) => id))];
1726
+ const releaseHold = hooks?.holdMounts(heldMountIds, `hydration transaction ${context.transactionId}`);
1739
1727
  try {
1740
- const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
1741
- const expectedHydratedIds = hydrationMounts.map(({ id }) => id).sort();
1742
- if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== expectedHydratedIds.length || hydration.hydratedMountIds.some((id, index) => id !== expectedHydratedIds[index])) {
1743
- throw new Error("Workspace hydration did not include every required mount");
1744
- }
1745
- fsyncHydratedWorkspaceMounts(advancedDurabilityMounts, durabilityScopes);
1746
- markHydrationTransactionDurable(workspacePath);
1747
- if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
1748
- removeHydrationTransaction(workspacePath);
1749
- return {
1750
- hydratedMountIds: hydration.hydratedMountIds,
1751
- skippedMountIds: selection.skippedMountIds
1752
- };
1753
- } catch (error) {
1754
- const transactionPath = hydrationTransactionPath(workspacePath);
1755
- if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
1756
- throw error;
1757
- }
1758
- try {
1759
- restoreHydrationTransaction(workspacePath, manifest);
1760
- removeHydrationTransaction(workspacePath);
1761
- } catch (restoreError) {
1762
- throw new AggregateError(
1763
- [error, restoreError],
1764
- `Workspace hydration failed and its durable snapshot could not be restored: ${error instanceof Error ? error.message : String(error)}`
1765
- );
1766
- }
1767
- throw error;
1728
+ const hydration = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("hydration_transaction", {
1729
+ workspacePath,
1730
+ hydrationMounts: hydrationMounts.map(workspaceGitMountData),
1731
+ advancedDurabilityMounts: advancedDurabilityMounts.map(workspaceGitMountData),
1732
+ targetReceipt
1733
+ });
1734
+ return { hydratedMountIds: hydration.hydratedMountIds, skippedMountIds: selection.skippedMountIds };
1735
+ } finally {
1736
+ releaseHold?.();
1768
1737
  }
1769
1738
  } finally {
1770
1739
  hooks?.afterTransaction(context);
1771
1740
  }
1772
1741
  }
1742
+ function runHydrationTransactionJob(input) {
1743
+ const workspacePath = import_node_path.default.resolve(input.workspacePath);
1744
+ const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = input;
1745
+ const { manifest, durabilityScopes } = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
1746
+ try {
1747
+ const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
1748
+ const expectedHydratedIds = hydrationMounts.map(({ id }) => id).sort();
1749
+ if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== expectedHydratedIds.length || hydration.hydratedMountIds.some((id, index) => id !== expectedHydratedIds[index])) {
1750
+ throw new Error("Workspace hydration did not include every required mount");
1751
+ }
1752
+ fsyncHydratedWorkspaceMounts(advancedDurabilityMounts, durabilityScopes);
1753
+ markHydrationTransactionDurable(workspacePath);
1754
+ if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
1755
+ removeHydrationTransaction(workspacePath);
1756
+ return { hydratedMountIds: hydration.hydratedMountIds };
1757
+ } catch (error) {
1758
+ const transactionPath = hydrationTransactionPath(workspacePath);
1759
+ if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
1760
+ throw error;
1761
+ }
1762
+ try {
1763
+ restoreHydrationTransaction(workspacePath, manifest);
1764
+ removeHydrationTransaction(workspacePath);
1765
+ } catch (restoreError) {
1766
+ throw new AggregateError(
1767
+ [error, restoreError],
1768
+ `Workspace hydration failed and its durable snapshot could not be restored: ${error instanceof Error ? error.message : String(error)}`
1769
+ );
1770
+ }
1771
+ throw error;
1772
+ }
1773
+ }
1774
+ function runHydrationRawJob(input) {
1775
+ return {
1776
+ hydratedMountIds: hydrateWorkspaceGitMountsRaw(import_node_path.default.resolve(input.workspacePath), input.mounts, { ignoreBusy: true }).hydratedMountIds
1777
+ };
1778
+ }
1779
+ function runHydrationRecoveryJob(input) {
1780
+ const workspacePath = import_node_path.default.resolve(input.workspacePath);
1781
+ recoverWorkspaceCheckoutDurability(workspacePath, input.preserveResolutionInProgress);
1782
+ recoverPendingHydrationTransaction(workspacePath);
1783
+ repairWorkspaceBasisRefs(workspacePath, readHydratedWorkspaceReceipt(workspacePath));
1784
+ return null;
1785
+ }
1773
1786
  async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
1774
- const gitDirectory = import_node_path.default.join(import_node_path.default.resolve(workspacePath), ".git");
1787
+ const resolvedWorkspacePath = import_node_path.default.resolve(workspacePath);
1788
+ const gitDirectory = import_node_path.default.join(resolvedWorkspacePath, ".git");
1775
1789
  if (!import_node_fs.default.existsSync(gitDirectory)) {
1776
- return hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options);
1790
+ const skippedMountIds = options.ignoreBusy ? [] : mounts.filter((mount) => mount.busy?.()).map(({ id }) => id).sort();
1791
+ const skipped = new Set(skippedMountIds);
1792
+ const selected = mounts.filter(({ id }) => !skipped.has(id));
1793
+ const releaseHold = options.hydrationHooks?.holdMounts(
1794
+ selected.map(({ id }) => id),
1795
+ "pre-clone hydration"
1796
+ );
1797
+ try {
1798
+ const hydration = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("hydration_raw", {
1799
+ workspacePath: resolvedWorkspacePath,
1800
+ mounts: selected.map(workspaceGitMountData)
1801
+ });
1802
+ return { hydratedMountIds: hydration.hydratedMountIds, skippedMountIds };
1803
+ } finally {
1804
+ releaseHold?.();
1805
+ }
1777
1806
  }
1778
- recoverWorkspaceCheckoutDurability(import_node_path.default.resolve(workspacePath));
1807
+ await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("checkout_durability_recovery", {
1808
+ workspacePath: resolvedWorkspacePath,
1809
+ preserveResolutionInProgress: false
1810
+ });
1779
1811
  return hydrateWorkspaceGitMountsTransactionally({
1780
1812
  workspacePath,
1781
1813
  mounts,
@@ -1786,13 +1818,12 @@ async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
1786
1818
  async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
1787
1819
  workspacePath = import_node_path.default.resolve(workspacePath);
1788
1820
  validateMounts(workspacePath, mounts);
1789
- recoverWorkspaceCheckoutDurability(workspacePath, options.preserveResolutionInProgress);
1790
- recoverPendingHydrationTransaction(workspacePath);
1821
+ await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("hydration_recovery", {
1822
+ workspacePath,
1823
+ preserveResolutionInProgress: options.preserveResolutionInProgress === true
1824
+ });
1791
1825
  const currentHead = await revParseAsync(workspacePath, "HEAD");
1792
1826
  const currentReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
1793
- if (!await workspaceBasisRefsAreCurrentAsync(workspacePath, currentReceipt ?? { version: 3, mounts: [] })) {
1794
- repairWorkspaceBasisRefs(workspacePath, currentReceipt);
1795
- }
1796
1827
  if (!currentHead) return;
1797
1828
  const outerHeadContainsMount = (mount) => tryGitAsync(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
1798
1829
  const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
@@ -1833,13 +1864,17 @@ async function resetWorkspaceGit(input) {
1833
1864
  throw new Error(`Workspace reset cannot run while mounts are busy: ${busyResetMountIds.sort().join(", ")}`);
1834
1865
  }
1835
1866
  const initial = await ensureWorkspaceGitClone({ ...input, workspacePath });
1836
- recoverPendingHydrationTransaction(workspacePath);
1867
+ await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("hydration_recovery", { workspacePath, preserveResolutionInProgress: false });
1837
1868
  const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
1838
1869
  const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
1839
1870
  const remoteHead = await fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
1840
1871
  if (remoteHead) {
1841
1872
  await runWorkspaceCheckoutTransition(workspacePath, async () => {
1842
- await gitAsync(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
1873
+ await gitAsync(
1874
+ workspacePath,
1875
+ ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead],
1876
+ "reset workspace main"
1877
+ );
1843
1878
  await gitAsync(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
1844
1879
  });
1845
1880
  } else if (revParse(workspacePath, "HEAD")) {
@@ -2050,7 +2085,7 @@ function snapshotConflict(input) {
2050
2085
  }
2051
2086
  async function rebaseWorkspace(input) {
2052
2087
  const remoteRevision = `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`;
2053
- beginWorkspaceCheckoutTransition(input.workspacePath);
2088
+ await beginWorkspaceCheckoutTransition(input.workspacePath);
2054
2089
  const rebase = await gitResultAsync(
2055
2090
  input.workspacePath,
2056
2091
  input.truncatedHistoryBase ? ["rebase", "--onto", remoteRevision, input.truncatedHistoryBase] : ["rebase", remoteRevision]
@@ -2220,18 +2255,24 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
2220
2255
  cycleSkippedMountIds.add(mount.id);
2221
2256
  continue;
2222
2257
  }
2223
- const merged = (0, import_workspace_merge_projection.mergeWorkspaceProjectionMount)({
2224
- workspacePath,
2225
- mount: {
2226
- id: mount.id,
2227
- sourcePath: mount.sourcePath,
2228
- workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
2229
- sourceMode: mount.sourceMode
2230
- },
2231
- basisHead,
2232
- currentHead: projectionBasisHead,
2233
- attemptId
2234
- });
2258
+ const releaseMergeHold = input.hydrationHooks?.holdMounts([mount.id], `merge projection of mount ${mount.id}`);
2259
+ let merged;
2260
+ try {
2261
+ merged = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("projection_merge", {
2262
+ workspacePath,
2263
+ mount: {
2264
+ id: mount.id,
2265
+ sourcePath: mount.sourcePath,
2266
+ workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
2267
+ sourceMode: mount.sourceMode
2268
+ },
2269
+ basisHead,
2270
+ currentHead: projectionBasisHead,
2271
+ attemptId
2272
+ });
2273
+ } finally {
2274
+ releaseMergeHold?.();
2275
+ }
2235
2276
  if (merged.kind === "conflict") {
2236
2277
  const refs = snapshotConflict({ workspacePath, attemptId, localHead: merged.oursCommit, remoteHead: projectionBasisHead });
2237
2278
  return {
@@ -2254,7 +2295,6 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
2254
2295
  }
2255
2296
  mergedProjectionTrees.set(mount.id, merged.resultTree);
2256
2297
  mergedProjectionMounts.push(mount);
2257
- await yieldToWorkerEventLoop();
2258
2298
  }
2259
2299
  }
2260
2300
  if (projectionWarning) process.stderr.write(`[r5d-worker] ${projectionWarning}
@@ -2337,7 +2377,7 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
2337
2377
  const projectedFastMounts = [];
2338
2378
  const currentProjectionMounts = [];
2339
2379
  if (!input.skipMountMirror) {
2340
- beginWorkspaceCheckoutTransition(workspacePath);
2380
+ await beginWorkspaceCheckoutTransition(workspacePath);
2341
2381
  try {
2342
2382
  const selectedActiveIds = new Set(selected.active.map(({ id }) => id));
2343
2383
  const fastActiveMounts = fastProjectionMounts.filter(({ id }) => selectedActiveIds.has(id));
@@ -2351,30 +2391,34 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
2351
2391
  currentProjectionMounts.push(mount);
2352
2392
  continue;
2353
2393
  }
2354
- projectedGitlinks.push(...mirrorMountsToWorkspace(workspacePath, [mount]));
2394
+ const releaseMirrorHold = input.hydrationHooks?.holdMounts([mount.id], `projection of mount ${mount.id}`);
2395
+ try {
2396
+ const mirrored = await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("projection_mirror", {
2397
+ workspacePath,
2398
+ mount: workspaceGitMountData(mount)
2399
+ });
2400
+ projectedGitlinks.push({ mount, gitlinks: mirrored.gitlinks });
2401
+ } finally {
2402
+ releaseMirrorHold?.();
2403
+ }
2355
2404
  projectedFastMounts.push(mount);
2356
- await yieldToWorkerEventLoop();
2357
2405
  }
2358
- removeWorkspaceMounts(workspacePath, fastTombstones);
2359
- projectedFastMounts.push(...fastTombstones);
2360
- for (const mount of mergedProjectionMounts) {
2361
- (0, import_workspace_merge_projection.materializeWorkspaceProjectionTree)({
2362
- workspacePath,
2406
+ await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("projection_outer_apply", {
2407
+ workspacePath,
2408
+ removeMounts: fastTombstones.map(workspaceGitMountData),
2409
+ materialize: mergedProjectionMounts.map((mount) => ({
2363
2410
  workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
2364
2411
  resultTree: mergedProjectionTrees.get(mount.id)
2365
- });
2366
- await yieldToWorkerEventLoop();
2367
- }
2368
- restoreDeferredWorkspaceMountsFromHead(
2369
- workspacePath,
2370
- input.mounts.filter(
2412
+ })),
2413
+ restoreFromHead: input.mounts.filter(
2371
2414
  (mount) => deferredMountIds.has(mount.id) || mount.preserveLocalOnHydrationBasisChange === true && cycleSkippedMountIds.has(mount.id)
2372
- )
2373
- );
2415
+ ).map(workspaceGitMountData)
2416
+ });
2417
+ projectedFastMounts.push(...fastTombstones);
2374
2418
  await stageAndCommitWorkspace();
2375
- await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
2419
+ await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("outer_checkout_fsync", { workspacePath });
2376
2420
  } catch (error) {
2377
- throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
2421
+ await throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
2378
2422
  }
2379
2423
  } else {
2380
2424
  await stageAndCommitWorkspace();
@@ -2390,12 +2434,12 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
2390
2434
  allProjectionReceiptMounts
2391
2435
  );
2392
2436
  const currentProjectionReceipt = await readHydratedWorkspaceReceiptAsync(workspacePath);
2393
- if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !hydratedWorkspaceReceiptFileIsV3(workspacePath) || !await workspaceBasisRefsAreCurrentAsync(workspacePath, targetProjectionReceipt)) {
2394
- for (const mount of projectedFastMounts) {
2395
- fsyncHydratedWorkspaceMount(mount);
2396
- await yieldToWorkerEventLoop();
2397
- }
2398
- await updateHydratedWorkspaceReceiptAsync(workspacePath, targetProjectionReceipt);
2437
+ if (!workspaceHydrationReceiptsEqual(currentProjectionReceipt, targetProjectionReceipt) || !await hydratedWorkspaceReceiptFileIsV3(workspacePath) || !await workspaceBasisRefsAreCurrentAsync(workspacePath, targetProjectionReceipt)) {
2438
+ await (0, import_workspace_filesystem_executor.workspaceFilesystemExecutor)().run("projection_receipt", {
2439
+ workspacePath,
2440
+ fsyncMounts: projectedFastMounts.map(workspaceGitMountData),
2441
+ receipt: targetProjectionReceipt
2442
+ });
2399
2443
  }
2400
2444
  await completeWorkspaceCheckoutTransition(workspacePath);
2401
2445
  }
@@ -2562,6 +2606,25 @@ ${push.stdout}`)) {
2562
2606
  }
2563
2607
  throw new Error(`Workspace push did not converge after ${maxPushAttempts} attempts`);
2564
2608
  }
2609
+ function runProjectionMirrorJob(input) {
2610
+ const projected = mirrorMountsToWorkspace(import_node_path.default.resolve(input.workspacePath), [input.mount]);
2611
+ return { gitlinks: projected[0]?.gitlinks ?? [] };
2612
+ }
2613
+ function runProjectionOuterApplyJob(input) {
2614
+ const workspacePath = import_node_path.default.resolve(input.workspacePath);
2615
+ removeWorkspaceMounts(workspacePath, input.removeMounts);
2616
+ for (const { workspaceRelativePath, resultTree } of input.materialize) {
2617
+ (0, import_workspace_merge_projection.materializeWorkspaceProjectionTree)({ workspacePath, workspaceRelativePath, resultTree });
2618
+ }
2619
+ restoreDeferredWorkspaceMountsFromHead(workspacePath, input.restoreFromHead);
2620
+ return null;
2621
+ }
2622
+ function runProjectionReceiptJob(input) {
2623
+ const workspacePath = import_node_path.default.resolve(input.workspacePath);
2624
+ for (const mount of input.fsyncMounts) fsyncHydratedWorkspaceMount(mount);
2625
+ updateHydratedWorkspaceReceipt(workspacePath, input.receipt);
2626
+ return null;
2627
+ }
2565
2628
  const workspaceGitSyncTestHarness = {
2566
2629
  commandArgs: gitCommandArgs,
2567
2630
  workspaceCloneCommandArgs,
@@ -2583,9 +2646,20 @@ const workspaceGitSyncTestHarness = {
2583
2646
  hydrateWorkspaceGitMounts,
2584
2647
  recoverWorkspaceGitHydration,
2585
2648
  resetWorkspaceGit,
2649
+ runCheckoutDurabilityRecoveryJob,
2650
+ runCheckoutTransitionBeginJob,
2651
+ runCheckoutTransitionCompleteJob,
2652
+ runHydrationRawJob,
2653
+ runHydrationRecoveryJob,
2654
+ runHydrationTransactionJob,
2655
+ runOuterCheckoutFsyncJob,
2656
+ runProjectionMirrorJob,
2657
+ runProjectionOuterApplyJob,
2658
+ runProjectionReceiptJob,
2586
2659
  synchronizeWorkspaceGit,
2587
2660
  workspaceGitHydrationIsCurrent,
2588
2661
  workspaceGitHydrationIsRecoverable,
2662
+ workspaceGitMountData,
2589
2663
  workspaceGitMountsForRemediation,
2590
2664
  workspaceGitSyncTestHarness
2591
2665
  });