@ricsam/r5d-worker 0.0.83 → 0.0.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mjs/main.mjs CHANGED
@@ -28,11 +28,14 @@ import {
28
28
  PENDING_CREATED_BRANCH_AUTOMATIC_SYNC_GRACE_MS,
29
29
  pendingCreatedBranchAutomaticSyncDeferral,
30
30
  pendingCreatedBranchKey,
31
- projectBranchHasActiveWorkspaceTarget
31
+ projectBranchHasActiveWorkspaceTarget,
32
+ projectBranchMountBusyDuringExclusiveSync,
33
+ workspacePlansMountBusyDuringExclusiveSync
32
34
  } from "./workspace-automatic-sync-policy.mjs";
33
35
  import {
34
36
  acquireWorkspaceCommandMutation,
35
37
  reserveWorkspaceCommandAfterCurrentSync,
38
+ runReservedWorkspaceCommand,
36
39
  runWorkspaceCommand
37
40
  } from "./workspace-command-sync-policy.mjs";
38
41
  import { WorkspaceMutationGate } from "./workspace-mutation-gate.mjs";
@@ -86,6 +89,7 @@ import {
86
89
  resetWorkspaceGit,
87
90
  synchronizeWorkspaceGit,
88
91
  workspaceGitHydrationIsCurrent,
92
+ workspaceGitMountsForRemediation,
89
93
  WorkspaceRemediationAncestryError
90
94
  } from "./workspace-git-sync.mjs";
91
95
  class ProjectWorkspaceConfigurationDeferredError extends Error {
@@ -843,6 +847,33 @@ function tryGit(args, options = {}) {
843
847
  return false;
844
848
  }
845
849
  }
