@ricsam/r5d-worker 0.0.78 → 0.0.80

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 (58) hide show
  1. package/README.md +6 -0
  2. package/dist/cjs/main.cjs +1550 -335
  3. package/dist/cjs/managed-paths.cjs +101 -1
  4. package/dist/cjs/package.json +1 -1
  5. package/dist/cjs/project-workspace-state.cjs +184 -5
  6. package/dist/cjs/project-worktrees.cjs +676 -58
  7. package/dist/cjs/registry-auth.cjs +310 -0
  8. package/dist/cjs/repository-transition-policy.cjs +49 -0
  9. package/dist/cjs/supervisor.cjs +62 -11
  10. package/dist/cjs/working-tree-mirror.cjs +196 -36
  11. package/dist/cjs/workspace-automatic-sync-policy.cjs +69 -0
  12. package/dist/cjs/workspace-branch-incarnation-policy.cjs +37 -0
  13. package/dist/cjs/workspace-command-sync-policy.cjs +18 -0
  14. package/dist/cjs/workspace-git-sync.cjs +1037 -54
  15. package/dist/cjs/workspace-mount-boundary.cjs +71 -0
  16. package/dist/cjs/workspace-path-move.cjs +195 -0
  17. package/dist/cjs/workspace-preserve-only-policy.cjs +36 -0
  18. package/dist/cjs/workspace-project-config-policy.cjs +46 -0
  19. package/dist/cjs/workspace-publication-evidence.cjs +46 -0
  20. package/dist/mjs/main.mjs +1584 -341
  21. package/dist/mjs/managed-paths.mjs +100 -1
  22. package/dist/mjs/package.json +1 -1
  23. package/dist/mjs/project-workspace-state.mjs +181 -5
  24. package/dist/mjs/project-worktrees.mjs +667 -57
  25. package/dist/mjs/registry-auth.mjs +269 -0
  26. package/dist/mjs/repository-transition-policy.mjs +22 -0
  27. package/dist/mjs/supervisor.mjs +61 -11
  28. package/dist/mjs/working-tree-mirror.mjs +195 -36
  29. package/dist/mjs/workspace-automatic-sync-policy.mjs +40 -0
  30. package/dist/mjs/workspace-branch-incarnation-policy.mjs +13 -0
  31. package/dist/mjs/workspace-command-sync-policy.mjs +17 -0
  32. package/dist/mjs/workspace-git-sync.mjs +1032 -54
  33. package/dist/mjs/workspace-mount-boundary.mjs +37 -0
  34. package/dist/mjs/workspace-path-move.mjs +160 -0
  35. package/dist/mjs/workspace-preserve-only-policy.mjs +11 -0
  36. package/dist/mjs/workspace-project-config-policy.mjs +21 -0
  37. package/dist/mjs/workspace-publication-evidence.mjs +21 -0
  38. package/dist/types/credential-authority-lock-fixture.d.ts +1 -0
  39. package/dist/types/main.d.ts +175 -1
  40. package/dist/types/managed-paths.d.ts +11 -0
  41. package/dist/types/project-workspace-state.d.ts +45 -1
  42. package/dist/types/project-worktrees.d.ts +119 -2
  43. package/dist/types/registry-auth.d.ts +47 -0
  44. package/dist/types/repository-transition-policy.d.ts +26 -0
  45. package/dist/types/supervisor-daemonized-fixture.d.ts +1 -0
  46. package/dist/types/supervisor-signal-fixture.d.ts +1 -0
  47. package/dist/types/supervisor.d.ts +6 -4
  48. package/dist/types/working-tree-mirror.d.ts +14 -0
  49. package/dist/types/workspace-automatic-sync-policy.d.ts +37 -0
  50. package/dist/types/workspace-branch-incarnation-policy.d.ts +14 -0
  51. package/dist/types/workspace-command-sync-policy.d.ts +9 -0
  52. package/dist/types/workspace-git-sync.d.ts +33 -3
  53. package/dist/types/workspace-mount-boundary.d.ts +10 -0
  54. package/dist/types/workspace-path-move.d.ts +21 -0
  55. package/dist/types/workspace-preserve-only-policy.d.ts +15 -0
  56. package/dist/types/workspace-project-config-policy.d.ts +34 -0
  57. package/dist/types/workspace-publication-evidence.d.ts +17 -0
  58. package/package.json +1 -1
@@ -30,11 +30,16 @@ var workspace_git_sync_exports = {};
30
30
  __export(workspace_git_sync_exports, {
31
31
  MAX_WORKSPACE_GIT_DIFF_BYTES: () => MAX_WORKSPACE_GIT_DIFF_BYTES,
32
32
  WORKSPACE_GIT_BRANCH: () => WORKSPACE_GIT_BRANCH,
33
+ WORKSPACE_GIT_CHECKOUT_DURABILITY: () => WORKSPACE_GIT_CHECKOUT_DURABILITY,
33
34
  WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION: () => WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION,
35
+ WORKSPACE_GIT_HYDRATED_RECEIPT: () => WORKSPACE_GIT_HYDRATED_RECEIPT,
36
+ WORKSPACE_GIT_HYDRATION_TRANSACTION: () => WORKSPACE_GIT_HYDRATION_TRANSACTION,
34
37
  ensureWorkspaceGitClone: () => ensureWorkspaceGitClone,
35
38
  hydrateWorkspaceGitMounts: () => hydrateWorkspaceGitMounts,
39
+ recoverWorkspaceGitHydration: () => recoverWorkspaceGitHydration,
36
40
  resetWorkspaceGit: () => resetWorkspaceGit,
37
41
  synchronizeWorkspaceGit: () => synchronizeWorkspaceGit,
42
+ workspaceGitHydrationIsCurrent: () => workspaceGitHydrationIsCurrent,
38
43
  workspaceGitSyncTestHarness: () => workspaceGitSyncTestHarness
39
44
  });
40
45
  module.exports = __toCommonJS(workspace_git_sync_exports);
@@ -42,10 +47,14 @@ var import_node_fs = __toESM(require("node:fs"), 1);
42
47
  var import_node_path = __toESM(require("node:path"), 1);
43
48
  var import_git_process_environment = require("./git-process-environment.cjs");
44
49
  var import_working_tree_mirror = require("./working-tree-mirror.cjs");
50
+ var import_workspace_mount_boundary = require("./workspace-mount-boundary.cjs");
45
51
  const WORKSPACE_GIT_BRANCH = "main";
46
52
  const MAX_WORKSPACE_GIT_DIFF_BYTES = 5 * 1024 * 1024;
47
53
  const WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION = "r5d-confirm-large-diff-v1";
48
54
  const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
