@ricsam/r5d-worker 0.0.82 → 0.0.83

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.83",
4
4
  "type": "commonjs"
5
5
  }
@@ -34,6 +34,8 @@ __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,
@@ -56,6 +58,12 @@ const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
56
58
  const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
57
59
  const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
58
60
  const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
61
+ class WorkspaceRemediationAncestryError extends Error {
62
+ constructor(message) {
63
+ super(message);
64
+ this.name = "WorkspaceRemediationAncestryError";
65
+ }
66
+ }
59
67
  const NON_RECURSIVE_GIT_CONFIG = [
60
68
  "-c",
61
69
  "submodule.recurse=false",
@@ -95,6 +103,29 @@ function revParse(workspacePath, revision) {
95
103
  const result = gitResult(workspacePath, ["rev-parse", "--verify", revision]);
96
104
  return result.exitCode === 0 ? result.stdout : null;
97
105
  }
106
+ function requiredWorkspaceAncestorHeads(value) {
107
+ if (value === void 0) return [];
108
+ 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) {
109
+ throw new WorkspaceRemediationAncestryError("Workspace remediation ancestor requirements are invalid");
110
+ }
111
+ return [...value];
112
+ }
113
+ function assertWorkspaceRemediationAncestry(input) {
114
+ if (input.requiredAncestorHeads.length === 0) return;
115
+ if (!input.currentHead) throw new WorkspaceRemediationAncestryError("Workspace remediation has no resolved HEAD to verify");
116
+ for (const requiredHead of input.requiredAncestorHeads) {
117
+ if (!tryGit(input.workspacePath, ["cat-file", "-e", `${requiredHead}^{commit}`]) || !tryGit(input.workspacePath, ["merge-base", "--is-ancestor", requiredHead, input.currentHead])) {
118
+ throw new WorkspaceRemediationAncestryError(
119
+ `Workspace remediation HEAD does not descend from required conflict commit ${requiredHead}`
120
+ );
121
+ }
122
+ }
123
+ if (!input.remoteHead || !tryGit(input.workspacePath, ["cat-file", "-e", `${input.remoteHead}^{commit}`]) || !tryGit(input.workspacePath, ["merge-base", "--is-ancestor", input.remoteHead, input.currentHead])) {
124
+ throw new WorkspaceRemediationAncestryError(
125
+ "Fetched workspace head is not an ancestor of the resolved remediation HEAD; merge it explicitly before synchronizing"
126
+ );
127
+ }
128
+ }
98
129
  function updateIntegratedWorkspaceHead(workspacePath, head) {
99
130
  git(workspacePath, ["update-ref", WORKSPACE_GIT_INTEGRATED_REF, head], "record integrated workspace head");
100
131
  }
@@ -964,6 +995,15 @@ function configureWorkspaceRepository(input) {
964
995
  );
965
996
  git(input.workspacePath, ["config", "--local", "core.fsyncMethod", "fsync"], "configure workspace fsync method");
966
997
  }