850
+ async function runGitAsync(args, options = {}) {
851
+ const command = workerGitCommand.commandArgs(args);
852
+ const subprocess = Bun.spawn(command, {
853
+ cwd: options.cwd,
854
+ stdin: "ignore",
855
+ stdout: "pipe",
856
+ stderr: "pipe",
857
+ env: workerGitProcessEnvironment()
858
+ });
859
+ const [stdout, stderr, exitCode] = await Promise.all([
860
+ new Response(subprocess.stdout).text(),
861
+ new Response(subprocess.stderr).text(),
862
+ subprocess.exited
863
+ ]);
864
+ if (exitCode !== 0) {
865
+ throw new Error(`git ${args.join(" ")} failed: ${stderr.trim() || stdout.trim() || `exit ${exitCode}`}`);
866
+ }
867
+ return stdout.trim();
868
+ }
869
+ async function tryGitAsync(args, options = {}) {
870
+ try {
871
+ await runGitAsync(args, options);
872
+ return true;
873
+ } catch {
874
+ return false;
875
+ }
876
+ }
846
877
  function getGitBlobHashForContent(content) {
847
878
  const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : content;
848
879
  return createHash("sha1").update(`blob ${buffer.length}\0`).update(buffer).digest("hex");
@@ -1539,10 +1570,20 @@ function workspaceConfigurationIncidentDeferral(input) {
1539
1570
  return { incidentId };
1540
1571
  }
1541
1572
  function workspaceIncidentTerminalClearDisposition(input) {
1542
- if (!input.deferredConfiguration) return "ordinary_resume";
1543
- void input.clearedIncidentId;
1573
+ if (!input.deferredConfiguration) return "await_server_refresh";
1574
+ if (input.deferredConfiguration.incidentId !== input.clearedIncidentId) return "reconnect_for_refresh";
1544
1575
  return input.deferredConfiguration.serverRefreshExpected ? "await_server_refresh" : "reconnect_for_refresh";
1545
1576
  }
1577
+ function workspaceIncidentTerminalRefreshFence(input) {
1578
+ const disposition = workspaceIncidentTerminalClearDisposition(input);
1579
+ return {
1580
+ disposition,
1581
+ deferredConfiguration: input.deferredConfiguration ?? { incidentId: input.clearedIncidentId, serverRefreshExpected: true }
1582
+ };
1583
+ }
1584
+ function workspaceAutomaticSyncIsAdmitted(input) {
1585
+ return input.workspaceConfigured && input.activeIncidentId === null && input.deferredConfiguration === null;
1586
+ }
1546
1587
  function deferredWorkspaceTargetIsAllowed(deferredConfiguration, activeIncidentId, target) {
1547
1588
  const canonicalTarget = target.type === "workspace" && target.rootProfile === "canonical_sync";
1548
1589
  if (deferredConfiguration) return activeIncidentId === deferredConfiguration.incidentId && canonicalTarget;
@@ -1564,6 +1605,9 @@ function deferredCredentialTransitionMustWait(input) {
1564
1605
  function workspaceConfigurationIncidentSnapshotIsCurrent(capturedIncidentId, activeIncidentId) {
1565
1606
  return capturedIncidentId === null ? activeIncidentId === null : activeIncidentId === null || activeIncidentId === capturedIncidentId;
1566
1607
  }
1608
+ function workspaceConfigurationResetToCanonicalIsAllowed(requested, incidentId) {
1609
+ return requested === true && incidentId === null;
1610
+ }
1567
1611
  function deferredWorkspaceRefreshWatchdogIsCurrent(input) {
1568
1612
  return input.deferredConfiguration?.incidentId === input.capturedIncidentId && input.activeIncidentId === null;
1569
1613
  }
@@ -1636,12 +1680,15 @@ const workerGitSecurityTestHarness = {
1636
1680
  fenceUnsafeWorkspaceSyncFailure,
1637
1681
  workspaceConfigurationIncidentDeferral,
1638
1682
  workspaceIncidentTerminalClearDisposition,
1683
+ workspaceIncidentTerminalRefreshFence,
1684
+ workspaceAutomaticSyncIsAdmitted,
1639
1685
  deferredWorkspaceTargetIsAllowed,
1640
1686
  deferredWorkspaceSyncTriggerIsAllowed,
1641
1687
  workspaceOperationsAreFenced,
1642
1688
  workspaceCommandTransportIsAllowed,
1643
1689
  deferredCredentialTransitionMustWait,
1644
1690
  workspaceConfigurationIncidentSnapshotIsCurrent,
1691
+ workspaceConfigurationResetToCanonicalIsAllowed,
1645
1692
  deferredWorkspaceRefreshWatchdogIsCurrent,
1646
1693
  incidentDeferredProjectCheckouts,
1647
1694
  deferredIncidentWorkspaceConfigurationResult,
@@ -3402,6 +3449,11 @@ async function startWorker(options) {
3402
3449
  let cliUpdateInProgress = false;
3403
3450
  let reloadAfterClose = false;
3404
3451
  let shutdownAfterClose = false;
3452
+ const automaticWorkspaceSyncIsCurrentlyAdmitted = () => workspaceAutomaticSyncIsAdmitted({
3453
+ workspaceConfigured,
3454
+ activeIncidentId: activeWorkspaceIncidentId,
3455
+ deferredConfiguration: deferredWorkspaceConfiguration
3456
+ });
3405
3457
  const bearerAuthHeader = `Authorization: Bearer ${token}`;
3406
3458
  const workerCredentialUsername = credentialUsernameForAuthHeader(bearerAuthHeader);
3407
3459
  const projectBranchKey = (projectId, branchName) => `${projectId}\0${branchName}`;
@@ -3683,6 +3735,17 @@ async function startWorker(options) {
3683
3735
  const activeTargets = activeWorkspaceMutationTargets();
3684
3736
  return projectConfigById.get(projectId)?.executionDisabled === true || hasProjectWorktree(branchPath) && projectWorktreeOperationInProgress(branchPath) || canonicalWorkspaceMutationIsActive(activeTargets) || projectBranchHasActiveWorkspaceTarget(projectId, branchName, activeTargets);
3685
3737
  };
3738
+ const projectBranchMountExclusiveSyncBusy = (projectId, branchName, branchPath) => {
3739
+ const activeTargets = activeWorkspaceMutationTargets();
3740
+ return projectBranchMountBusyDuringExclusiveSync({
3741
+ projectId,
3742
+ branchName,
3743
+ executionDisabled: projectConfigById.get(projectId)?.executionDisabled === true,
3744
+ worktreeOperationInProgress: hasProjectWorktree(branchPath) && projectWorktreeOperationInProgress(branchPath),
3745
+ retainedCanonicalProcessGroup: canonicalWorkspaceMutationIsActive([...credentialBearingProcessGroupTargets.values()]),
3746
+ activeTargets
3747
+ });
3748
+ };
3686
3749
  const projectBranchMountBusy = (projectId, branchName, branchPath) => !readyProjectIds.has(projectId) || projectBranchMountActivityBusy(projectId, branchName, branchPath);
3687
3750
  const buildWorkspaceMounts = (projects = projectConfigById.values()) => {
3688
3751
  const mounts = [];
@@ -3715,7 +3778,7 @@ async function startWorker(options) {
3715
3778
  preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
3716
3779
  busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
3717
3780
  mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
3718
- busyForRecovery: () => projectBranchMountActivityBusy(project.projectId, branch.branchName, branchPath)
3781
+ busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
3719
3782
  });
3720
3783
  mounts.push({
3721
3784
  id: projectPlanMountId(project.projectId, branch.branchName),
@@ -3729,7 +3792,7 @@ async function startWorker(options) {
3729
3792
  preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
3730
3793
  busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
3731
3794
  mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
3732
- busyForRecovery: () => projectBranchMountActivityBusy(project.projectId, branch.branchName, branchPath)
3795
+ busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
3733
3796
  });
3734
3797
  }
3735
3798
  void projectRoot;
@@ -3752,7 +3815,10 @@ async function startWorker(options) {
3752
3815
  mutationToken: () => String(visibleWorkspaceMutationEpoch),
3753
3816
  busyForRecovery: () => {
3754
3817
  const activeTargets = activeWorkspaceMutationTargets();
3755
- return canonicalWorkspaceMutationIsActive(activeTargets) || hasActiveVisibleProjectsWorkspaceTarget(activeTargets);
3818
+ return workspacePlansMountBusyDuringExclusiveSync(
3819
+ activeTargets,
3820
+ canonicalWorkspaceMutationIsActive([...credentialBearingProcessGroupTargets.values()])
3821
+ );
3756
3822
  }
3757
3823
  });
3758
3824
  for (const deletion of pendingMirrorDeletes.values()) {
@@ -3779,10 +3845,14 @@ async function startWorker(options) {
3779
3845
  }
3780
3846
  return mounts;
3781
3847
  };
3782
- const workspaceLocalHead = () => fs.existsSync(path.join(workspaceShadowRoot, ".git")) && tryGit(["rev-parse", "--verify", "HEAD"], { cwd: workspaceShadowRoot }) ? runGit(["rev-parse", "HEAD"], { cwd: workspaceShadowRoot }) : null;
3783
- const workspaceGitStatus = () => {
3848
+ const workspaceLocalHead = async () => {
3849
+ if (!fs.existsSync(path.join(workspaceShadowRoot, ".git"))) return null;
3850
+ if (!await tryGitAsync(["rev-parse", "--verify", "HEAD"], { cwd: workspaceShadowRoot })) return null;
3851
+ return runGitAsync(["rev-parse", "HEAD"], { cwd: workspaceShadowRoot });
3852
+ };
3853
+ const workspaceGitStatus = async () => {
3784
3854
  try {
3785
- return runGit(["status", "--porcelain"], { cwd: workspaceShadowRoot });
3855
+ return await runGitAsync(["status", "--porcelain"], { cwd: workspaceShadowRoot });
3786
3856
  } catch {
3787
3857
  return "";
3788
3858
  }
@@ -3795,7 +3865,7 @@ async function startWorker(options) {
3795
3865
  })
3796
3866
  )
3797
3867
  ].sort();
3798
- const ensureConfiguredProjects = () => {
3868
+ const ensureConfiguredProjects = async () => {
3799
3869
  for (const project of projectConfigById.values()) {
3800
3870
  validateProjectId(project.projectId);
3801
3871
  assertRepositoryTransitionState(project);
@@ -3832,7 +3902,7 @@ async function startWorker(options) {
3832
3902
  }
3833
3903
  try {
3834
3904
  const connection = projectConnection(project);
3835
- const states = ensureProjectWorktrees({
3905
+ const states = await ensureProjectWorktrees({
3836
3906
  projectRoot,
3837
3907
  snapshotRoot: projectWorktreeSnapshotsRoot,
3838
3908
  primaryBranchName: primaryProjectBranch(project),
@@ -3881,13 +3951,13 @@ async function startWorker(options) {
3881
3951
  }
3882
3952
  }
3883
3953
  };
3884
- const collectAheadOfOriginBranches = () => {
3954
+ const collectAheadOfOriginBranches = async () => {
3885
3955
  const aheadBranches = [];
3886
3956
  for (const project of projectConfigById.values()) {
3887
3957
  if (project.executionDisabled || !readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
3888
3958
  const connection = projectConnection(project);
3889
3959
  const primaryPath = configuredProjectBranchPath(projectsRoot, project, primaryProjectBranch(project));
3890
- if (!tryGit(
3960
+ if (!await tryGitAsync(
3891
3961
  [
3892
3962
  ...gitTransportSecurityArgs(connection.originUrl, connection.credentialHelper, connection.originCredentialUsername),
3893
3963
  "fetch",
@@ -3911,11 +3981,11 @@ async function startWorker(options) {
3911
3981
  primaryBranchName: primaryProjectBranch(project),
3912
3982
  defaultBranch: project.defaultBranch
3913
3983
  })}`;
3914
- if (tryGit(["show-ref", "--verify", "--quiet", originRef], { cwd: branchPath })) {
3915
- const counts = runGit(["rev-list", "--left-right", "--count", `${originRef}...HEAD`], { cwd: branchPath }).split(/\s+/).map(Number);
3984
+ if (await tryGitAsync(["show-ref", "--verify", "--quiet", originRef], { cwd: branchPath })) {
3985
+ const counts = (await runGitAsync(["rev-list", "--left-right", "--count", `${originRef}...HEAD`], { cwd: branchPath })).split(/\s+/).map(Number);
3916
3986
  ahead = Number.isSafeInteger(counts[1]) ? counts[1] : 0;
3917
- } else if (tryGit(["cat-file", "-e", `${branch.baseCommitHash}^{commit}`], { cwd: branchPath })) {
3918
- const count = Number(runGit(["rev-list", "--count", `${branch.baseCommitHash}..HEAD`], { cwd: branchPath }));
3987
+ } else if (await tryGitAsync(["cat-file", "-e", `${branch.baseCommitHash}^{commit}`], { cwd: branchPath })) {
3988
+ const count = Number(await runGitAsync(["rev-list", "--count", `${branch.baseCommitHash}..HEAD`], { cwd: branchPath }));
3919
3989
  ahead = Number.isSafeInteger(count) ? count : 0;
3920
3990
  }
3921
3991
  if (ahead > 0) aheadBranches.push({ projectId: project.projectId, branchName: branch.branchName, ahead });
@@ -3931,19 +4001,19 @@ async function startWorker(options) {
3931
4001
  (left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
3932
4002
  );
3933
4003
  };
3934
- const captureProjectHeadsForWorkspacePublication = () => {
4004
+ const captureProjectHeadsForWorkspacePublication = async () => {
3935
4005
  const heads = /* @__PURE__ */ new Map();
3936
4006
  for (const project of projectConfigById.values()) {
3937
4007
  if (!readyProjectIds.has(project.projectId)) continue;
3938
4008
  for (const branch of project.branches) {
3939
4009
  const branchPath = configuredProjectBranchPath(projectsRoot, project, branch.branchName);
3940
4010
  if (!hasProjectWorktree(branchPath)) continue;
3941
- heads.set(projectMountId(project.projectId, branch.branchName), runGit(["rev-parse", "HEAD"], { cwd: branchPath }));
4011
+ heads.set(projectMountId(project.projectId, branch.branchName), await runGitAsync(["rev-parse", "HEAD"], { cwd: branchPath }));
3942
4012
  }
3943
4013
  }
3944
4014
  return heads;
3945
4015
  };
3946
- const pushChangedProjectHeads = (activeMountIds, publishedHead, publicationHeads, suppressedMountIds = /* @__PURE__ */ new Set()) => {
4016
+ const pushChangedProjectHeads = async (activeMountIds, publishedHead, publicationHeads, suppressedMountIds = /* @__PURE__ */ new Set()) => {
3947
4017
  const active = new Set(activeMountIds);
3948
4018
  for (const project of projectConfigById.values()) {
3949
4019
  if (!readyProjectIds.has(project.projectId) || !repositoryMirrorWritesAllowed(project)) continue;
@@ -3959,7 +4029,7 @@ async function startWorker(options) {
3959
4029
  }
3960
4030
  if (changed.size === 0) continue;
3961
4031
  const connection = projectConnection(project);
3962
- const results = pushProjectMirrorHeads({
4032
+ const results = await pushProjectMirrorHeads({
3963
4033
  projectRoot: configuredProjectRoot(projectsRoot, project),
3964
4034
  branchNames: project.branches.map(({ branchName }) => branchName),
3965
4035
  mirrorUrl: connection.mirrorUrl,
@@ -3990,7 +4060,7 @@ async function startWorker(options) {
3990
4060
  if (!active.has(deletion.id)) continue;
3991
4061
  if (!repositoryMirrorWritesAllowed(projectConfigById.get(deletion.projectId))) continue;
3992
4062
  if (!deletion.projectDeleted) {
3993
- deleteProjectMirrorBranch({
4063
+ await deleteProjectMirrorBranch({
3994
4064
  gitDirectory: deletion.gitDirectory,
3995
4065
  branchName: deletion.branchName,
3996
4066
  mirrorUrl: deletion.mirrorUrl,
@@ -4011,7 +4081,7 @@ async function startWorker(options) {
4011
4081
  lastObservedProjectHeads.delete(key);
4012
4082
  }
4013
4083
  };
4014
- const observeProjectHeadsBeforeOuterWorkspace = (input) => {
4084
+ const observeProjectHeadsBeforeOuterWorkspace = async (input) => {
4015
4085
  const bundles = [];
4016
4086
  for (const project of projectConfigById.values()) {
4017
4087
  if (project.executionDisabled || !readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
@@ -4023,7 +4093,7 @@ async function startWorker(options) {
4023
4093
  projectId: project.projectId,
4024
4094
  projectRoot,
4025
4095
  primaryBranchName,
4026
- observations: observeProjectMirrorHeads({
4096
+ observations: await observeProjectMirrorHeads({
4027
4097
  projectRoot,
4028
4098
  primaryBranchName,
4029
4099
  branchNames: project.branches.map(({ branchName }) => branchName),
@@ -4043,7 +4113,7 @@ async function startWorker(options) {
4043
4113
  }
4044
4114
  return bundles;
4045
4115
  };
4046
- const applyObservedProjectHeadsAfterInboundWorkspace = (bundles, activeMountIds, requireObservedHeads) => {
4116
+ const applyObservedProjectHeadsAfterInboundWorkspace = async (bundles, activeMountIds, requireObservedHeads) => {
4047
4117
  const active = new Set(activeMountIds);
4048
4118
  for (const bundle of bundles) {
4049
4119
  const project = projectConfigById.get(bundle.projectId);
@@ -4051,7 +4121,7 @@ async function startWorker(options) {
4051
4121
  const observations = bundle.observations.filter(({ branchName }) => active.has(projectMountId(project.projectId, branchName)));
4052
4122
  if (observations.length === 0) continue;
4053
4123
  try {
4054
- const refreshed = applyObservedProjectMirrorHeads({
4124
+ const refreshed = await applyObservedProjectMirrorHeads({
4055
4125
  projectRoot: bundle.projectRoot,
4056
4126
  primaryBranchName: bundle.primaryBranchName,
4057
4127
  observations,
@@ -4061,7 +4131,7 @@ async function startWorker(options) {
4061
4131
  for (const observation of observations) {
4062
4132
  if (!observation.shouldMove || !observation.mirrorHead) continue;
4063
4133
  const checkoutPath = configuredProjectBranchPath(projectsRoot, project, observation.branchName);
4064
- const currentHead = runGit(["rev-parse", "HEAD"], { cwd: checkoutPath });
4134
+ const currentHead = await runGitAsync(["rev-parse", "HEAD"], { cwd: checkoutPath });
4065
4135
  if (currentHead !== observation.mirrorHead) {
4066
4136
  throw new Error(
4067
4137
  `Project ${project.projectId}/${observation.branchName} did not apply observed hidden mirror head ${observation.mirrorHead}`
@@ -4084,7 +4154,7 @@ async function startWorker(options) {
4084
4154
  }
4085
4155
  }
4086
4156
  };
4087
- const mapWorkspaceGitResult = (attemptId, trigger, result) => {
4157
+ const mapWorkspaceGitResult = async (attemptId, trigger, result) => {
4088
4158
  const activeProjectBranchPublications = activeProjectBranchPublicationEvidence([...projectConfigById.values()], result.activeMountIds);
4089
4159
  return {
4090
4160
  type: "workspace_sync",
@@ -4097,7 +4167,7 @@ async function startWorker(options) {
4097
4167
  ...result.publishedHead ? { publishedHead: result.publishedHead } : {},
4098
4168
  rebaseCount: result.rebaseCount,
4099
4169
  diffSizeBytes: result.diffSizeBytes,
4100
- gitStatus: workspaceGitStatus(),
4170
+ gitStatus: await workspaceGitStatus(),
4101
4171
  affectedProjects: affectedProjects(result.affectedPaths),
4102
4172
  affectedPaths: result.affectedPaths,
4103
4173
  activeMountIds: result.activeMountIds,
@@ -4112,48 +4182,51 @@ async function startWorker(options) {
4112
4182
  ...result.error ? { error: result.error } : {}
4113
4183
  };
4114
4184
  };
4115
- const failedWorkspaceSyncResult = (attemptId, trigger, error) => ({
4116
- type: "workspace_sync",
4117
- attemptId,
4118
- workerLabel: label,
4119
- trigger,
4120
- outcome: "failed",
4121
- startingHead: workspaceLocalHead(),
4122
- ...workspaceLocalHead() ? { localHead: workspaceLocalHead() } : {},
4123
- rebaseCount: 0,
4124
- diffSizeBytes: 0,
4125
- gitStatus: workspaceGitStatus(),
4126
- affectedProjects: [],
4127
- affectedPaths: [],
4128
- discardedPaths: [],
4129
- localChangesDiscarded: false,
4130
- error: error instanceof Error ? error.message : String(error)
4131
- });
4185
+ const failedWorkspaceSyncResult = async (attemptId, trigger, error) => {
4186
+ const localHead = await workspaceLocalHead();
4187
+ return {
4188
+ type: "workspace_sync",
4189
+ attemptId,
4190
+ workerLabel: label,
4191
+ trigger,
4192
+ outcome: "failed",
4193
+ startingHead: localHead,
4194
+ ...localHead ? { localHead } : {},
4195
+ rebaseCount: 0,
4196
+ diffSizeBytes: 0,
4197
+ gitStatus: await workspaceGitStatus(),
4198
+ affectedProjects: [],
4199
+ affectedPaths: [],
4200
+ discardedPaths: [],
4201
+ localChangesDiscarded: false,
4202
+ error: error instanceof Error ? error.message : String(error)
4203
+ };
4204
+ };
4132
4205
  const performWorkspaceSync = async (input) => {
4133
4206
  if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
4134
4207
  throw new Error("Worker workspace configuration has not been received");
4135
4208
  }
4136
- const mounts = buildWorkspaceMounts();
4209
+ const configuredMounts = buildWorkspaceMounts();
4137
4210
  if (input.resetToCanonical) {
4138
- const resetHasBusyLiveMount = mounts.some(
4211
+ const resetHasBusyLiveMount = configuredMounts.some(
4139
4212
  (mount) => !(mount.deleteWhenSourceMissing && !fs.existsSync(mount.sourcePath)) && mount.busy?.()
4140
4213
  );
4141
4214
  if (resetHasBusyLiveMount) {
4142
4215
  throw new Error("Workspace reset cannot observe project mirror heads while a live mount is busy; remediation remains active");
4143
4216
  }
4144
- const observedProjectHeads2 = observeProjectHeadsBeforeOuterWorkspace({
4217
+ const observedProjectHeads2 = await observeProjectHeadsBeforeOuterWorkspace({
4145
4218
  allowNonFastForward: true,
4146
4219
  failClosed: true
4147
4220
  });
4148
- const reset = resetWorkspaceGit({
4221
+ const reset = await resetWorkspaceGit({
4149
4222
  workspacePath: workspaceShadowRoot,
4150
4223
  remoteUrl: workspaceRemoteUrl,
4151
4224
  credentialHelper: workspaceCredentialHelper,
4152
4225
  credentialUsername: workspaceCredentialUsername,
4153
4226
  gitIdentity: workspaceGitIdentity,
4154
- mounts
4227
+ mounts: configuredMounts
4155
4228
  });
4156
- applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads2, reset.activeMountIds, true);
4229
+ await applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads2, reset.activeMountIds, true);
4157
4230
  return {
4158
4231
  type: "workspace_sync",
4159
4232
  attemptId: input.attemptId,
@@ -4165,7 +4238,7 @@ async function startWorker(options) {
4165
4238
  ...reset.remoteHead ? { publishedHead: reset.remoteHead } : {},
4166
4239
  rebaseCount: 0,
4167
4240
  diffSizeBytes: 0,
4168
- gitStatus: workspaceGitStatus(),
4241
+ gitStatus: await workspaceGitStatus(),
4169
4242
  affectedProjects: affectedProjects(reset.discardedPaths),
4170
4243
  affectedPaths: reset.discardedPaths,
4171
4244
  activeMountIds: reset.activeMountIds,
@@ -4175,13 +4248,14 @@ async function startWorker(options) {
4175
4248
  };
4176
4249
  }
4177
4250
  const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
4178
- const observedProjectHeads = outerRemediation ? [] : observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
4251
+ const mounts = outerRemediation ? workspaceGitMountsForRemediation(configuredMounts) : configuredMounts;
4252
+ const observedProjectHeads = outerRemediation ? [] : await observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
4179
4253
  const inboundMoveMountIds = new Set(
4180
4254
  observedProjectHeads.flatMap(
4181
4255
  ({ projectId, observations }) => observations.filter(({ shouldMove }) => shouldMove).map(({ branchName }) => projectMountId(projectId, branchName))
4182
4256
  )
4183
4257
  );
4184
- const publicationHeads = outerRemediation ? /* @__PURE__ */ new Map() : captureProjectHeadsForWorkspacePublication();
4258
+ const publicationHeads = outerRemediation ? /* @__PURE__ */ new Map() : await captureProjectHeadsForWorkspacePublication();
4185
4259
  const result = await synchronizeWorkspaceGit({
4186
4260
  attemptId: input.attemptId,
4187
4261
  workerLabel: label,
@@ -4196,13 +4270,13 @@ async function startWorker(options) {
4196
4270
  skipMountMirror: outerRemediation,
4197
4271
  requiredAncestorHeads: input.requiredAncestorHeads,
4198
4272
  assertStillAdmitted: input.assertStillAdmitted,
4199
- afterWorkspacePublished: outerRemediation ? void 0 : ({ activeMountIds, publishedHead }) => {
4200
- pushChangedProjectHeads(activeMountIds, publishedHead, publicationHeads, inboundMoveMountIds);
4273
+ afterWorkspacePublished: outerRemediation ? void 0 : async ({ activeMountIds, publishedHead }) => {
4274
+ await pushChangedProjectHeads(activeMountIds, publishedHead, publicationHeads, inboundMoveMountIds);
4201
4275
  }
4202
4276
  });
4203
4277
  input.assertStillAdmitted?.();
4204
4278
  if (["no_change", "updated", "pushed"].includes(result.outcome) && !outerRemediation) {
4205
- applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads, result.activeMountIds, false);
4279
+ await applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads, result.activeMountIds, false);
4206
4280
  }
4207
4281
  return mapWorkspaceGitResult(input.attemptId, input.trigger, result);
4208
4282
  };
@@ -4233,7 +4307,7 @@ async function startWorker(options) {
4233
4307
  };
4234
4308
  if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
4235
4309
  const result2 = {
4236
- ...failedWorkspaceSyncResult(
4310
+ ...await failedWorkspaceSyncResult(
4237
4311
  attemptId,
4238
4312
  input.trigger,
4239
4313
  new Error(
@@ -4260,7 +4334,7 @@ async function startWorker(options) {
4260
4334
  queueEnteredAt = Date.now();
4261
4335
  syncStartedAt = Date.now();
4262
4336
  if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
4263
- return failedWorkspaceSyncResult(
4337
+ return await failedWorkspaceSyncResult(
4264
4338
  attemptId,
4265
4339
  input.trigger,
4266
4340
  new Error(
@@ -4299,7 +4373,7 @@ async function startWorker(options) {
4299
4373
  },
4300
4374
  exitCode: 1
4301
4375
  });
4302
- return failedWorkspaceSyncResult(attemptId, input.trigger, error);
4376
+ return await failedWorkspaceSyncResult(attemptId, input.trigger, error);
4303
4377
  }
4304
4378
  });
4305
4379
  } finally {
@@ -4326,7 +4400,7 @@ async function startWorker(options) {
4326
4400
  pendingCreatedBranchPublicationNotBefore
4327
4401
  );
4328
4402
  const initialDeferral = pendingBranchDeferral();
4329
- if (!workspaceConfigured || activeWorkspaceIncidentId || deferredWorkspaceConfiguration || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || automaticSyncInFlight || initialDeferral?.kind === "active_target") {
4403
+ if (!automaticWorkspaceSyncIsCurrentlyAdmitted() || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || automaticSyncInFlight || initialDeferral?.kind === "active_target") {
4330
4404
  return;
4331
4405
  }
4332
4406
  if (workspaceAutomaticTimer) clearTimeout(workspaceAutomaticTimer);
@@ -4334,8 +4408,7 @@ async function startWorker(options) {
4334
4408
  () => {
4335
4409
  workspaceAutomaticTimer = void 0;
4336
4410
  const scheduledTrigger = pendingAutomaticTrigger;
4337
- if (!scheduledTrigger || !workspaceConfigured || activeWorkspaceIncidentId || deferredWorkspaceConfiguration || currentWorkerSocket !== ws)
4338
- return;
4411
+ if (!scheduledTrigger || !automaticWorkspaceSyncIsCurrentlyAdmitted() || currentWorkerSocket !== ws) return;
4339
4412
  const currentDeferral = pendingBranchDeferral();
4340
4413
  if (currentDeferral?.kind === "active_target") return;
4341
4414
  if (currentDeferral?.kind === "creation_grace") {
@@ -4357,7 +4430,7 @@ async function startWorker(options) {
4357
4430
  }).finally(() => {
4358
4431
  automaticSyncInFlight = false;
4359
4432
  heartbeatBusyGrace = grantWorkerHeartbeatBusyGrace(lastServerHeartbeatAt, heartbeatBusyGrace);
4360
- if (pendingAutomaticTrigger && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
4433
+ if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
4361
4434
  scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, WORKSPACE_GIT_QUIET_MS);
4362
4435
  }
4363
4436
  });
@@ -4399,7 +4472,7 @@ async function startWorker(options) {
4399
4472
  if (incidentDeferral.error) {
4400
4473
  workspaceConfigured = false;
4401
4474
  return {
4402
- result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, incidentDeferral.error),
4475
+ result: await failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, incidentDeferral.error),
4403
4476
  pending: incidentDeferredProjectCheckouts(message.projects),
4404
4477
  aheadOfOriginBranches: []
4405
4478
  };
@@ -4427,7 +4500,7 @@ async function startWorker(options) {
4427
4500
  canonicalRemediationActive: canonicalWorkspaceMutationIsActive(activeWorkspaceMutationTargets())
4428
4501
  })) {
4429
4502
  return {
4430
- result: failedWorkspaceSyncResult(
4503
+ result: await failedWorkspaceSyncResult(
4431
4504
  crypto.randomUUID(),
4432
4505
  { type: "connect" },
4433
4506
  new Error("Workspace credential rotation is deferred until the active canonical remediation command finishes")
@@ -4486,7 +4559,7 @@ async function startWorker(options) {
4486
4559
  } catch (error) {
4487
4560
  const wrapped = error instanceof RegistryAuthConfigurationError ? error : new RegistryAuthConfigurationError(error);
4488
4561
  return {
4489
- result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, wrapped),
4562
+ result: await failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, wrapped),
4490
4563
  pending: [],
4491
4564
  aheadOfOriginBranches: []
4492
4565
  };
@@ -4663,7 +4736,7 @@ async function startWorker(options) {
4663
4736
  }));
4664
4737
  const nextProjectConfigById = new Map(effectiveProjects.map((project) => [project.projectId, project]));
4665
4738
  if (incidentDeferral.incidentId) {
4666
- configureExistingWorkspaceGitForRemediation({
4739
+ await configureExistingWorkspaceGitForRemediation({
4667
4740
  workspacePath: workspaceShadowRoot,
4668
4741
  remoteUrl: message.workspaceRemoteUrl,
4669
4742
  credentialHelper: nextWorkspaceCredentialHelper,
@@ -4682,7 +4755,7 @@ async function startWorker(options) {
4682
4755
  result: deferredIncidentWorkspaceConfigurationResult({
4683
4756
  attemptId: crypto.randomUUID(),
4684
4757
  workerLabel: label,
4685
- head: workspaceLocalHead(),
4758
+ head: await workspaceLocalHead(),
4686
4759
  skippedMountIds: buildWorkspaceMounts().map(({ id }) => id)
4687
4760
  }),
4688
4761
  pending: pending2,
@@ -4690,7 +4763,7 @@ async function startWorker(options) {
4690
4763
  deferredWorkspaceSyncForIncidentId: incidentDeferral.incidentId
4691
4764
  };
4692
4765
  }
4693
- ensureWorkspaceGitClone({
4766
+ await ensureWorkspaceGitClone({
4694
4767
  workspacePath: workspaceShadowRoot,
4695
4768
  remoteUrl: message.workspaceRemoteUrl,
4696
4769
  credentialHelper: nextWorkspaceCredentialHelper,
@@ -4710,7 +4783,7 @@ async function startWorker(options) {
4710
4783
  sourcePath: recoverySourceOverrides.get(mount.id) ?? mount.sourcePath,
4711
4784
  busy: mount.busyForRecovery ?? mount.busy
4712
4785
  }));
4713
- recoverWorkspaceGitHydration(workspaceShadowRoot, recoveryMounts, { preserveStaleBases: true });
4786
+ await recoverWorkspaceGitHydration(workspaceShadowRoot, recoveryMounts, { preserveStaleBases: true });
4714
4787
  for (const project of message.projects) {
4715
4788
  for (const { branchName } of project.preserveOnlyBranches) {
4716
4789
  if (stillPendingCreatedBranches.has(pendingCreatedBranchKey(project.projectId, branchName))) {
@@ -4747,7 +4820,7 @@ async function startWorker(options) {
4747
4820
  }
4748
4821
  stageDurableWorkspaceDeletions();
4749
4822
  const mountsBeforeGit = buildWorkspaceMounts();
4750
- hydrateWorkspaceGitMounts(
4823
+ await hydrateWorkspaceGitMounts(
4751
4824
  workspaceShadowRoot,
4752
4825
  mountsBeforeGit.filter((mount) => !mount.deleteWhenSourceMissing && !fs.existsSync(mount.sourcePath) && mount.busy?.() !== true),
4753
4826
  // These mounts are intentionally hydrated before their project Git
@@ -4756,7 +4829,7 @@ async function startWorker(options) {
4756
4829
  // checkout initialization from erasing synchronized dirt.
4757
4830
  { ignoreBusy: true }
4758
4831
  );
4759
- ensureConfiguredProjects();
4832
+ await ensureConfiguredProjects();
4760
4833
  const configurationSyncAdmissionGeneration = workerAdmissionGeneration;
4761
4834
  const assertConfigurationSyncStillAdmitted = () => {
4762
4835
  if (receiptGeneration !== workspaceConfigurationReceiptGeneration || configurationSyncAdmissionGeneration !== workerAdmissionGeneration || !workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
@@ -4766,6 +4839,7 @@ async function startWorker(options) {
4766
4839
  const result = await performWorkspaceSync({
4767
4840
  attemptId: crypto.randomUUID(),
4768
4841
  trigger: { type: "connect" },
4842
+ resetToCanonical: workspaceConfigurationResetToCanonicalIsAllowed(message.resetToCanonical, incidentDeferral.incidentId),
4769
4843
  assertStillAdmitted: assertConfigurationSyncStillAdmitted
4770
4844
  });
4771
4845
  assertConfigurationSyncStillAdmitted();
@@ -4800,12 +4874,12 @@ async function startWorker(options) {
4800
4874
  const pending = [...pendingCheckouts.values()].sort(
4801
4875
  (left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
4802
4876
  );
4803
- return { result, pending, aheadOfOriginBranches: collectAheadOfOriginBranches() };
4877
+ return { result, pending, aheadOfOriginBranches: await collectAheadOfOriginBranches() };
4804
4878
  });
4805
4879
  } catch (error) {
4806
4880
  if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
4807
4881
  return {
4808
- result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, error),
4882
+ result: await failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, error),
4809
4883
  pending: incidentDeferredProjectCheckouts(message.projects),
4810
4884
  aheadOfOriginBranches: []
4811
4885
  };
@@ -4837,7 +4911,7 @@ async function startWorker(options) {
4837
4911
  pendingCheckouts.set(projectBranchKey(pending.projectId, pending.branchName), pending);
4838
4912
  }
4839
4913
  return {
4840
- result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, error),
4914
+ result: await failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, error),
4841
4915
  pending: [...pendingCheckouts.values()].sort(
4842
4916
  (left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
4843
4917
  ),
@@ -4864,7 +4938,7 @@ async function startWorker(options) {
4864
4938
  workspaceAutomaticTimer = void 0;
4865
4939
  }
4866
4940
  void (async () => {
4867
- if (workspaceConfigured && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
4941
+ if (automaticWorkspaceSyncIsCurrentlyAdmitted()) {
4868
4942
  await runWorkspaceSync({
4869
4943
  trigger: { type: "manual", detail: "graceful worker shutdown" }
4870
4944
  });
@@ -4915,7 +4989,8 @@ async function startWorker(options) {
4915
4989
  execStdinV1: true,
4916
4990
  ptyEnvFilesV1: true,
4917
4991
  workspaceRemediationAncestorGuardV1: true,
4918
- workspaceIncidentConfigDeferralV1: true
4992
+ workspaceIncidentConfigDeferralV1: true,
4993
+ workspaceConfigResetToCanonicalV1: true
4919
4994
  },
4920
4995
  projectRoot: projectsRoot,
4921
4996
  artifactRoot,
@@ -4979,7 +5054,7 @@ async function startWorker(options) {
4979
5054
  }, WORKSPACE_GIT_PERIODIC_MS);
4980
5055
  workspacePeriodicTimer.unref();
4981
5056
  }
4982
- if (pendingAutomaticTrigger && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
5057
+ if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
4983
5058
  scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, 0);
4984
5059
  }
4985
5060
  return;
@@ -5219,35 +5294,33 @@ async function startWorker(options) {
5219
5294
  if (nextIncidentId !== previousIncidentId) advanceWorkerAdmissionGeneration();
5220
5295
  activeWorkspaceIncidentId = nextIncidentId;
5221
5296
  if (previousIncidentId && !activeWorkspaceIncidentId && (message.status === "resolved" || message.status === "confirmed" || message.status === "reset")) {
5222
- const disposition = workspaceIncidentTerminalClearDisposition({
5297
+ const refresh = workspaceIncidentTerminalRefreshFence({
5223
5298
  deferredConfiguration: deferredWorkspaceConfiguration,
5224
5299
  clearedIncidentId: previousIncidentId
5225
5300
  });
5226
- if (disposition !== "ordinary_resume") {
5227
- pendingAutomaticTrigger ??= { type: "periodic", detail: "resume after workspace incident" };
5228
- workspaceConfigured = false;
5229
- advanceWorkerAdmissionGeneration();
5230
- if (disposition === "reconnect_for_refresh") {
5231
- ws.close(1012, "Refreshing deferred workspace configuration");
5232
- } else {
5233
- if (deferredWorkspaceConfigurationRefreshTimer) clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
5234
- const deferredIncidentId = deferredWorkspaceConfiguration?.incidentId;
5235
- deferredWorkspaceConfigurationRefreshTimer = setTimeout(() => {
5236
- deferredWorkspaceConfigurationRefreshTimer = void 0;
5237
- if (!deferredIncidentId || !deferredWorkspaceRefreshWatchdogIsCurrent({
5238
- capturedIncidentId: deferredIncidentId,
5239
- deferredConfiguration: deferredWorkspaceConfiguration,
5240
- activeIncidentId: activeWorkspaceIncidentId
5241
- }) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
5242
- return;
5243
- }
5244
- ws.close(1012, "Timed out waiting for deferred workspace configuration refresh");
5245
- }, WORKSPACE_INCIDENT_CONFIG_REFRESH_TIMEOUT_MS);
5246
- deferredWorkspaceConfigurationRefreshTimer.unref();
5247
- }
5248
- return;
5301
+ deferredWorkspaceConfiguration = refresh.deferredConfiguration;
5302
+ pendingAutomaticTrigger ??= { type: "periodic", detail: "resume after workspace incident" };
5303
+ workspaceConfigured = false;
5304
+ advanceWorkerAdmissionGeneration();
5305
+ if (refresh.disposition === "reconnect_for_refresh") {
5306
+ ws.close(1012, "Refreshing deferred workspace configuration");
5307
+ } else {
5308
+ if (deferredWorkspaceConfigurationRefreshTimer) clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
5309
+ const deferredIncidentId = deferredWorkspaceConfiguration.incidentId;
5310
+ deferredWorkspaceConfigurationRefreshTimer = setTimeout(() => {
5311
+ deferredWorkspaceConfigurationRefreshTimer = void 0;
5312
+ if (!deferredWorkspaceRefreshWatchdogIsCurrent({
5313
+ capturedIncidentId: deferredIncidentId,
5314
+ deferredConfiguration: deferredWorkspaceConfiguration,
5315
+ activeIncidentId: activeWorkspaceIncidentId
5316
+ }) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
5317
+ return;
5318
+ }
5319
+ ws.close(1012, "Timed out waiting for deferred workspace configuration refresh");
5320
+ }, WORKSPACE_INCIDENT_CONFIG_REFRESH_TIMEOUT_MS);
5321
+ deferredWorkspaceConfigurationRefreshTimer.unref();
5249
5322
  }
5250
- scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger ?? { type: "periodic", detail: "resume after workspace incident" }, 0);
5323
+ return;
5251
5324
  }
5252
5325
  return;
5253
5326
  }
@@ -5620,7 +5693,12 @@ async function startWorker(options) {
5620
5693
  if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
5621
5694
  });
5622
5695
  }
5623
- await runWorkspaceCommand(message.target, workspaceSyncSingleFlight, runCommand);
5696
+ await runReservedWorkspaceCommand(message.target, workspaceSyncSingleFlight, runCommand, () => {
5697
+ if (targetReserved) {
5698
+ workspaceSyncPriorityProcessTargets.delete(message.runId);
5699
+ targetReserved = false;
5700
+ }
5701
+ });
5624
5702
  } finally {
5625
5703
  if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
5626
5704
  }
@@ -5694,7 +5772,7 @@ async function startWorker(options) {
5694
5772
  } finally {
5695
5773
  if (reservesVisibleWorkspace) {
5696
5774
  workspaceSyncPriorityOperationTargets.delete(message.requestId);
5697
- if (pendingAutomaticTrigger && !activeWorkspaceIncidentId && !deferredWorkspaceConfiguration) {
5775
+ if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
5698
5776
  scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, dirtyTrigger ? WORKSPACE_GIT_QUIET_MS : 0);
5699
5777
  }
5700
5778
  }