55
+ const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
56
+ const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
57
+ const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
49
58
  const NON_RECURSIVE_GIT_CONFIG = [
50
59
  "-c",
51
60
  "submodule.recurse=false",
@@ -88,6 +97,640 @@ function revParse(workspacePath, revision) {
88
97
  function updateIntegratedWorkspaceHead(workspacePath, head) {
89
98
  git(workspacePath, ["update-ref", WORKSPACE_GIT_INTEGRATED_REF, head], "record integrated workspace head");
90
99
  }
100
+ function fsyncDirectory(directoryPath) {
101
+ const descriptor = import_node_fs.default.openSync(directoryPath, import_node_fs.default.constants.O_RDONLY);
102
+ try {
103
+ import_node_fs.default.fsyncSync(descriptor);
104
+ } finally {
105
+ import_node_fs.default.closeSync(descriptor);
106
+ }
107
+ }
108
+ function lstatIfExists(targetPath) {
109
+ try {
110
+ return import_node_fs.default.lstatSync(targetPath);
111
+ } catch (error) {
112
+ if (error.code === "ENOENT") return null;
113
+ throw error;
114
+ }
115
+ }
116
+ function hydratedWorkspaceReceiptPath(workspacePath) {
117
+ return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATED_RECEIPT.split("/"));
118
+ }
119
+ function workspaceCheckoutDurabilityPath(workspacePath) {
120
+ return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_CHECKOUT_DURABILITY.split("/"));
121
+ }
122
+ function normalizedWorkspaceMountPath(workspaceRelativePath) {
123
+ return workspaceRelativePath.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
124
+ }
125
+ function workspaceHydrationMountBasis(mount) {
126
+ return {
127
+ id: mount.id,
128
+ incarnationKey: mount.hydrationIncarnationKey,
129
+ sourcePath: import_node_path.default.resolve(mount.sourcePath),
130
+ durabilityRootPath: import_node_path.default.resolve(mount.durabilityRootPath),
131
+ workspaceRelativePath: normalizedWorkspaceMountPath(mount.workspaceRelativePath),
132
+ sourceMode: mount.sourceMode,
133
+ hydrateDeletionMode: mount.hydrateDeletionMode,
134
+ preserveLocalOnInitialOuterAbsence: mount.preserveLocalOnInitialOuterAbsence === true,
135
+ preserveLocalOnHydrationBasisChange: mount.preserveLocalOnHydrationBasisChange === true,
136
+ deleteWhenSourceMissing: mount.deleteWhenSourceMissing === true
137
+ };
138
+ }
139
+ function compareWorkspaceHydrationMountBasis(left, right) {
140
+ return left.id.localeCompare(right.id);
141
+ }
142
+ function workspaceHydrationReceipt(head, mounts) {
143
+ const bases = mounts.map(workspaceHydrationMountBasis).sort(compareWorkspaceHydrationMountBasis);
144
+ const ids = /* @__PURE__ */ new Set();
145
+ for (const basis of bases) {
146
+ if (ids.has(basis.id)) throw new Error(`Workspace hydration receipt mount id must be unique: ${basis.id}`);
147
+ ids.add(basis.id);
148
+ }
149
+ return { version: 2, head, mounts: bases };
150
+ }
151
+ function workspaceHydrationReceiptsEqual(left, right) {
152
+ return JSON.stringify(left) === JSON.stringify(right);
153
+ }
154
+ function workspaceHydrationReceiptCoversMount(receipt, head, mount) {
155
+ if (!receipt || receipt.head !== head) return false;
156
+ const expected = workspaceHydrationMountBasis(mount);
157
+ const actual = receipt.mounts.find(({ id }) => id === expected.id);
158
+ return Boolean(actual && JSON.stringify(actual) === JSON.stringify(expected));
159
+ }
160
+ function workspaceHydrationReceiptMatchesMounts(receipt, head, mounts) {
161
+ return workspaceHydrationReceiptsEqual(receipt, workspaceHydrationReceipt(head, mounts));
162
+ }
163
+ function configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, deferMountIds) {
164
+ const resolvedWorkspacePath = import_node_path.default.resolve(workspacePath);
165
+ return mounts.filter((mount) => {
166
+ if (deferMountIds?.has(mount.id)) return false;
167
+ if (mount.deleteWhenSourceMissing) return !import_node_fs.default.existsSync(mount.sourcePath);
168
+ const outerPath = import_node_path.default.join(resolvedWorkspacePath, ...normalizedWorkspaceMountPath(mount.workspaceRelativePath).split("/"));
169
+ return import_node_fs.default.existsSync(mount.sourcePath) || import_node_fs.default.existsSync(outerPath) || mount.sourceMode === "all";
170
+ });
171
+ }
172
+ function parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content) {
173
+ const legacyHead = content.endsWith("\n") ? content.slice(0, -1) : "";
174
+ if (/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(legacyHead) && content === `${legacyHead}
175
+ `) {
176
+ if (!tryGit(workspacePath, ["cat-file", "-e", `${legacyHead}^{commit}`])) {
177
+ throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
178
+ }
179
+ return { version: 2, head: legacyHead, mounts: [] };
180
+ }
181
+ let parsed;
182
+ try {
183
+ parsed = JSON.parse(content);
184
+ } catch {
185
+ throw new Error(`Workspace hydration receipt contains an invalid object id or mount basis: ${receiptPath}`);
186
+ }
187
+ if (!parsed || typeof parsed !== "object") {
188
+ throw new Error(`Workspace hydration receipt contains an invalid object id or mount basis: ${receiptPath}`);
189
+ }
190
+ const candidate = parsed;
191
+ if (candidate.version !== 2 || typeof candidate.head !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(candidate.head)) {
192
+ throw new Error(`Workspace hydration receipt contains an invalid object id or mount basis: ${receiptPath}`);
193
+ }
194
+ if (!Array.isArray(candidate.mounts)) {
195
+ throw new Error(`Workspace hydration receipt contains an invalid mount basis: ${receiptPath}`);
196
+ }
197
+ const seenIds = /* @__PURE__ */ new Set();
198
+ const mounts = candidate.mounts.map((rawMount) => {
199
+ if (!rawMount || typeof rawMount !== "object") {
200
+ throw new Error(`Workspace hydration receipt contains an invalid mount basis: ${receiptPath}`);
201
+ }
202
+ const mount = rawMount;
203
+ const normalizedRelativePath = typeof mount.workspaceRelativePath === "string" ? normalizedWorkspaceMountPath(mount.workspaceRelativePath) : "";
204
+ const durabilityRelative = typeof mount.sourcePath === "string" && typeof mount.durabilityRootPath === "string" ? import_node_path.default.relative(mount.durabilityRootPath, mount.sourcePath) : "..";
205
+ if (typeof mount.id !== "string" || !mount.id || seenIds.has(mount.id) || typeof mount.incarnationKey !== "string" || !mount.incarnationKey || mount.incarnationKey.includes("\0") || typeof mount.sourcePath !== "string" || import_node_path.default.resolve(mount.sourcePath) !== mount.sourcePath || typeof mount.durabilityRootPath !== "string" || import_node_path.default.resolve(mount.durabilityRootPath) !== mount.durabilityRootPath || !durabilityRelative || durabilityRelative === ".." || durabilityRelative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(durabilityRelative) || typeof mount.workspaceRelativePath !== "string" || mount.workspaceRelativePath !== normalizedRelativePath || !normalizedRelativePath || normalizedRelativePath === "." || normalizedRelativePath.split("/").some((segment) => segment === ".." || segment === ".git") || mount.sourceMode !== "all" && mount.sourceMode !== "git" || mount.hydrateDeletionMode !== "all" && mount.hydrateDeletionMode !== "git" || typeof mount.preserveLocalOnInitialOuterAbsence !== "boolean" || typeof mount.preserveLocalOnHydrationBasisChange !== "boolean" || typeof mount.deleteWhenSourceMissing !== "boolean") {
206
+ throw new Error(`Workspace hydration receipt contains an invalid mount basis: ${receiptPath}`);
207
+ }
208
+ seenIds.add(mount.id);
209
+ return mount;
210
+ });
211
+ const receipt = {
212
+ version: 2,
213
+ head: candidate.head,
214
+ mounts: mounts.sort(compareWorkspaceHydrationMountBasis)
215
+ };
216
+ if (content !== `${JSON.stringify(receipt)}
217
+ `) {
218
+ throw new Error(`Workspace hydration receipt contains a noncanonical mount basis: ${receiptPath}`);
219
+ }
220
+ if (!tryGit(workspacePath, ["cat-file", "-e", `${receipt.head}^{commit}`])) {
221
+ throw new Error(`Workspace hydration receipt does not resolve to a local commit: ${receiptPath}`);
222
+ }
223
+ return receipt;
224
+ }
225
+ function readHydratedWorkspaceReceipt(workspacePath) {
226
+ const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
227
+ const status = lstatIfExists(receiptPath);
228
+ if (!status) return null;
229
+ if (!status.isFile() || status.isSymbolicLink()) {
230
+ throw new Error(`Workspace hydration receipt is not a regular file: ${receiptPath}`);
231
+ }
232
+ const content = import_node_fs.default.readFileSync(receiptPath, "utf8");
233
+ return parseWorkspaceHydrationReceiptContent(workspacePath, receiptPath, content);
234
+ }
235
+ function workspaceGitHydrationIsCurrent(workspacePath, mounts) {
236
+ const resolvedWorkspacePath = import_node_path.default.resolve(workspacePath);
237
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(resolvedWorkspacePath, ".git"))) return true;
238
+ if (mounts) validateMounts(resolvedWorkspacePath, mounts);
239
+ const head = revParse(resolvedWorkspacePath, "HEAD");
240
+ if (!workspaceCheckoutDurabilityIsCurrent(resolvedWorkspacePath)) return false;
241
+ if (head === null) return true;
242
+ const receipt = readHydratedWorkspaceReceipt(resolvedWorkspacePath);
243
+ return mounts ? workspaceHydrationReceiptMatchesMounts(receipt, head, configuredWorkspaceHydrationBasisMounts(resolvedWorkspacePath, mounts)) : receipt?.head === head;
244
+ }
245
+ function updateHydratedWorkspaceReceipt(workspacePath, receipt) {
246
+ if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(receipt.head)) {
247
+ throw new Error(`Cannot record invalid fully hydrated workspace object id: ${receipt.head}`);
248
+ }
249
+ if (!tryGit(workspacePath, ["cat-file", "-e", `${receipt.head}^{commit}`])) {
250
+ throw new Error(`Cannot record workspace hydration receipt for a missing commit: ${receipt.head}`);
251
+ }
252
+ readHydratedWorkspaceReceipt(workspacePath);
253
+ const gitDirectory = import_node_path.default.join(workspacePath, ".git");
254
+ const gitDirectoryStatus = import_node_fs.default.lstatSync(gitDirectory);
255
+ if (!gitDirectoryStatus.isDirectory() || gitDirectoryStatus.isSymbolicLink()) {
256
+ throw new Error(`Workspace Git directory is not a regular directory: ${gitDirectory}`);
257
+ }
258
+ const receiptPath = hydratedWorkspaceReceiptPath(workspacePath);
259
+ const receiptDirectory = import_node_path.default.dirname(receiptPath);
260
+ const existingReceiptDirectory = lstatIfExists(receiptDirectory);
261
+ if (!existingReceiptDirectory) {
262
+ import_node_fs.default.mkdirSync(receiptDirectory, { recursive: false, mode: 448 });
263
+ fsyncDirectory(gitDirectory);
264
+ }
265
+ const receiptDirectoryStatus = import_node_fs.default.lstatSync(receiptDirectory);
266
+ if (!receiptDirectoryStatus.isDirectory() || receiptDirectoryStatus.isSymbolicLink()) {
267
+ throw new Error(`Workspace hydration receipt directory is not a regular directory: ${receiptDirectory}`);
268
+ }
269
+ const temporaryPath = import_node_path.default.join(receiptDirectory, `.workspace-hydrated-head.${process.pid}.${crypto.randomUUID()}.tmp`);
270
+ let descriptor;
271
+ try {
272
+ descriptor = import_node_fs.default.openSync(temporaryPath, import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_CREAT | import_node_fs.default.constants.O_EXCL, 384);
273
+ import_node_fs.default.writeFileSync(descriptor, `${JSON.stringify(receipt)}
274
+ `, "utf8");
275
+ import_node_fs.default.fsyncSync(descriptor);
276
+ import_node_fs.default.closeSync(descriptor);
277
+ descriptor = void 0;
278
+ import_node_fs.default.renameSync(temporaryPath, receiptPath);
279
+ fsyncDirectory(receiptDirectory);
280
+ } catch (error) {
281
+ if (descriptor !== void 0) import_node_fs.default.closeSync(descriptor);
282
+ import_node_fs.default.rmSync(temporaryPath, { force: true });
283
+ throw error;
284
+ }
285
+ }
286
+ function hydrationTransactionPath(workspacePath) {
287
+ return import_node_path.default.join(workspacePath, ".git", ...WORKSPACE_GIT_HYDRATION_TRANSACTION.split("/"));
288
+ }
289
+ function workspacePrivateStateDirectory(workspacePath) {
290
+ const gitDirectory = import_node_path.default.join(workspacePath, ".git");
291
+ const gitDirectoryStatus = import_node_fs.default.lstatSync(gitDirectory);
292
+ if (!gitDirectoryStatus.isDirectory() || gitDirectoryStatus.isSymbolicLink()) {
293
+ throw new Error(`Workspace Git directory is not a regular directory: ${gitDirectory}`);
294
+ }
295
+ const stateDirectory = import_node_path.default.dirname(hydrationTransactionPath(workspacePath));
296
+ const existing = lstatIfExists(stateDirectory);
297
+ if (!existing) {
298
+ import_node_fs.default.mkdirSync(stateDirectory, { recursive: false, mode: 448 });
299
+ fsyncDirectory(gitDirectory);
300
+ } else if (!existing.isDirectory() || existing.isSymbolicLink()) {
301
+ throw new Error(`Workspace Git private state is not a regular directory: ${stateDirectory}`);
302
+ }
303
+ return stateDirectory;
304
+ }
305
+ function writePrivateFileDurably(filePath, content) {
306
+ const descriptor = import_node_fs.default.openSync(
307
+ filePath,
308
+ import_node_fs.default.constants.O_WRONLY | import_node_fs.default.constants.O_CREAT | import_node_fs.default.constants.O_EXCL | import_node_fs.default.constants.O_NOFOLLOW,
309
+ 384
310
+ );
311
+ try {
312
+ import_node_fs.default.writeFileSync(descriptor, content, "utf8");
313
+ import_node_fs.default.fsyncSync(descriptor);
314
+ } finally {
315
+ import_node_fs.default.closeSync(descriptor);
316
+ }
317
+ }
318
+ function fsyncTree(rootPath) {
319
+ const syncEntry = (entryPath) => {
320
+ const status = import_node_fs.default.lstatSync(entryPath);
321
+ if (status.isSymbolicLink()) return;
322
+ if (status.isDirectory()) {
323
+ for (const name of import_node_fs.default.readdirSync(entryPath)) syncEntry(import_node_path.default.join(entryPath, name));
324
+ fsyncDirectory(entryPath);
325
+ return;
326
+ }
327
+ if (!status.isFile()) return;
328
+ const descriptor = import_node_fs.default.openSync(entryPath, import_node_fs.default.constants.O_RDONLY | import_node_fs.default.constants.O_NOFOLLOW);
329
+ try {
330
+ import_node_fs.default.fsyncSync(descriptor);
331
+ } finally {
332
+ import_node_fs.default.closeSync(descriptor);
333
+ }
334
+ };
335
+ syncEntry(rootPath);
336
+ }
337
+ function parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, content) {
338
+ let parsed;
339
+ try {
340
+ parsed = JSON.parse(content);
341
+ } catch {
342
+ throw new Error(`Workspace checkout durability record is invalid: ${recordPath}`);
343
+ }
344
+ if (!parsed || typeof parsed !== "object") {
345
+ throw new Error(`Workspace checkout durability record is invalid: ${recordPath}`);
346
+ }
347
+ const candidate = parsed;
348
+ if (candidate.version !== 1 || candidate.state !== "durable" && candidate.state !== "transition" || candidate.head !== null && (typeof candidate.head !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(candidate.head))) {
349
+ throw new Error(`Workspace checkout durability record is invalid: ${recordPath}`);
350
+ }
351
+ const record = {
352
+ version: 1,
353
+ state: candidate.state,
354
+ head: candidate.head
355
+ };
356
+ if (content !== `${JSON.stringify(record)}
357
+ `) {
358
+ throw new Error(`Workspace checkout durability record is noncanonical: ${recordPath}`);
359
+ }
360
+ if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
361
+ throw new Error(`Workspace checkout durability record names a missing commit: ${recordPath}`);
362
+ }
363
+ return record;
364
+ }
365
+ function readWorkspaceCheckoutDurabilityRecord(workspacePath) {
366
+ const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
367
+ const status = lstatIfExists(recordPath);
368
+ if (!status) return null;
369
+ if (!status.isFile() || status.isSymbolicLink()) {
370
+ throw new Error(`Workspace checkout durability record is not a regular file: ${recordPath}`);
371
+ }
372
+ return parseWorkspaceCheckoutDurabilityRecord(workspacePath, recordPath, import_node_fs.default.readFileSync(recordPath, "utf8"));
373
+ }
374
+ function updateWorkspaceCheckoutDurabilityRecord(workspacePath, record) {
375
+ if (record.head && !tryGit(workspacePath, ["cat-file", "-e", `${record.head}^{commit}`])) {
376
+ throw new Error(`Cannot record workspace checkout durability for a missing commit: ${record.head}`);
377
+ }
378
+ readWorkspaceCheckoutDurabilityRecord(workspacePath);
379
+ const stateDirectory = workspacePrivateStateDirectory(workspacePath);
380
+ const recordPath = workspaceCheckoutDurabilityPath(workspacePath);
381
+ const temporaryPath = import_node_path.default.join(stateDirectory, `.workspace-checkout-durability.${process.pid}.${crypto.randomUUID()}.tmp`);
382
+ try {
383
+ writePrivateFileDurably(temporaryPath, `${JSON.stringify(record)}
384
+ `);
385
+ import_node_fs.default.renameSync(temporaryPath, recordPath);
386
+ fsyncDirectory(stateDirectory);
387
+ } catch (error) {
388
+ import_node_fs.default.rmSync(temporaryPath, { force: true });
389
+ throw error;
390
+ }
391
+ }
392
+ function fsyncWorkspaceCheckoutTree(workspacePath) {
393
+ const status = import_node_fs.default.lstatSync(workspacePath);
394
+ if (!status.isDirectory() || status.isSymbolicLink()) {
395
+ throw new Error(`Workspace clone root is not a regular directory: ${workspacePath}`);
396
+ }
397
+ for (const name of import_node_fs.default.readdirSync(workspacePath)) {
398
+ if (name === ".git") continue;
399
+ fsyncTree(import_node_path.default.join(workspacePath, name));
400
+ }
401
+ fsyncDirectory(workspacePath);
402
+ }
403
+ function workspaceRebaseInProgress(workspacePath) {
404
+ const gitDirectory = import_node_path.default.join(workspacePath, ".git");
405
+ 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"));
406
+ }
407
+ function clearWorkspaceCheckoutTree(workspacePath) {
408
+ for (const name of import_node_fs.default.readdirSync(workspacePath)) {
409
+ if (name === ".git") continue;
410
+ import_node_fs.default.rmSync(import_node_path.default.join(workspacePath, name), { recursive: true, force: true });
411
+ }
412
+ fsyncDirectory(workspacePath);
413
+ }
414
+ function recoverWorkspaceCheckoutDurability(workspacePath) {
415
+ workspacePath = import_node_path.default.resolve(workspacePath);
416
+ const record = readWorkspaceCheckoutDurabilityRecord(workspacePath);
417
+ const observedHead = revParse(workspacePath, "HEAD");
418
+ if (record?.state === "durable" && record.head === observedHead) return;
419
+ let targetHead = record?.state === "transition" ? record.head : observedHead;
420
+ if (record?.state === "transition" && observedHead && observedHead !== record.head) {
421
+ const hydrationReceipt = readHydratedWorkspaceReceipt(workspacePath);
422
+ if (hydrationReceipt?.head === observedHead) {
423
+ targetHead = observedHead;
424
+ }
425
+ }
426
+ if (workspaceRebaseInProgress(workspacePath)) {
427
+ const abort = gitResult(workspacePath, ["rebase", "--abort"]);
428
+ if (abort.exitCode !== 0) {
429
+ throw new Error(`Abort interrupted workspace rebase: ${abort.stderr || abort.stdout || `git exited ${abort.exitCode}`}`);
430
+ }
431
+ if (record?.state !== "transition") targetHead = revParse(workspacePath, "HEAD");
432
+ }
433
+ clearWorkspaceCheckoutTree(workspacePath);
434
+ if (targetHead) {
435
+ git(workspacePath, ["reset", "--hard", targetHead], "reconstruct durable workspace checkout");
436
+ if (revParse(workspacePath, "HEAD") !== targetHead) {
437
+ throw new Error(`Workspace checkout reconstruction did not restore ${targetHead}`);
438
+ }
439
+ } else {
440
+ git(workspacePath, ["symbolic-ref", "HEAD", `refs/heads/${WORKSPACE_GIT_BRANCH}`], "restore unborn workspace HEAD");
441
+ git(workspacePath, ["update-ref", "-d", `refs/heads/${WORKSPACE_GIT_BRANCH}`], "remove interrupted workspace branch ref");
442
+ git(workspacePath, ["read-tree", "--empty"], "reconstruct unborn workspace checkout");
443
+ if (revParse(workspacePath, "HEAD") !== null) {
444
+ throw new Error("Workspace checkout reconstruction did not restore its unborn HEAD");
445
+ }
446
+ }
447
+ fsyncWorkspaceCheckoutTree(workspacePath);
448
+ updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "durable", head: targetHead });
449
+ }
450
+ function workspaceCheckoutDurabilityIsCurrent(workspacePath) {
451
+ const record = readWorkspaceCheckoutDurabilityRecord(workspacePath);
452
+ return Boolean(record?.state === "durable" && record.head === revParse(workspacePath, "HEAD"));
453
+ }
454
+ function beginWorkspaceCheckoutTransition(workspacePath) {
455
+ recoverWorkspaceCheckoutDurability(workspacePath);
456
+ const head = revParse(workspacePath, "HEAD");
457
+ updateWorkspaceCheckoutDurabilityRecord(workspacePath, { version: 1, state: "transition", head });
458
+ }
459
+ function completeWorkspaceCheckoutTransition(workspacePath) {
460
+ fsyncWorkspaceCheckoutTree(workspacePath);
461
+ updateWorkspaceCheckoutDurabilityRecord(workspacePath, {
462
+ version: 1,
463
+ state: "durable",
464
+ head: revParse(workspacePath, "HEAD")
465
+ });
466
+ }
467
+ function throwAfterWorkspaceCheckoutRecovery(workspacePath, error) {
468
+ try {
469
+ recoverWorkspaceCheckoutDurability(workspacePath);
470
+ } catch (recoveryError) {
471
+ throw new AggregateError(
472
+ [error, recoveryError],
473
+ `Workspace checkout mutation failed and its durable head could not be restored: ${error instanceof Error ? error.message : String(error)}`
474
+ );
475
+ }
476
+ throw error;
477
+ }
478
+ function runWorkspaceCheckoutTransition(workspacePath, mutate) {
479
+ beginWorkspaceCheckoutTransition(workspacePath);
480
+ try {
481
+ mutate();
482
+ completeWorkspaceCheckoutTransition(workspacePath);
483
+ } catch (error) {
484
+ throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
485
+ }
486
+ }
487
+ function parseHydrationTransactionManifest(transactionPath) {
488
+ const manifestPath = import_node_path.default.join(transactionPath, "manifest.json");
489
+ const workspacePath = import_node_path.default.resolve(transactionPath, "..", "..", "..");
490
+ const status = import_node_fs.default.lstatSync(manifestPath);
491
+ if (!status.isFile() || status.isSymbolicLink()) {
492
+ throw new Error(`Workspace hydration transaction manifest is not a regular file: ${manifestPath}`);
493
+ }
494
+ let parsed;
495
+ try {
496
+ parsed = JSON.parse(import_node_fs.default.readFileSync(manifestPath, "utf8"));
497
+ } catch (error) {
498
+ throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`, { cause: error });
499
+ }
500
+ if (!parsed || typeof parsed !== "object") {
501
+ throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
502
+ }
503
+ const candidate = parsed;
504
+ if (candidate.version !== 2 || !Array.isArray(candidate.mounts)) {
505
+ throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
506
+ }
507
+ const parseReceipt = (rawReceipt) => {
508
+ if (rawReceipt === null) return null;
509
+ if (!rawReceipt || typeof rawReceipt !== "object") {
510
+ throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
511
+ }
512
+ return parseWorkspaceHydrationReceiptContent(workspacePath, manifestPath, `${JSON.stringify(rawReceipt)}
513
+ `);
514
+ };
515
+ const targetReceipt = parseReceipt(candidate.targetReceipt);
516
+ const receiptBefore = parseReceipt(candidate.receiptBefore);
517
+ const seenIds = /* @__PURE__ */ new Set();
518
+ const seenSnapshots = /* @__PURE__ */ new Set();
519
+ const mounts = candidate.mounts.map((rawMount) => {
520
+ if (!rawMount || typeof rawMount !== "object") {
521
+ throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
522
+ }
523
+ const mount = rawMount;
524
+ if (typeof mount.id !== "string" || !mount.id || typeof mount.incarnationKey !== "string" || !mount.incarnationKey || mount.incarnationKey.includes("\0") || typeof mount.sourcePath !== "string" || import_node_path.default.resolve(mount.sourcePath) !== mount.sourcePath || typeof mount.durabilityRootPath !== "string" || import_node_path.default.resolve(mount.durabilityRootPath) !== mount.durabilityRootPath || typeof mount.workspaceRelativePath !== "string" || typeof mount.existed !== "boolean" || typeof mount.snapshotName !== "string" || !/^[0-9]+$/.test(mount.snapshotName) || seenIds.has(mount.id) || seenSnapshots.has(mount.snapshotName)) {
525
+ throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
526
+ }
527
+ const relative = import_node_path.default.relative(mount.durabilityRootPath, mount.sourcePath);
528
+ if (!relative || relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
529
+ throw new Error(`Workspace hydration transaction source escapes its durability root: ${mount.sourcePath}`);
530
+ }
531
+ seenIds.add(mount.id);
532
+ seenSnapshots.add(mount.snapshotName);
533
+ return mount;
534
+ });
535
+ return {
536
+ version: 2,
537
+ targetReceipt,
538
+ receiptBefore,
539
+ mounts
540
+ };
541
+ }
542
+ function removeHydrationTransaction(workspacePath) {
543
+ const transactionPath = hydrationTransactionPath(workspacePath);
544
+ import_node_fs.default.rmSync(transactionPath, { recursive: true, force: true });
545
+ fsyncDirectory(import_node_path.default.dirname(transactionPath));
546
+ }
547
+ function hydrationTransactionCommitted(transactionPath) {
548
+ const markerPath = import_node_path.default.join(transactionPath, "committed");
549
+ const markerStatus = lstatIfExists(markerPath);
550
+ if (!markerStatus) return false;
551
+ if (!markerStatus.isFile() || markerStatus.isSymbolicLink() || import_node_fs.default.readFileSync(markerPath, "utf8") !== "committed\n") {
552
+ throw new Error(`Workspace hydration transaction commit marker is invalid: ${markerPath}`);
553
+ }
554
+ return true;
555
+ }
556
+ function restoreHydrationTransaction(workspacePath, manifest) {
557
+ const transactionPath = hydrationTransactionPath(workspacePath);
558
+ for (const mount of manifest.mounts) {
559
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
560
+ trustedRoot: mount.durabilityRootPath,
561
+ candidate: mount.sourcePath,
562
+ label: `Workspace hydration recovery mount ${mount.id}`
563
+ });
564
+ if (!mount.existed) {
565
+ import_node_fs.default.rmSync(mount.sourcePath, { recursive: true, force: true });
566
+ continue;
567
+ }
568
+ const snapshotPath = import_node_path.default.join(transactionPath, "mounts", mount.snapshotName);
569
+ const snapshotStatus = import_node_fs.default.lstatSync(snapshotPath);
570
+ if (!snapshotStatus.isDirectory() || snapshotStatus.isSymbolicLink()) {
571
+ throw new Error(`Workspace hydration snapshot is not a regular directory: ${snapshotPath}`);
572
+ }
573
+ (0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: snapshotPath, targetRoot: mount.sourcePath, sourceMode: "all", deletionMode: "all" });
574
+ }
575
+ fsyncHydratedWorkspaceMounts(
576
+ manifest.mounts.map((mount) => ({
577
+ ...mount,
578
+ hydrationIncarnationKey: mount.incarnationKey,
579
+ sourceMode: "all",
580
+ hydrateDeletionMode: "all"
581
+ }))
582
+ );
583
+ }
584
+ function recoverPendingHydrationTransaction(workspacePath) {
585
+ const stateDirectory = import_node_path.default.dirname(hydrationTransactionPath(workspacePath));
586
+ const stateStatus = lstatIfExists(stateDirectory);
587
+ if (!stateStatus) return;
588
+ if (!stateStatus.isDirectory() || stateStatus.isSymbolicLink()) {
589
+ throw new Error(`Workspace Git private state is not a regular directory: ${stateDirectory}`);
590
+ }
591
+ for (const name of import_node_fs.default.readdirSync(stateDirectory)) {
592
+ if (!name.startsWith(".workspace-hydration-transaction.") || !name.endsWith(".tmp")) continue;
593
+ import_node_fs.default.rmSync(import_node_path.default.join(stateDirectory, name), { recursive: true, force: true });
594
+ fsyncDirectory(stateDirectory);
595
+ }
596
+ const transactionPath = hydrationTransactionPath(workspacePath);
597
+ const transactionStatus = lstatIfExists(transactionPath);
598
+ if (!transactionStatus) return;
599
+ if (!transactionStatus.isDirectory() || transactionStatus.isSymbolicLink()) {
600
+ throw new Error(`Workspace hydration transaction is not a regular directory: ${transactionPath}`);
601
+ }
602
+ const manifest = parseHydrationTransactionManifest(transactionPath);
603
+ if (hydrationTransactionCommitted(transactionPath)) {
604
+ const receipt2 = readHydratedWorkspaceReceipt(workspacePath);
605
+ if (manifest.targetReceipt && !workspaceHydrationReceiptsEqual(receipt2, manifest.targetReceipt)) {
606
+ updateHydratedWorkspaceReceipt(workspacePath, manifest.targetReceipt);
607
+ }
608
+ removeHydrationTransaction(workspacePath);
609
+ return;
610
+ }
611
+ const receipt = readHydratedWorkspaceReceipt(workspacePath);
612
+ if (manifest.targetReceipt && !workspaceHydrationReceiptsEqual(manifest.targetReceipt, manifest.receiptBefore) && workspaceHydrationReceiptsEqual(receipt, manifest.targetReceipt)) {
613
+ removeHydrationTransaction(workspacePath);
614
+ return;
615
+ }
616
+ restoreHydrationTransaction(workspacePath, manifest);
617
+ removeHydrationTransaction(workspacePath);
618
+ }
619
+ function beginHydrationTransaction(workspacePath, mounts, targetReceipt) {
620
+ recoverPendingHydrationTransaction(workspacePath);
621
+ const stateDirectory = workspacePrivateStateDirectory(workspacePath);
622
+ const transactionPath = hydrationTransactionPath(workspacePath);
623
+ const stagingPath = import_node_path.default.join(stateDirectory, `.workspace-hydration-transaction.${process.pid}.${crypto.randomUUID()}.tmp`);
624
+ import_node_fs.default.mkdirSync(stagingPath, { mode: 448 });
625
+ try {
626
+ const snapshotRoot = import_node_path.default.join(stagingPath, "mounts");
627
+ import_node_fs.default.mkdirSync(snapshotRoot, { mode: 448 });
628
+ const manifest = {
629
+ version: 2,
630
+ targetReceipt,
631
+ receiptBefore: readHydratedWorkspaceReceipt(workspacePath),
632
+ mounts: mounts.map((mount, index) => {
633
+ const sourcePath = import_node_path.default.resolve(mount.sourcePath);
634
+ const durabilityRootPath = import_node_path.default.resolve(mount.durabilityRootPath);
635
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
636
+ trustedRoot: durabilityRootPath,
637
+ candidate: sourcePath,
638
+ label: `Workspace hydration snapshot mount ${mount.id}`
639
+ });
640
+ const sourceStatus = lstatIfExists(sourcePath);
641
+ if (sourceStatus && (!sourceStatus.isDirectory() || sourceStatus.isSymbolicLink())) {
642
+ throw new Error(`Workspace hydration snapshot source is not a regular directory: ${sourcePath}`);
643
+ }
644
+ const snapshotName = String(index);
645
+ const snapshotPath = import_node_path.default.join(snapshotRoot, snapshotName);
646
+ import_node_fs.default.mkdirSync(snapshotPath, { mode: 448 });
647
+ if (sourceStatus) {
648
+ (0, import_working_tree_mirror.mirrorWorkingTree)({
649
+ sourceRoot: sourcePath,
650
+ targetRoot: snapshotPath,
651
+ sourceMode: "all",
652
+ deletionMode: "all",
653
+ // This tree is still private and the source authority is untouched.
654
+ // The single recursive barrier below makes the complete snapshot
655
+ // durable before its directory entry is published or hydration starts.
656
+ durability: "deferred_private_staging"
657
+ });
658
+ }
659
+ return {
660
+ id: mount.id,
661
+ incarnationKey: mount.hydrationIncarnationKey,
662
+ sourcePath,
663
+ durabilityRootPath,
664
+ workspaceRelativePath: mount.workspaceRelativePath,
665
+ existed: Boolean(sourceStatus),
666
+ snapshotName
667
+ };
668
+ })
669
+ };
670
+ writePrivateFileDurably(import_node_path.default.join(stagingPath, "manifest.json"), `${JSON.stringify(manifest)}
671
+ `);
672
+ fsyncTree(stagingPath);
673
+ import_node_fs.default.renameSync(stagingPath, transactionPath);
674
+ fsyncDirectory(stateDirectory);
675
+ return manifest;
676
+ } catch (error) {
677
+ import_node_fs.default.rmSync(stagingPath, { recursive: true, force: true });
678
+ fsyncDirectory(stateDirectory);
679
+ throw error;
680
+ }
681
+ }
682
+ function markHydrationTransactionDurable(workspacePath) {
683
+ const transactionPath = hydrationTransactionPath(workspacePath);
684
+ writePrivateFileDurably(import_node_path.default.join(transactionPath, "committed"), "committed\n");
685
+ fsyncDirectory(transactionPath);
686
+ }
687
+ function fsyncHydratedWorkspaceMount(mount) {
688
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
689
+ trustedRoot: mount.durabilityRootPath,
690
+ candidate: mount.sourcePath,
691
+ label: `Workspace mount ${mount.id} source`
692
+ });
693
+ const syncEntry = (entryPath, root) => {
694
+ const status = import_node_fs.default.lstatSync(entryPath);
695
+ if (status.isSymbolicLink()) return;
696
+ if (status.isDirectory()) {
697
+ for (const name of import_node_fs.default.readdirSync(entryPath)) {
698
+ if (root && name === ".git") continue;
699
+ syncEntry(import_node_path.default.join(entryPath, name), false);
700
+ }
701
+ fsyncDirectory(entryPath);
702
+ return;
703
+ }
704
+ if (!status.isFile()) return;
705
+ const descriptor = import_node_fs.default.openSync(entryPath, import_node_fs.default.constants.O_RDONLY);
706
+ try {
707
+ import_node_fs.default.fsyncSync(descriptor);
708
+ } finally {
709
+ import_node_fs.default.closeSync(descriptor);
710
+ }
711
+ };
712
+ if (import_node_fs.default.existsSync(mount.sourcePath)) syncEntry(mount.sourcePath, true);
713
+ const durabilityRoot = import_node_path.default.resolve(mount.durabilityRootPath);
714
+ let boundaryParent = import_node_path.default.dirname(import_node_path.default.resolve(mount.sourcePath));
715
+ while (true) {
716
+ if (import_node_fs.default.existsSync(boundaryParent)) {
717
+ const boundaryStatus = import_node_fs.default.lstatSync(boundaryParent);
718
+ if (!boundaryStatus.isDirectory() || boundaryStatus.isSymbolicLink()) {
719
+ throw new Error(`Workspace mount durability boundary is not a regular directory: ${boundaryParent}`);
720
+ }
721
+ fsyncDirectory(boundaryParent);
722
+ }
723
+ if (boundaryParent === durabilityRoot) break;
724
+ const nextParent = import_node_path.default.dirname(boundaryParent);
725
+ if (nextParent === boundaryParent) {
726
+ throw new Error(`Workspace mount ${mount.sourcePath} does not descend from durability root ${durabilityRoot}`);
727
+ }
728
+ boundaryParent = nextParent;
729
+ }
730
+ }
731
+ function fsyncHydratedWorkspaceMounts(mounts) {
732
+ for (const mount of mounts) fsyncHydratedWorkspaceMount(mount);
733
+ }
91
734
  function workspaceIsShallow(workspacePath) {
92
735
  return gitResult(workspacePath, ["rev-parse", "--is-shallow-repository"]).stdout === "true";
93
736
  }
@@ -100,7 +743,22 @@ function validateMounts(workspacePath, mounts) {
100
743
  const paths = [];
101
744
  for (const mount of mounts) {
102
745
  if (!mount.id || ids.has(mount.id)) throw new Error(`Workspace mount id must be unique: ${mount.id}`);
746
+ if (!mount.hydrationIncarnationKey || mount.hydrationIncarnationKey.includes("\0")) {
747
+ throw new Error(`Workspace mount hydration incarnation is invalid: ${mount.id}`);
748
+ }
103
749
  ids.add(mount.id);
750
+ const sourcePath = import_node_path.default.resolve(mount.sourcePath);
751
+ const durabilityRoot = import_node_path.default.resolve(mount.durabilityRootPath);
752
+ const durabilityRelative = import_node_path.default.relative(durabilityRoot, sourcePath);
753
+ const durabilityRootStatus = import_node_fs.default.lstatSync(durabilityRoot);
754
+ if (!durabilityRootStatus.isDirectory() || durabilityRootStatus.isSymbolicLink() || !durabilityRelative || durabilityRelative === ".." || durabilityRelative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(durabilityRelative)) {
755
+ throw new Error(`Workspace mount source escapes its durability root: ${mount.sourcePath}`);
756
+ }
757
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
758
+ trustedRoot: durabilityRoot,
759
+ candidate: sourcePath,
760
+ label: `Workspace mount ${mount.id} source`
761
+ });
104
762
  const normalized = mount.workspaceRelativePath.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "");
105
763
  if (!normalized || normalized === "." || normalized.split("/").some((segment) => segment === ".." || segment === ".git")) {
106
764
  throw new Error(`Invalid workspace mount path: ${mount.workspaceRelativePath}`);
@@ -147,6 +805,8 @@ function configureWorkspaceRepository(input) {
147
805
  if (!name || !email) throw new Error("Workspace Git identity must include name and email");
148
806
  git(input.workspacePath, ["config", "--local", "user.name", name], "configure workspace Git user name");
149
807
  git(input.workspacePath, ["config", "--local", "user.email", email], "configure workspace Git user email");
808
+ git(input.workspacePath, ["config", "--local", "core.fsync", "committed"], "configure durable workspace commits");
809
+ git(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
150
810
  }
151
811
  function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
152
812
  const result = gitResult(workspacePath, [
@@ -170,7 +830,24 @@ ${result.stdout}`;
170
830
  }
171
831
  function ensureWorkspaceGitClone(input) {
172
832
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
833
+ const workspaceParent = import_node_path.default.dirname(workspacePath);
834
+ const parentStatus = import_node_fs.default.lstatSync(workspaceParent);
835
+ if (!parentStatus.isDirectory() || parentStatus.isSymbolicLink()) {
836
+ throw new Error(`Workspace clone parent is not a regular directory: ${workspaceParent}`);
837
+ }
838
+ const workspaceStatus = lstatIfExists(workspacePath);
839
+ if (workspaceStatus) {
840
+ if (!workspaceStatus.isDirectory() || workspaceStatus.isSymbolicLink()) {
841
+ throw new Error(`Workspace clone root is not a regular directory: ${workspacePath}`);
842
+ }
843
+ }
173
844
  const gitPath = import_node_path.default.join(workspacePath, ".git");
845
+ const gitStatus = lstatIfExists(gitPath);
846
+ if (gitStatus) {
847
+ if (!gitStatus.isDirectory() || gitStatus.isSymbolicLink()) {
848
+ throw new Error(`Workspace clone Git metadata is not a regular directory: ${gitPath}`);
849
+ }
850
+ }
174
851
  if (!import_node_fs.default.existsSync(gitPath)) {
175
852
  import_node_fs.default.rmSync(workspacePath, { recursive: true, force: true });
176
853
  import_node_fs.default.mkdirSync(import_node_path.default.dirname(workspacePath), { recursive: true });
@@ -192,6 +869,7 @@ function ensureWorkspaceGitClone(input) {
192
869
  }
193
870
  }
194
871
  configureWorkspaceRepository({ ...input, workspacePath });
872
+ recoverWorkspaceCheckoutDurability(workspacePath);
195
873
  const previousRemoteHead = revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`);
196
874
  const localHeadBeforeFetch = revParse(workspacePath, "HEAD");
197
875
  if (!revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) && previousRemoteHead && localHeadBeforeFetch && tryGit(workspacePath, ["merge-base", "--is-ancestor", previousRemoteHead, localHeadBeforeFetch])) {
@@ -200,17 +878,21 @@ function ensureWorkspaceGitClone(input) {
200
878
  const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
201
879
  let localHead = revParse(workspacePath, "HEAD");
202
880
  if (!localHead && remoteHead) {
203
- git(
204
- workspacePath,
205
- ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
206
- "check out workspace main"
207
- );
881
+ runWorkspaceCheckoutTransition(workspacePath, () => {
882
+ git(
883
+ workspacePath,
884
+ ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
885
+ "check out workspace main"
886
+ );
887
+ });
208
888
  localHead = remoteHead;
209
889
  }
210
890
  if (localHead && remoteHead && localHead === remoteHead) {
211
891
  updateIntegratedWorkspaceHead(workspacePath, remoteHead);
212
892
  } else if (localHead && remoteHead && workspaceIsShallow(workspacePath) && !tryGit(workspacePath, ["merge-base", localHead, remoteHead]) && revParse(workspacePath, WORKSPACE_GIT_INTEGRATED_REF) === localHead && workspaceIsClean(workspacePath)) {
213
- git(workspacePath, ["reset", "--hard", remoteHead], "advance workspace across retained history boundary");
893
+ runWorkspaceCheckoutTransition(workspacePath, () => {
894
+ git(workspacePath, ["reset", "--hard", remoteHead], "advance workspace across retained history boundary");
895
+ });
214
896
  updateIntegratedWorkspaceHead(workspacePath, remoteHead);
215
897
  localHead = remoteHead;
216
898
  }
@@ -230,6 +912,16 @@ function activeMounts(mounts) {
230
912
  }
231
913
  function mirrorMountsToWorkspace(workspacePath, mounts) {
232
914
  for (const mount of mounts) {
915
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
916
+ trustedRoot: mount.durabilityRootPath,
917
+ candidate: mount.sourcePath,
918
+ label: `Workspace mount ${mount.id} source`
919
+ });
920
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
921
+ trustedRoot: workspacePath,
922
+ candidate: import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/")),
923
+ label: `Workspace mount ${mount.id} outer target`
924
+ });
233
925
  (0, import_working_tree_mirror.mirrorWorkingTree)({
234
926
  sourceRoot: mount.sourcePath,
235
927
  targetRoot: import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/")),
@@ -241,6 +933,11 @@ function mirrorMountsToWorkspace(workspacePath, mounts) {
241
933
  function removeWorkspaceMounts(workspacePath, mounts) {
242
934
  for (const mount of mounts) {
243
935
  const targetPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
936
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
937
+ trustedRoot: workspacePath,
938
+ candidate: targetPath,
939
+ label: `Workspace mount ${mount.id} outer target`
940
+ });
244
941
  const relative = import_node_path.default.relative(import_node_path.default.resolve(workspacePath), import_node_path.default.resolve(targetPath));
245
942
  if (relative === ".." || relative.startsWith(`..${import_node_path.default.sep}`) || import_node_path.default.isAbsolute(relative)) {
246
943
  throw new Error(`Workspace mount escapes the workspace clone: ${mount.workspaceRelativePath}`);
@@ -248,9 +945,45 @@ function removeWorkspaceMounts(workspacePath, mounts) {
248
945
  import_node_fs.default.rmSync(targetPath, { recursive: true, force: true });
249
946
  }
250
947
  }
251
- function hydrateWorkspaceGitMounts(workspacePath, mounts) {
948
+ function restoreDeferredWorkspaceMountsFromHead(workspacePath, mounts) {
949
+ const head = revParse(workspacePath, "HEAD");
252
950
  for (const mount of mounts) {
951
+ const normalized = normalizedWorkspaceMountPath(mount.workspaceRelativePath);
952
+ const targetPath = import_node_path.default.join(workspacePath, ...normalized.split("/"));
953
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
954
+ trustedRoot: workspacePath,
955
+ candidate: targetPath,
956
+ label: `Deferred workspace mount ${mount.id} outer target`
957
+ });
958
+ import_node_fs.default.rmSync(targetPath, { recursive: true, force: true });
959
+ if (head && tryGit(workspacePath, ["cat-file", "-e", `${head}:${normalized}`])) {
960
+ git(
961
+ workspacePath,
962
+ ["checkout", "--no-recurse-submodules", head, "--", `:(literal)${normalized}`],
963
+ `restore deferred workspace mount ${mount.id} from its integration basis`
964
+ );
965
+ }
966
+ }
967
+ }
968
+ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
969
+ const hydratedMountIds = [];
970
+ const skippedMountIds = [];
971
+ for (const mount of mounts) {
972
+ if (!options.ignoreBusy && mount.busy?.()) {
973
+ skippedMountIds.push(mount.id);
974
+ continue;
975
+ }
253
976
  const sourceRoot = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
977
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
978
+ trustedRoot: workspacePath,
979
+ candidate: sourceRoot,
980
+ label: `Workspace mount ${mount.id} outer source`
981
+ });
982
+ (0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
983
+ trustedRoot: mount.durabilityRootPath,
984
+ candidate: mount.sourcePath,
985
+ label: `Workspace mount ${mount.id} hydration target`
986
+ });
254
987
  if (!import_node_fs.default.existsSync(sourceRoot)) {
255
988
  if (mount.hydrateDeletionMode === "all") {
256
989
  import_node_fs.default.rmSync(mount.sourcePath, { recursive: true, force: true });
@@ -267,37 +1000,167 @@ function hydrateWorkspaceGitMounts(workspacePath, mounts) {
267
1000
  import_node_fs.default.rmSync(emptyRoot, { recursive: true, force: true });
268
1001
  }
269
1002
  }
1003
+ hydratedMountIds.push(mount.id);
270
1004
  continue;
271
1005
  }
1006
+ const targetExists = import_node_fs.default.existsSync(mount.sourcePath);
272
1007
  (0, import_working_tree_mirror.mirrorWorkingTree)({
273
1008
  sourceRoot,
274
1009
  targetRoot: mount.sourcePath,
275
1010
  sourceMode: "all",
276
- deletionMode: mount.hydrateDeletionMode
1011
+ // A missing checkout has no Git index to define git-only deletions. Its
1012
+ // newly created directory is empty, so all-mode is equivalent and lets
1013
+ // bootstrap hydration preserve the fetched tree before Git initializes.
1014
+ deletionMode: targetExists ? mount.hydrateDeletionMode : "all"
277
1015
  });
1016
+ hydratedMountIds.push(mount.id);
1017
+ }
1018
+ return { hydratedMountIds: hydratedMountIds.sort(), skippedMountIds: skippedMountIds.sort() };
1019
+ }
1020
+ function hydrateWorkspaceGitMountsTransactionally(input) {
1021
+ const workspacePath = import_node_path.default.resolve(input.workspacePath);
1022
+ const mountIds = input.mounts.map(({ id }) => id).sort();
1023
+ const requiredIds = (input.requiredMounts ?? input.mounts).map(({ id }) => id).sort();
1024
+ if (mountIds.length !== requiredIds.length || mountIds.some((id, index) => id !== requiredIds[index])) {
1025
+ return { hydratedMountIds: [], skippedMountIds: requiredIds };
1026
+ }
1027
+ const busyMountIds = input.ignoreBusy ? [] : (input.durabilityMounts ?? input.mounts).filter((mount) => mount.busy?.()).map(({ id }) => id).sort();
1028
+ if (busyMountIds.length > 0) {
1029
+ return { hydratedMountIds: [], skippedMountIds: busyMountIds };
1030
+ }
1031
+ if (input.recordCurrentHead && !workspaceCheckoutDurabilityIsCurrent(workspacePath)) {
1032
+ throw new Error("Workspace checkout is not durably materialized at its current HEAD");
1033
+ }
1034
+ const targetHead = input.recordCurrentHead ? revParse(workspacePath, "HEAD") : null;
1035
+ const targetReceipt = targetHead ? workspaceHydrationReceipt(targetHead, input.receiptMounts ?? input.durabilityMounts ?? input.mounts) : null;
1036
+ const manifest = beginHydrationTransaction(workspacePath, input.mounts, targetReceipt);
1037
+ try {
1038
+ const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, input.mounts, { ignoreBusy: true });
1039
+ if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== requiredIds.length || hydration.hydratedMountIds.some((id, index) => id !== requiredIds[index])) {
1040
+ throw new Error("Workspace hydration did not include every required mount");
1041
+ }
1042
+ fsyncHydratedWorkspaceMounts(input.durabilityMounts ?? input.mounts);
1043
+ markHydrationTransactionDurable(workspacePath);
1044
+ if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
1045
+ removeHydrationTransaction(workspacePath);
1046
+ return hydration;
1047
+ } catch (error) {
1048
+ const transactionPath = hydrationTransactionPath(workspacePath);
1049
+ if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
1050
+ throw error;
1051
+ }
1052
+ try {
1053
+ restoreHydrationTransaction(workspacePath, manifest);
1054
+ removeHydrationTransaction(workspacePath);
1055
+ } catch (restoreError) {
1056
+ throw new AggregateError(
1057
+ [error, restoreError],
1058
+ `Workspace hydration failed and its durable snapshot could not be restored: ${error instanceof Error ? error.message : String(error)}`
1059
+ );
1060
+ }
1061
+ throw error;
1062
+ }
1063
+ }
1064
+ function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
1065
+ const gitDirectory = import_node_path.default.join(import_node_path.default.resolve(workspacePath), ".git");
1066
+ if (!import_node_fs.default.existsSync(gitDirectory)) {
1067
+ return hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options);
1068
+ }
1069
+ recoverWorkspaceCheckoutDurability(import_node_path.default.resolve(workspacePath));
1070
+ return hydrateWorkspaceGitMountsTransactionally({
1071
+ workspacePath,
1072
+ mounts,
1073
+ ignoreBusy: options.ignoreBusy
1074
+ });
1075
+ }
1076
+ function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
1077
+ workspacePath = import_node_path.default.resolve(workspacePath);
1078
+ validateMounts(workspacePath, mounts);
1079
+ recoverWorkspaceCheckoutDurability(workspacePath);
1080
+ recoverPendingHydrationTransaction(workspacePath);
1081
+ const currentHead = revParse(workspacePath, "HEAD");
1082
+ if (!currentHead) return;
1083
+ const currentReceipt = readHydratedWorkspaceReceipt(workspacePath);
1084
+ const outerHeadContainsMount = (mount) => tryGit(workspacePath, ["cat-file", "-e", `HEAD:${normalizedWorkspaceMountPath(mount.workspaceRelativePath)}`]);
1085
+ const configuredBasisMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, mounts, options.deferMountIds);
1086
+ const uncoveredBasisMounts = configuredBasisMounts.filter(
1087
+ (mount) => !workspaceHydrationReceiptCoversMount(currentReceipt, currentHead, mount)
1088
+ );
1089
+ if (workspaceHydrationReceiptMatchesMounts(currentReceipt, currentHead, configuredBasisMounts)) return;
1090
+ const busyMountIds = options.ignoreBusy ? [] : uncoveredBasisMounts.filter((mount) => mount.busy?.()).map(({ id }) => id);
1091
+ if (busyMountIds.length > 0) {
1092
+ throw new Error(
1093
+ `Workspace HEAD ${currentHead} has not been fully hydrated; busy mounts must finish before projection: ${busyMountIds.join(", ")}`
1094
+ );
1095
+ }
1096
+ const hydrationTargets = uncoveredBasisMounts.filter(
1097
+ (mount) => !mount.deleteWhenSourceMissing && !(mount.preserveLocalOnHydrationBasisChange === true && currentReceipt?.head === currentHead) && (outerHeadContainsMount(mount) || mount.preserveLocalOnInitialOuterAbsence !== true)
1098
+ );
1099
+ const hydration = hydrateWorkspaceGitMountsTransactionally({
1100
+ workspacePath,
1101
+ mounts: hydrationTargets,
1102
+ durabilityMounts: uncoveredBasisMounts,
1103
+ receiptMounts: configuredBasisMounts,
1104
+ requiredMounts: hydrationTargets,
1105
+ ignoreBusy: options.ignoreBusy,
1106
+ recordCurrentHead: true
1107
+ });
1108
+ if (hydration.skippedMountIds.length > 0) {
1109
+ throw new Error(
1110
+ `Workspace HEAD ${currentHead} has not been fully hydrated; busy mounts must finish before projection: ${hydration.skippedMountIds.join(", ")}`
1111
+ );
278
1112
  }
279
1113
  }
280
1114
  function resetWorkspaceGit(input) {
281
1115
  const workspacePath = import_node_path.default.resolve(input.workspacePath);
282
1116
  validateMounts(workspacePath, input.mounts);
283
1117
  const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
1118
+ recoverPendingHydrationTransaction(workspacePath);
284
1119
  const status = gitResult(workspacePath, ["status", "--porcelain=v1", "-z"]);
285
1120
  const discardedPaths = status.exitCode === 0 ? status.stdout.split("\0").filter(Boolean).map((entry) => entry.slice(3)).sort() : [];
286
1121
  const remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
287
1122
  if (remoteHead) {
288
- git(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
289
- git(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
1123
+ runWorkspaceCheckoutTransition(workspacePath, () => {
1124
+ git(workspacePath, ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, remoteHead], "reset workspace main");
1125
+ git(workspacePath, ["clean", "-fd", "--", "."], "remove untracked workspace changes");
1126
+ });
290
1127
  } else if (revParse(workspacePath, "HEAD")) {
291
- git(workspacePath, ["rm", "-rf", "--ignore-unmatch", "--", "."], "clear unborn workspace tree");
292
- git(workspacePath, ["commit", "--allow-empty", "-m", JSON.stringify({ type: "workspace_reset" })], "record empty workspace reset");
1128
+ runWorkspaceCheckoutTransition(workspacePath, () => {
1129
+ git(workspacePath, ["rm", "-rf", "--ignore-unmatch", "--", "."], "clear unborn workspace tree");
1130
+ git(workspacePath, ["commit", "--allow-empty", "-m", JSON.stringify({ type: "workspace_reset" })], "record empty workspace reset");
1131
+ });
293
1132
  }
294
1133
  const selected = activeMounts(input.mounts);
295
- hydrateWorkspaceGitMounts(workspacePath, selected.active);
1134
+ const requiredLiveMountsBeforeHydration = input.mounts.filter(({ sourcePath }) => import_node_fs.default.existsSync(sourcePath));
1135
+ const bootstrapMounts = selected.skipped.filter((mount) => {
1136
+ if (mount.deleteWhenSourceMissing || import_node_fs.default.existsSync(mount.sourcePath) || mount.busy?.()) return false;
1137
+ const outerPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
1138
+ return import_node_fs.default.existsSync(outerPath);
1139
+ });
1140
+ const hydrationMounts = [...selected.active, ...bootstrapMounts];
1141
+ const requiredLiveMounts = [
1142
+ ...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
1143
+ ];
1144
+ const hydration = hydrateWorkspaceGitMountsTransactionally({
1145
+ workspacePath,
1146
+ mounts: hydrationMounts,
1147
+ durabilityMounts: [...requiredLiveMounts, ...selected.tombstones],
1148
+ receiptMounts: input.mounts,
1149
+ requiredMounts: requiredLiveMounts,
1150
+ recordCurrentHead: true
1151
+ });
1152
+ const requiredLiveIds = requiredLiveMounts.map(({ id }) => id).sort();
1153
+ const resetHead = revParse(workspacePath, "HEAD");
1154
+ if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== requiredLiveIds.length || hydration.hydratedMountIds.some((id, index) => id !== requiredLiveIds[index]) || resetHead && !workspaceHydrationReceiptMatchesMounts(readHydratedWorkspaceReceipt(workspacePath), resetHead, input.mounts)) {
1155
+ throw new Error("Workspace reset could not durably hydrate every live mount; remediation remains active");
1156
+ }
296
1157
  return {
297
1158
  startingHead: initial.localHead,
298
1159
  localHead: revParse(workspacePath, "HEAD"),
299
1160
  remoteHead,
300
- discardedPaths
1161
+ discardedPaths,
1162
+ activeMountIds: hydration.hydratedMountIds,
1163
+ skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
301
1164
  };
302
1165
  }
303
1166
  function stagedPaths(workspacePath) {
@@ -362,17 +1225,22 @@ function snapshotConflict(input) {
362
1225
  }
363
1226
  function rebaseWorkspace(input) {
364
1227
  const remoteRevision = `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`;
1228
+ beginWorkspaceCheckoutTransition(input.workspacePath);
365
1229
  const rebase = gitResult(
366
1230
  input.workspacePath,
367
1231
  input.truncatedHistoryBase ? ["rebase", "--onto", remoteRevision, input.truncatedHistoryBase] : ["rebase", remoteRevision]
368
1232
  );
369
- if (rebase.exitCode === 0) return { ok: true };
1233
+ if (rebase.exitCode === 0) {
1234
+ completeWorkspaceCheckoutTransition(input.workspacePath);
1235
+ return { ok: true };
1236
+ }
370
1237
  const conflicts = conflictPaths(input.workspacePath);
371
1238
  const refs = snapshotConflict(input);
372
1239
  const abort = gitResult(input.workspacePath, ["rebase", "--abort"]);
373
1240
  if (abort.exitCode !== 0) {
374
1241
  throw new Error(`Abort workspace rebase: ${abort.stderr || abort.stdout || `git exited ${abort.exitCode}`}`);
375
1242
  }
1243
+ completeWorkspaceCheckoutTransition(input.workspacePath);
376
1244
  return {
377
1245
  ok: false,
378
1246
  conflictPaths: conflicts,
@@ -382,26 +1250,31 @@ function rebaseWorkspace(input) {
382
1250
  }
383
1251
  function synchronizeWithFetchedHead(input) {
384
1252
  const localHead = revParse(input.workspacePath, "HEAD");
385
- if (!input.remoteHead) return { kind: "ready", updated: false, rebased: false };
1253
+ const remoteHead = input.remoteHead;
1254
+ if (!remoteHead) return { kind: "ready", updated: false, rebased: false };
386
1255
  if (!localHead) {
387
- git(
388
- input.workspacePath,
389
- ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
390
- "check out fetched workspace main"
391
- );
1256
+ runWorkspaceCheckoutTransition(input.workspacePath, () => {
1257
+ git(
1258
+ input.workspacePath,
1259
+ ["checkout", "--no-recurse-submodules", "-B", WORKSPACE_GIT_BRANCH, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`],
1260
+ "check out fetched workspace main"
1261
+ );
1262
+ });
392
1263
  return { kind: "ready", updated: true, rebased: false };