998
+ function configureExistingWorkspaceGitForRemediation(input) {
999
+ const workspacePath = import_node_path.default.resolve(input.workspacePath);
1000
+ const workspaceStatus = lstatIfExists(workspacePath);
1001
+ const gitStatus = lstatIfExists(import_node_path.default.join(workspacePath, ".git"));
1002
+ if (!workspaceStatus?.isDirectory() || workspaceStatus.isSymbolicLink() || !gitStatus?.isDirectory() || gitStatus.isSymbolicLink()) {
1003
+ throw new Error("Active workspace remediation requires a regular existing canonical synchronization checkout");
1004
+ }
1005
+ configureWorkspaceRepository({ ...input, workspacePath });
1006
+ }
967
1007
  function fetchWorkspaceHead(workspacePath, remoteUrl, credentialHelper, credentialUsername) {
968
1008
  const result = gitResult(workspacePath, [
969
1009
  ...(0, import_git_process_environment.gitTransportSecurityArgs)(remoteUrl, credentialHelper, credentialUsername),
@@ -1465,8 +1505,16 @@ async function synchronizeWorkspaceGit(input) {
1465
1505
  const maxDiffBytes = input.maxDiffBytes ?? MAX_WORKSPACE_GIT_DIFF_BYTES;
1466
1506
  const maxPushAttempts = Math.max(1, input.maxPushAttempts ?? 4);
1467
1507
  validateMounts(workspacePath, input.mounts);
1508
+ const requiredAncestorHeads = requiredWorkspaceAncestorHeads(input.requiredAncestorHeads);
1468
1509
  const preserveResolutionInProgress = input.skipMountMirror === true;
1469
1510
  const initial = ensureWorkspaceGitClone({ ...input, workspacePath, preserveResolutionInProgress });
1511
+ assertWorkspaceRemediationAncestry({
1512
+ workspacePath,
1513
+ currentHead: initial.localHead,
1514
+ remoteHead: initial.remoteHead,
1515
+ requiredAncestorHeads
1516
+ });
1517
+ const verifiedAncestorHeads = requiredAncestorHeads.length > 0 ? requiredAncestorHeads : void 0;
1470
1518
  const startingHead = initial.localHead;
1471
1519
  const receiptBeforeInitialHydration = readHydratedWorkspaceReceipt(workspacePath);
1472
1520
  const deferredMountIds = /* @__PURE__ */ new Set();
@@ -1558,6 +1606,7 @@ async function synchronizeWorkspaceGit(input) {
1558
1606
  conflictPaths: merged.conflictPaths,
1559
1607
  conflictSnapshotRefs: refs,
1560
1608
  conflictKind: "projection_merge",
1609
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1561
1610
  error: merged.error
1562
1611
  };
1563
1612
  }
@@ -1681,6 +1730,12 @@ async function synchronizeWorkspaceGit(input) {
1681
1730
  let rebaseCount = 0;
1682
1731
  let updated = false;
1683
1732
  for (let pushAttempt = 0; pushAttempt < maxPushAttempts; pushAttempt += 1) {
1733
+ assertWorkspaceRemediationAncestry({
1734
+ workspacePath,
1735
+ currentHead: revParse(workspacePath, "HEAD"),
1736
+ remoteHead,
1737
+ requiredAncestorHeads
1738
+ });
1684
1739
  const reconciled = synchronizeWithFetchedHead({ workspacePath, remoteHead, attemptId });
1685
1740
  if (reconciled.kind === "conflict") {
1686
1741
  const localHead2 = revParse(workspacePath, "HEAD");
@@ -1698,6 +1753,7 @@ async function synchronizeWorkspaceGit(input) {
1698
1753
  conflictPaths: reconciled.conflictPaths,
1699
1754
  conflictSnapshotRefs: reconciled.refs,
1700
1755
  conflictKind: "integration_rebase",
1756
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1701
1757
  error: reconciled.error
1702
1758
  };
1703
1759
  }
@@ -1717,11 +1773,13 @@ async function synchronizeWorkspaceGit(input) {
1717
1773
  diffSizeBytes: 0,
1718
1774
  affectedPaths: [],
1719
1775
  ...classifiedMounts,
1776
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1720
1777
  ...projectionWarning ? { error: projectionWarning } : {}
1721
1778
  };
1722
1779
  }
1723
1780
  const paths = changedPaths(workspacePath, remoteHead, localHead);
1724
- const size = paths.length > 0 ? await diffSizeBytes(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
1781
+ const size = paths.length > 0 ? await (input.measureDiffSize ?? diffSizeBytes)(workspacePath, remoteHead, localHead, maxDiffBytes) : 0;
1782
+ input.assertStillAdmitted?.();
1725
1783
  if (paths.length > 0 && size > maxDiffBytes && !input.allowLargeDiff) {
1726
1784
  const classifiedMounts = classifyMounts([...selected.active, ...selected.tombstones]);
1727
1785
  return {
@@ -1734,15 +1792,18 @@ async function synchronizeWorkspaceGit(input) {
1734
1792
  diffSizeBytes: size,
1735
1793
  affectedPaths: paths,
1736
1794
  ...classifiedMounts,
1795
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1737
1796
  error: `Workspace diff exceeds the ${maxDiffBytes}-byte automatic publication limit`
1738
1797
  };
1739
1798
  }
1740
1799
  if (localHead === remoteHead) {
1800
+ assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
1741
1801
  const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
1742
1802
  await input.afterWorkspacePublished?.({
1743
1803
  publishedHead: localHead,
1744
1804
  activeMountIds: completedMounts.activeMountIds
1745
1805
  });
1806
+ input.assertStillAdmitted?.();
1746
1807
  return {
1747
1808
  outcome: updated ? "updated" : "no_change",
1748
1809
  startingHead,
@@ -1753,9 +1814,11 @@ async function synchronizeWorkspaceGit(input) {
1753
1814
  diffSizeBytes: size,
1754
1815
  affectedPaths: paths,
1755
1816
  ...completedMounts,
1817
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1756
1818
  ...projectionWarning ? { error: projectionWarning } : {}
1757
1819
  };
1758
1820
  }
1821
+ assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
1759
1822
  const pushArgs = [
1760
1823
  ...(0, import_git_process_environment.gitTransportSecurityArgs)(input.remoteUrl, input.credentialHelper, input.credentialUsername),
1761
1824
  "push",
@@ -1765,12 +1828,14 @@ async function synchronizeWorkspaceGit(input) {
1765
1828
  pushArgs.push("origin", `HEAD:refs/heads/${WORKSPACE_GIT_BRANCH}`);
1766
1829
  const push = gitResult(workspacePath, pushArgs);
1767
1830
  if (push.exitCode === 0) {
1831
+ assertWorkspaceRemediationAncestry({ workspacePath, currentHead: localHead, remoteHead, requiredAncestorHeads });
1768
1832
  updateIntegratedWorkspaceHead(workspacePath, localHead);
1769
1833
  const completedMounts = completedMountSelection(Boolean(input.skipMountMirror));
1770
1834
  await input.afterWorkspacePublished?.({
1771
1835
  publishedHead: localHead,
1772
1836
  activeMountIds: completedMounts.activeMountIds
1773
1837
  });
1838
+ input.assertStillAdmitted?.();
1774
1839
  return {
1775
1840
  outcome: "pushed",
1776
1841
  startingHead,
@@ -1781,6 +1846,7 @@ async function synchronizeWorkspaceGit(input) {
1781
1846
  diffSizeBytes: size,
1782
1847
  affectedPaths: paths,
1783
1848
  ...completedMounts,
1849
+ ...verifiedAncestorHeads ? { verifiedAncestorHeads } : {},
1784
1850
  ...projectionWarning ? { error: projectionWarning } : {}
1785
1851
  };
1786
1852
  }
@@ -1807,6 +1873,8 @@ const workspaceGitSyncTestHarness = {
1807
1873
  WORKSPACE_GIT_CONFIRMED_LARGE_DIFF_PUSH_OPTION,
1808
1874
  WORKSPACE_GIT_HYDRATED_RECEIPT,
1809
1875
  WORKSPACE_GIT_HYDRATION_TRANSACTION,
1876
+ WorkspaceRemediationAncestryError,
1877
+ configureExistingWorkspaceGitForRemediation,
1810
1878
  ensureWorkspaceGitClone,
1811
1879
  hydrateWorkspaceGitMounts,
1812
1880
  recoverWorkspaceGitHydration,