@ricsam/r5d-worker 0.0.82 → 0.0.86

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.82",
3
+ "version": "0.0.86",
4
4
  "type": "commonjs"
5
5
  }
@@ -23,7 +23,9 @@ __export(workspace_automatic_sync_policy_exports, {
23
23
  pendingCreatedBranchAutomaticSyncDeferral: () => pendingCreatedBranchAutomaticSyncDeferral,
24
24
  pendingCreatedBranchKey: () => pendingCreatedBranchKey,
25
25
  projectBranchHasActiveWorkspaceTarget: () => projectBranchHasActiveWorkspaceTarget,
26
- shouldDeferAutomaticWorkspaceSyncForPendingBranch: () => shouldDeferAutomaticWorkspaceSyncForPendingBranch
26
+ projectBranchMountBusyDuringExclusiveSync: () => projectBranchMountBusyDuringExclusiveSync,
27
+ shouldDeferAutomaticWorkspaceSyncForPendingBranch: () => shouldDeferAutomaticWorkspaceSyncForPendingBranch,
28
+ workspacePlansMountBusyDuringExclusiveSync: () => workspacePlansMountBusyDuringExclusiveSync
27
29
  });
28
30
  module.exports = __toCommonJS(workspace_automatic_sync_policy_exports);
29
31
  const PENDING_CREATED_BRANCH_AUTOMATIC_SYNC_GRACE_MS = 12e4;
@@ -43,6 +45,12 @@ function projectBranchHasActiveWorkspaceTarget(projectId, branchName, activeTarg
43
45
  }
44
46
  return false;
45
47
  }
48
+ function projectBranchMountBusyDuringExclusiveSync(input) {
49
+ return input.executionDisabled || input.worktreeOperationInProgress || input.retainedCanonicalProcessGroup || projectBranchHasActiveWorkspaceTarget(input.projectId, input.branchName, input.activeTargets);
50
+ }
51
+ function workspacePlansMountBusyDuringExclusiveSync(activeTargets, retainedCanonicalProcessGroup = false) {
52
+ return retainedCanonicalProcessGroup || hasActiveVisibleProjectsWorkspaceTarget(activeTargets);
53
+ }
46
54
  function shouldDeferAutomaticWorkspaceSyncForPendingBranch(pendingCreatedBranches, activeTargets) {
47
55
  if (pendingCreatedBranches.length === 0) return false;
48
56
  const targets = [...activeTargets];
@@ -65,5 +73,7 @@ function pendingCreatedBranchAutomaticSyncDeferral(pendingCreatedBranches, activ
65
73
  pendingCreatedBranchAutomaticSyncDeferral,
66
74
  pendingCreatedBranchKey,
67
75
  projectBranchHasActiveWorkspaceTarget,
68
- shouldDeferAutomaticWorkspaceSyncForPendingBranch
76
+ projectBranchMountBusyDuringExclusiveSync,
77
+ shouldDeferAutomaticWorkspaceSyncForPendingBranch,
78
+ workspacePlansMountBusyDuringExclusiveSync
69
79
  });
@@ -20,6 +20,7 @@ var workspace_command_sync_policy_exports = {};
20
20
  __export(workspace_command_sync_policy_exports, {
21
21
  acquireWorkspaceCommandMutation: () => acquireWorkspaceCommandMutation,
22
22
  reserveWorkspaceCommandAfterCurrentSync: () => reserveWorkspaceCommandAfterCurrentSync,
23
+ runReservedWorkspaceCommand: () => runReservedWorkspaceCommand,
23
24
  runWorkspaceCommand: () => runWorkspaceCommand,
24
25
  shouldSerializeWorkspaceCommand: () => shouldSerializeWorkspaceCommand
25
26
  });