393
1264
  }
394
- if (localHead === input.remoteHead) {
395
- updateIntegratedWorkspaceHead(input.workspacePath, input.remoteHead);
1265
+ if (localHead === remoteHead) {
1266
+ updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
396
1267
  return { kind: "ready", updated: false, rebased: false };
397
1268
  }
398
- if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", localHead, input.remoteHead])) {
399
- git(input.workspacePath, ["reset", "--hard", input.remoteHead], "fast-forward workspace main");
400
- updateIntegratedWorkspaceHead(input.workspacePath, input.remoteHead);
1269
+ if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", localHead, remoteHead])) {
1270
+ runWorkspaceCheckoutTransition(input.workspacePath, () => {
1271
+ git(input.workspacePath, ["reset", "--hard", remoteHead], "fast-forward workspace main");
1272
+ });
1273
+ updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
401
1274
  return { kind: "ready", updated: true, rebased: false };
402
1275
  }
403
- if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", input.remoteHead, localHead])) {
404
- updateIntegratedWorkspaceHead(input.workspacePath, input.remoteHead);
1276
+ if (tryGit(input.workspacePath, ["merge-base", "--is-ancestor", remoteHead, localHead])) {
1277
+ updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
405
1278
  return { kind: "ready", updated: false, rebased: false };
406
1279
  }
407
1280
  const integratedHead = revParse(input.workspacePath, WORKSPACE_GIT_INTEGRATED_REF);
@@ -414,11 +1287,11 @@ function synchronizeWithFetchedHead(input) {
414
1287
  const rebased = rebaseWorkspace({
415
1288
  workspacePath: input.workspacePath,
416
1289
  localHead,
417
- remoteHead: input.remoteHead,
1290
+ remoteHead,
418
1291
  attemptId: input.attemptId,
419
1292
  truncatedHistoryBase
420
1293
  });
421
- if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath, input.remoteHead);
1294
+ if (rebased.ok) updateIntegratedWorkspaceHead(input.workspacePath, remoteHead);
422
1295
  return rebased.ok ? { kind: "ready", updated: false, rebased: true } : { kind: "conflict", conflictPaths: rebased.conflictPaths, refs: rebased.refs, error: rebased.error };
423
1296
  }
424
1297
  async function synchronizeWorkspaceGit(input) {
@@ -427,25 +1300,131 @@ async function synchronizeWorkspaceGit(input) {
427
1300
  const maxDiffBytes = input.maxDiffBytes ?? MAX_WORKSPACE_GIT_DIFF_BYTES;
428
1301
  const maxPushAttempts = Math.max(1, input.maxPushAttempts ?? 4);
429
1302
  validateMounts(workspacePath, input.mounts);
1303
+ const busyLiveMountIds = input.skipMountMirror ? [] : input.mounts.filter((mount) => !(mount.deleteWhenSourceMissing && !import_node_fs.default.existsSync(mount.sourcePath)) && mount.busy?.()).map(({ id }) => id).sort();
1304
+ if (busyLiveMountIds.length > 0) {
1305
+ const cloneExists = import_node_fs.default.existsSync(import_node_path.default.join(workspacePath, ".git"));
1306
+ const localHead = cloneExists ? revParse(workspacePath, "HEAD") : null;
1307
+ const remoteHead2 = cloneExists ? revParse(workspacePath, `refs/remotes/origin/${WORKSPACE_GIT_BRANCH}`) : null;
1308
+ return {
1309
+ outcome: "no_change",
1310
+ startingHead: localHead,
1311
+ localHead,
1312
+ remoteHead: remoteHead2,
1313
+ publishedHead: remoteHead2,
1314
+ rebaseCount: 0,
1315
+ diffSizeBytes: 0,
1316
+ affectedPaths: [],
1317
+ activeMountIds: [],
1318
+ skippedMountIds: input.mounts.map(({ id }) => id).sort()
1319
+ };
1320
+ }
430
1321
  const initial = ensureWorkspaceGitClone({ ...input, workspacePath });
431
1322
  const startingHead = initial.localHead;
1323
+ const receiptBeforeInitialHydration = readHydratedWorkspaceReceipt(workspacePath);
1324
+ const deferredMountIds = /* @__PURE__ */ new Set();
1325
+ if (initial.localHead && initial.remoteHead && initial.remoteHead !== initial.localHead && receiptBeforeInitialHydration?.head === initial.localHead) {
1326
+ for (const mount of input.mounts) {
1327
+ if (!mount.deleteWhenSourceMissing && mount.preserveLocalOnInitialOuterAbsence !== true && mount.preserveLocalOnHydrationBasisChange !== true && !workspaceHydrationReceiptCoversMount(receiptBeforeInitialHydration, initial.localHead, mount)) {
1328
+ deferredMountIds.add(mount.id);
1329
+ }
1330
+ }
1331
+ }
1332
+ recoverWorkspaceGitHydration(workspacePath, input.mounts, {
1333
+ ...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {}
1334
+ });
1335
+ const receiptRequiredLiveMounts = input.mounts.filter(({ sourcePath }) => import_node_fs.default.existsSync(sourcePath));
432
1336
  const selected = activeMounts(input.mounts);
1337
+ const projectedActiveMounts = selected.active.filter(({ id }) => !deferredMountIds.has(id));
1338
+ const projectedTombstones = selected.tombstones.filter(({ id }) => !deferredMountIds.has(id));
1339
+ const projectionBasisHead = revParse(workspacePath, "HEAD");
1340
+ const projectionBasisReceipt = readHydratedWorkspaceReceipt(workspacePath);
1341
+ const projectionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts).filter(
1342
+ (mount) => !deferredMountIds.has(mount.id) && Boolean(projectionBasisHead && workspaceHydrationReceiptCoversMount(projectionBasisReceipt, projectionBasisHead, mount))
1343
+ );
1344
+ const completedMountSelection = (requireCurrentHeadReceipt = false) => {
1345
+ const currentHeadBeforeHydration = revParse(workspacePath, "HEAD");
1346
+ const completionReceiptMounts = configuredWorkspaceHydrationBasisMounts(workspacePath, input.mounts);
1347
+ if (currentHeadBeforeHydration && workspaceHydrationReceiptMatchesMounts(
1348
+ readHydratedWorkspaceReceipt(workspacePath),
1349
+ currentHeadBeforeHydration,
1350
+ completionReceiptMounts
1351
+ )) {
1352
+ const lateBusyIds = selected.active.filter((mount) => mount.busy?.()).map(({ id }) => id);
1353
+ const lateBusy = new Set(lateBusyIds);
1354
+ return {
1355
+ activeMountIds: [...selected.active.filter(({ id }) => !lateBusy.has(id)), ...selected.tombstones].map(({ id }) => id).sort(),
1356
+ skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...lateBusyIds])].sort()
1357
+ };
1358
+ }
1359
+ const currentReceiptBeforeHydration = readHydratedWorkspaceReceipt(workspacePath);
1360
+ const mountIsAlreadyCovered = (mount) => Boolean(
1361
+ currentHeadBeforeHydration && workspaceHydrationReceiptCoversMount(currentReceiptBeforeHydration, currentHeadBeforeHydration, mount)
1362
+ );
1363
+ const uncoveredCompletionMounts = selected.active.filter((mount) => !mountIsAlreadyCovered(mount));
1364
+ const uncoveredRequiredLiveMounts = receiptRequiredLiveMounts.filter((mount) => !mountIsAlreadyCovered(mount));
1365
+ const uncoveredCompletionBasisMounts = completionReceiptMounts.filter((mount) => !mountIsAlreadyCovered(mount));
1366
+ const hydration = hydrateWorkspaceGitMountsTransactionally({
1367
+ workspacePath,
1368
+ mounts: uncoveredCompletionMounts,
1369
+ durabilityMounts: uncoveredCompletionBasisMounts,
1370
+ receiptMounts: completionReceiptMounts,
1371
+ requiredMounts: uncoveredRequiredLiveMounts,
1372
+ recordCurrentHead: true
1373
+ });
1374
+ if (requireCurrentHeadReceipt) {
1375
+ const currentHead = revParse(workspacePath, "HEAD");
1376
+ if (currentHead && !workspaceHydrationReceiptMatchesMounts(readHydratedWorkspaceReceipt(workspacePath), currentHead, completionReceiptMounts)) {
1377
+ throw new Error(`Workspace inbound integration ${currentHead} could not be fully hydrated before publication continued`);
1378
+ }
1379
+ }
1380
+ return {
1381
+ activeMountIds: [...selected.active, ...selected.tombstones].map(({ id }) => id).sort(),
1382
+ skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
1383
+ };
1384
+ };
1385
+ let newlyStagedPaths = [];
1386
+ const stageAndCommitWorkspace = () => {
1387
+ git(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
1388
+ newlyStagedPaths = stagedPaths(workspacePath);
1389
+ if (newlyStagedPaths.length > 0) {
1390
+ git(
1391
+ workspacePath,
1392
+ [
1393
+ "commit",
1394
+ "-m",
1395
+ JSON.stringify({ type: "workspace_sync", worker: input.workerLabel, attemptId, detail: input.commitDetail ?? null })
1396
+ ],
1397
+ "commit workspace working trees"
1398
+ );
1399
+ }
1400
+ };
433
1401
  if (!input.skipMountMirror) {
434
- mirrorMountsToWorkspace(workspacePath, selected.active);
435
- removeWorkspaceMounts(workspacePath, selected.tombstones);
1402
+ beginWorkspaceCheckoutTransition(workspacePath);
1403
+ try {
1404
+ mirrorMountsToWorkspace(workspacePath, projectedActiveMounts);
1405
+ removeWorkspaceMounts(workspacePath, projectedTombstones);
1406
+ restoreDeferredWorkspaceMountsFromHead(
1407
+ workspacePath,
1408
+ input.mounts.filter(({ id }) => deferredMountIds.has(id))
1409
+ );
1410
+ stageAndCommitWorkspace();
1411
+ fsyncWorkspaceCheckoutTree(workspacePath);
1412
+ } catch (error) {
1413
+ throwAfterWorkspaceCheckoutRecovery(workspacePath, error);
1414
+ }
1415
+ } else {
1416
+ stageAndCommitWorkspace();
1417
+ if (newlyStagedPaths.length > 0) completeWorkspaceCheckoutTransition(workspacePath);
436
1418
  }
437
- git(workspacePath, ["add", "-A", "--", "."], "stage workspace working trees");
438
- const newlyStagedPaths = stagedPaths(workspacePath);
439
- if (newlyStagedPaths.length > 0) {
440
- git(
441
- workspacePath,
442
- [
443
- "commit",
444
- "-m",
445
- JSON.stringify({ type: "workspace_sync", worker: input.workerLabel, attemptId, detail: input.commitDetail ?? null })
446
- ],
447
- "commit workspace working trees"
448
- );
1419
+ if (!input.skipMountMirror) {
1420
+ const projectedHead = revParse(workspacePath, "HEAD");
1421
+ if (!projectedHead) throw new Error("Workspace projection did not retain a local HEAD");
1422
+ const targetProjectionReceipt = workspaceHydrationReceipt(projectedHead, projectionReceiptMounts);
1423
+ if (!workspaceHydrationReceiptsEqual(readHydratedWorkspaceReceipt(workspacePath), targetProjectionReceipt)) {
1424
+ fsyncHydratedWorkspaceMounts([...projectedActiveMounts, ...projectedTombstones]);
1425
+ updateHydratedWorkspaceReceipt(workspacePath, targetProjectionReceipt);
1426
+ }
1427
+ completeWorkspaceCheckoutTransition(workspacePath);
449
1428
  }
450
1429
  let remoteHead = fetchWorkspaceHead(workspacePath, input.remoteUrl, input.credentialHelper, input.credentialUsername);
451
1430
  let rebaseCount = 0;
@@ -463,8 +1442,8 @@ async function synchronizeWorkspaceGit(input) {
463
1442
  rebaseCount: rebaseCount + 1,
464
1443
  diffSizeBytes: 0,
465
1444
  affectedPaths: [.../* @__PURE__ */ new Set([...newlyStagedPaths, ...reconciled.conflictPaths])].sort(),
466
- activeMountIds: [...selected.active, ...selected.tombstones].map(({ id }) => id).sort(),
467
- skippedMountIds: selected.skipped.map(({ id }) => id).sort(),
1445
+ activeMountIds: [...projectedActiveMounts, ...projectedTombstones].map(({ id }) => id).sort(),
1446
+ skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...deferredMountIds])].sort(),
468
1447
  conflictPaths: reconciled.conflictPaths,