@@ -46,6 +47,25 @@ function shouldSerializeWorkspaceCommand(target) {
46
47
  function runWorkspaceCommand(target, coordinator, operation) {
47
48
  return shouldSerializeWorkspaceCommand(target) ? coordinator.runMutation(operation) : Promise.resolve().then(operation);
48
49
  }
50
+ async function runReservedWorkspaceCommand(target, coordinator, operation, releaseReservation) {
51
+ let released = false;
52
+ const releaseOnce = () => {
53
+ if (released) return;
54
+ released = true;
55
+ releaseReservation();
56
+ };
57
+ try {
58
+ return await runWorkspaceCommand(target, coordinator, async () => {
59
+ try {
60
+ return await operation();
61
+ } finally {
62
+ releaseOnce();
63
+ }
64
+ });
65
+ } finally {
66
+ releaseOnce();
67
+ }
68
+ }
49
69
  function acquireWorkspaceCommandMutation(target, coordinator) {
50
70
  return shouldSerializeWorkspaceCommand(target) ? coordinator.acquireMutation() : Promise.resolve(void 0);
51
71
  }
@@ -53,6 +73,7 @@ function acquireWorkspaceCommandMutation(target, coordinator) {
53
73
  0 && (module.exports = {
54
74
  acquireWorkspaceCommandMutation,
55
75
  reserveWorkspaceCommandAfterCurrentSync,
76
+ runReservedWorkspaceCommand,
56
77
  runWorkspaceCommand,
57
78
  shouldSerializeWorkspaceCommand
58
79
  });
@@ -34,12 +34,15 @@ __export(workspace_git_sync_exports, {
34
34
  WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION: () => WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION,
35
35
  WORKSPACE_GIT_HYDRATED_RECEIPT: () => WORKSPACE_GIT_HYDRATED_RECEIPT,
36
36
  WORKSPACE_GIT_HYDRATION_TRANSACTION: () => WORKSPACE_GIT_HYDRATION_TRANSACTION,
37
+ WorkspaceRemediationAncestryError: () => WorkspaceRemediationAncestryError,
38
+ configureExistingWorkspaceGitForRemediation: () => configureExistingWorkspaceGitForRemediation,
37
39
  ensureWorkspaceGitClone: () => ensureWorkspaceGitClone,
38
40
  hydrateWorkspaceGitMounts: () => hydrateWorkspaceGitMounts,
39
41
  recoverWorkspaceGitHydration: () => recoverWorkspaceGitHydration,
40
42
  resetWorkspaceGit: () => resetWorkspaceGit,
41
43
  synchronizeWorkspaceGit: () => synchronizeWorkspaceGit,
42
44
  workspaceGitHydrationIsCurrent: () => workspaceGitHydrationIsCurrent,
45
+ workspaceGitMountsForRemediation: () => workspaceGitMountsForRemediation,
43
46
  workspaceGitSyncTestHarness: () => workspaceGitSyncTestHarness
44
47
  });
45
48
  module.exports = __toCommonJS(workspace_git_sync_exports);
@@ -56,6 +59,15 @@ const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
56
59
  const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
57
60
  const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
58
61
  const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
62
+ function workspaceGitMountsForRemediation(mounts) {
63
+ return mounts.map((mount) => ({ ...mount, busy: mount.busyForRecovery ?? mount.busy }));
64
+ }
65
+ class WorkspaceRemediationAncestryError extends Error {
66
+ constructor(message) {
67
+ super(message);
68
+ this.name = "WorkspaceRemediationAncestryError";
69
+ }
70
+ }
59
71
  const NON_RECURSIVE_GIT_CONFIG = [
60
72
  "-c",
61
73
  "submodule.recurse=false",
@@ -95,6 +107,29 @@ function revParse(workspacePath, revision) {
95
107
  const result = gitResult(workspacePath, ["rev-parse", "--verify", revision]);
96
108
  return result.exitCode === 0 ? result.stdout : null;
97
109
  }
110
+ function requiredWorkspaceAncestorHeads(value) {
111
+ if (value === void 0) return [];
112
+ if (!Array.isArray(value) || value.length === 0 || value.some((head) => typeof head !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(head)) || new Set(value).size !== value.length) {
113
+ throw new WorkspaceRemediationAncestryError("Workspace remediation ancestor requirements are invalid");
114
+ }
115
+ return [...value];
116
+ }
117
+ function assertWorkspaceRemediationAncestry(input) {
118
+ if (input.requiredAncestorHeads.length === 0) return;
119
+ if (!input.currentHead) throw new WorkspaceRemediationAncestryError("Workspace remediation has no resolved HEAD to verify");
120
+ for (const requiredHead of input.requiredAncestorHeads) {
121
+ if (!tryGit(input.workspacePath, ["cat-file", "-e", `${requiredHead}^{commit}`]) || !tryGit(input.workspacePath, ["merge-base", "--is-ancestor", requiredHead, input.currentHead])) {
122
+ throw new WorkspaceRemediationAncestryError(
123
+ `Workspace remediation HEAD does not descend from required conflict commit ${requiredHead}`
124
+ );
125
+ }
126
+ }
127
+ if (!input.remoteHead || !tryGit(input.workspacePath, ["cat-file", "-e", `${input.remoteHead}^{commit}`]) || !tryGit(input.workspacePath, ["merge-base", "--is-ancestor", input.remoteHead, input.currentHead])) {
128
+ throw new WorkspaceRemediationAncestryError(
129
+ "Fetched workspace head is not an ancestor of the resolved remediation HEAD; merge it explicitly before synchronizing"
130
+ );
131
+ }
132
+ }
98
133
  function updateIntegratedWorkspaceHead(workspacePath, head) {
99
134
  git(workspacePath, ["update-ref", WORKSPACE_GIT_INTEGRATED_REF, head], "record integrated workspace head");
100
135
  }
@@ -964,6 +999,15 @@ function configureWorkspaceRepository(input) {
964
999
  );
965
1000
  git(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
966
1001
  }
1002
+ function configureExistingWorkspaceGitForRemediation(input) {
1003
+ const workspacePath = import_node_path.default.resolve(input.workspacePath);
1004
+ const workspaceStatus = lstatIfExists(workspacePath);
1005
+ const gitStatus = lstatIfExists(import_node_path.default.join(workspacePath, ".git"));
1006
+ if (!workspaceStatus?.isDirectory() || workspaceStatus.isSymbolicLink() || !gitStatus?.isDirectory() || gitStatus.isSymbolicLink()) {
1007
+ throw new Error("Active workspace remediation requires a regular existing canonical synchronization checkout");
1008
+ }
1009
+ configureWorkspaceRepository({ ...input, workspacePath });
1010
+ }
967
1011
  function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
968
1012
  const result = gitResult(workspacePath, [
969
1013
  ...(0, import_git_process_environment.gitTransportSecurityArgs)(remoteUrl, credentialHelper, credentialUsername),
@@ -1302,6 +1346,9 @@ function resetWorkspaceGit(input) {
1302
1346
  const outerPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
1303
1347
  return import_node_fs.default.existsSync(outerPath);
1304
1348
  });
1349
+ const bootstrapMountIds = new Set(bootstrapMounts.map(({ id }) => id));
1350
+ const durablyAbsentMounts = selected.skipped.filter((mount) => !bootstrapMountIds.has(mount.id) && mount.sourceMode === "all");
1351
+ const uncoveredSkippedMounts = selected.skipped.filter((mount) => !bootstrapMountIds.has(mount.id) && mount.sourceMode !== "all");
1305
1352
  const hydrationMounts = [...selected.active, ...bootstrapMounts];
1306
1353
  const requiredLiveMounts = [
1307
1354
  ...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
@@ -1309,7 +1356,7 @@ function resetWorkspaceGit(input) {
1309
1356
  const hydration = hydrateWorkspaceGitMountsTransactionally({
1310
1357
  workspacePath,
1311
1358
  mounts: hydrationMounts,
1312
- durabilityMounts: [...requiredLiveMounts, ...selected.tombstones],
1359
+ durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
1313
1360
  receiptMounts: input.mounts,
1314
1361
  requiredMounts: requiredLiveMounts,
1315
1362
  recordCurrentHead: true
@@ -1325,7 +1372,7 @@ function resetWorkspaceGit(input) {
1325
1372
  remoteHead,
1326
1373
  discardedPaths,
1327
1374
  activeMountIds: hydration.hydratedMountIds,
1328
- skippedMountIds: [.../* @__PURE__ */ new Set([...selected.skipped.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
1375
+ skippedMountIds: [.../* @__PURE__ */ new Set([...uncoveredSkippedMounts.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
1329
1376
  };
1330
1377
  }
1331
1378
  function stagedPaths(workspacePath) {
@@ -1465,8 +1512,16 @@ async function synchronizeWorkspaceGit(input) {
1465
1512
  const maxDiffBytes = input.maxDiffBytes ?? MAX_WORKSPACE_GIT_DIFF_BYTES;
1466
1513
  const maxPushAttempts = Math.max(1, input.maxPushAttempts ?? 4);
1467
1514
  validateMounts(workspacePath, input.mounts);
1515
+ const requiredAncestorHeads = requiredWorkspaceAncestorHeads(input.requiredAncestorHeads);
1468
1516
  const preserveResolutionInProgress = input.skipMountMirror === true;
1469
1517
  const initial = ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
1518
+ assertWorkspaceRemediationAncestry({
1519
+ workspacePath,
1520
+ currentHead: initial.localHead,
1521
+ remoteHead: initial.remoteHead,
1522
+ requiredAncestorHeads
1523
+ });
1524
+ const verifiedAncestorHeads = requiredAncestorHeads.length > 0 ? requiredAncestorHeads : void 0;
1470
1525
  const startingHead = initial.localHead;
1471
1526
  const receiptBeforeInitialHydration = readHydratedWorkspaceReceipt(workspacePath);
1472
1527
  const deferredMountIds = /* @__PURE__ */ new Set();
@@ -1558,6 +1613,7 @@ async function synchronizeWorkspaceGit(input) {
1558
1613
  conflictPaths: merged.conflictPaths,
1559
1614
  conflictSnapshotRefs: refs,
1560
1615
  conflictKind: "projection_merge",
1616
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1561
1617
  error: merged.error
1562
1618
  };
1563
1619
  }
@@ -1681,6 +1737,12 @@ async function synchronizeWorkspaceGit(input) {
1681
1737
  let rebaseCount = 0;
1682
1738
  let updated = false;
1683
1739
  for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
1740
+ assertWorkspaceRemediationAncestry({
1741
+ workspacePath,
1742
+ currentHead: revParse(workspacePath, "HEAD"),
1743
+ remoteHead,
1744
+ requiredAncestorHeads
1745
+ });
1684
1746
  const reconciled = synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
1685
1747
  if (reconciled.kind === "conflict") {
1686
1748
  const localHead2 = revParse(workspacePath, "HEAD");
@@ -1698,6 +1760,7 @@ async function synchronizeWorkspaceGit(input) {
1698
1760
  conflictPaths: reconciled.conflictPaths,
1699
1761
  conflictSnapshotRefs: reconciled.refs,
1700
1762
  conflictKind: "integration_rebase",
1763
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1701
1764
  error: reconciled.error
1702
1765
  };
1703
1766
  }
@@ -1717,11 +1780,13 @@ async function synchronizeWorkspaceGit(input) {
1717
1780
  diffSizeBytes: 0,
1718
1781
  affectedPaths: [],
1719
1782
  ...classifiedMounts,
1783
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1720
1784
  ...projectionWarning ? { error: projectionWarning } : {}
1721
1785
  };
1722
1786
  }
1723
1787
  const paths = changedPaths(workspacePath, remoteHead, localHead);
1724
- const size = paths.length > 0 ? await diffSizeBytes(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
1788
+ const size = paths.length > 0 ? await (input.measureDiffSize ?? diffSizeBytes)(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
1789
+ input.assertStillAdmitted?.();
1725
1790
  if (paths.length > 0 && size > maxDiffBytes && !input.allowLargeDiff) {
1726
1791
  const classifiedMounts = classifyMounts([...selected.active, ...selected.tombstones]);
1727
1792
  return {
@@ -1734,15 +1799,18 @@ async function synchronizeWorkspaceGit(input) {
1734
1799
  diffSizeBytes: size,
1735
1800
  affectedPaths: paths,
1736
1801
  ...classifiedMounts,
1802
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1737
1803
  error: `Workspace diff exceeds the ${maxDiffBytes}-byte automatic publication limit`
1738
1804
  };
1739
1805
  }
1740
1806
  if (localHead === remoteHead) {
1807
+ assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
1741
1808
  const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
1742
1809
  await input.afterWorkspacePublished?.({
1743
1810
  publishedHead: localHead,
1744
1811
  activeMountIds: completedMounts.activeMountIds
1745
1812
  });
1813
+ input.assertStillAdmitted?.();
1746
1814
  return {
1747
1815
  outcome: updated ? "updated" : "no_change",
1748
1816
  startingHead,
@@ -1753,9 +1821,11 @@ async function synchronizeWorkspaceGit(input) {
1753
1821
  diffSizeBytes: size,
1754
1822
  affectedPaths: paths,
1755
1823
  ...completedMounts,
1824
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1756
1825
  ...projectionWarning ? { error: projectionWarning } : {}
1757
1826
  };
1758
1827
  }
1828
+ assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
1759
1829
  const pushArgs = [
1760
1830
  ...(0, import_git_process_environment.gitTransportSecurityArgs)(input.remoteUrl, input.credentialHelper, input.credentialUsername),
1761
1831
  "push",
@@ -1765,12 +1835,14 @@ async function synchronizeWorkspaceGit(input) {
1765
1835
  pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
1766
1836
  const push = gitResult(workspacePath, pushArgs);
1767
1837
  if (push.exitCode === 0) {
1838
+ assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
1768
1839
  updateIntegratedWorkspaceHead(workspacePath, localHead);
1769
1840
  const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
1770
1841
  await input.afterWorkspacePublished?.({
1771
1842
  publishedHead: localHead,
1772
1843
  activeMountIds: completedMounts.activeMountIds
1773
1844
  });
1845
+ input.assertStillAdmitted?.();
1774
1846
  return {
1775
1847
  outcome: "pushed",
1776
1848
  startingHead,
@@ -1781,6 +1853,7 @@ async function synchronizeWorkspaceGit(input) {
1781
1853
  diffSizeBytes: size,
1782
1854
  affectedPaths: paths,
1783
1855
  ...completedMounts,
1856
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1784
1857
  ...projectionWarning ? { error: projectionWarning } : {}
1785
1858
  };
1786
1859
  }
@@ -1807,11 +1880,14 @@ const workspaceGitSyncTestHarness = {
1807
1880
  WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION,
1808
1881
  WORKSPACE_GIT_HYDRATED_RECEIPT,
1809
1882
  WORKSPACE_GIT_HYDRATION_TRANSACTION,
1883
+ WorkspaceRemediationAncestryError,
1884
+ configureExistingWorkspaceGitForRemediation,
1810
1885
  ensureWorkspaceGitClone,
1811
1886
  hydrateWorkspaceGitMounts,
1812
1887
  recoverWorkspaceGitHydration,
1813
1888
  resetWorkspaceGit,
1814
1889
  synchronizeWorkspaceGit,
1815
1890
  workspaceGitHydrationIsCurrent,
1891
+ workspaceGitMountsForRemediation,
1816
1892
  workspaceGitSyncTestHarness
1817
1893
  });