469
1448
  conflictSnapshotRefs: reconciled.refs,
470
1449
  error: reconciled.error
@@ -472,6 +1451,7 @@ async function synchronizeWorkspaceGit(input) {
472
1451
  }
473
1452
  updated ||= reconciled.updated;
474
1453
  if (reconciled.rebased) rebaseCount += 1;
1454
+ if (reconciled.updated || reconciled.rebased) completedMountSelection(true);
475
1455
  const localHead = revParse(workspacePath, "HEAD");
476
1456
  if (!localHead) {
477
1457
  return {
@@ -505,11 +1485,11 @@ async function synchronizeWorkspaceGit(input) {
505
1485
  };
506
1486
  }
507
1487
  if (localHead === remoteHead) {
1488
+ const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
508
1489
  await input.afterWorkspacePublished?.({
509
1490
  publishedHead: localHead,
510
- activeMountIds: [...selected.active, ...selected.tombstones].map(({ id }) => id).sort()
1491
+ activeMountIds: completedMounts.activeMountIds
511
1492
  });
512
- if (!input.skipMountHydration) hydrateWorkspaceGitMounts(workspacePath, selected.active);
513
1493
  return {
514
1494
  outcome: updated ? "updated" : "no_change",
515
1495
  startingHead,
@@ -519,8 +1499,7 @@ async function synchronizeWorkspaceGit(input) {
519
1499
  rebaseCount,
520
1500
  diffSizeBytes: size,
521
1501
  affectedPaths: paths,
522
- activeMountIds: [...selected.active, ...selected.tombstones].map(({ id }) => id).sort(),
523
- skippedMountIds: selected.skipped.map(({ id }) => id).sort()
1502
+ ...completedMounts
524
1503
  };
525
1504
  }
526
1505
  const pushArgs = [
@@ -533,11 +1512,11 @@ async function synchronizeWorkspaceGit(input) {
533
1512
  const push = gitResult(workspacePath, pushArgs);
534
1513
  if (push.exitCode === 0) {
535
1514
  updateIntegratedWorkspaceHead(workspacePath, localHead);
1515
+ const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
536
1516
  await input.afterWorkspacePublished?.({
537
1517
  publishedHead: localHead,
538
- activeMountIds: [...selected.active, ...selected.tombstones].map(({ id }) => id).sort()
1518
+ activeMountIds: completedMounts.activeMountIds
539
1519
  });
540
- if (!input.skipMountHydration) hydrateWorkspaceGitMounts(workspacePath, selected.active);
541
1520
  return {
542
1521
  outcome: "pushed",
543
1522
  startingHead,
@@ -547,8 +1526,7 @@ async function synchronizeWorkspaceGit(input) {
547
1526
  rebaseCount,
548
1527
  diffSizeBytes: size,
549
1528
  affectedPaths: paths,
550
- activeMountIds: [...selected.active, ...selected.tombstones].map(({ id }) => id).sort(),
551
- skippedMountIds: selected.skipped.map(({ id }) => id).sort()
1529
+ ...completedMounts
552
1530
  };
553
1531
  }
554
1532
  if (!/(non-fast-forward|fetch first|rejected|failed to push some refs)/i.test(`${push.stderr}
@@ -570,10 +1548,15 @@ const workspaceGitSyncTestHarness = {
570
1548
  0 && (module.exports = {
571
1549
  MAX_WORKSPACE_GIT_DIFF_BYTES,
572
1550
  WORKSPACE_GIT_BRANCH,
1551
+ WORKSPACE_GIT_CHECKOUT_DURABILITY,
573
1552
  WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION,
1553
+ WORKSPACE_GIT_HYDRATED_RECEIPT,
1554
+ WORKSPACE_GIT_HYDRATION_TRANSACTION,
574
1555
  ensureWorkspaceGitClone,
575
1556
  hydrateWorkspaceGitMounts,
1557
+ recoverWorkspaceGitHydration,
576
1558
  resetWorkspaceGit,
577
1559
  synchronizeWorkspaceGit,
1560
+ workspaceGitHydrationIsCurrent,
578
1561
  workspaceGitSyncTestHarness
579
1562
  });