@ricsam/r5d-worker 0.0.83 → 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.
- package/dist/cjs/main.cjs +82 -42
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-automatic-sync-policy.cjs +12 -2
- package/dist/cjs/workspace-command-sync-policy.cjs +21 -0
- package/dist/cjs/workspace-git-sync.cjs +10 -2
- package/dist/mjs/main.mjs +87 -43
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-automatic-sync-policy.mjs +9 -1
- package/dist/mjs/workspace-command-sync-policy.mjs +20 -0
- package/dist/mjs/workspace-git-sync.mjs +9 -2
- package/dist/types/main.d.ts +19 -2
- package/dist/types/workspace-automatic-sync-policy.d.ts +17 -0
- package/dist/types/workspace-command-sync-policy.d.ts +7 -0
- package/dist/types/workspace-git-sync.d.ts +8 -0
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -1535,10 +1535,20 @@ function workspaceConfigurationIncidentDeferral(input) {
|
|
|
1535
1535
|
return { incidentId };
|
|
1536
1536
|
}
|
|
1537
1537
|
function workspaceIncidentTerminalClearDisposition(input) {
|
|
1538
|
-
if (!input.deferredConfiguration) return "
|
|
1539
|
-
|
|
1538
|
+
if (!input.deferredConfiguration) return "await_server_refresh";
|
|
1539
|
+
if (input.deferredConfiguration.incidentId !== input.clearedIncidentId) return "reconnect_for_refresh";
|
|
1540
1540
|
return input.deferredConfiguration.serverRefreshExpected ? "await_server_refresh" : "reconnect_for_refresh";
|
|
1541
1541
|
}
|
|
1542
|
+
function workspaceIncidentTerminalRefreshFence(input) {
|
|
1543
|
+
const disposition = workspaceIncidentTerminalClearDisposition(input);
|
|
1544
|
+
return {
|
|
1545
|
+
disposition,
|
|
1546
|
+
deferredConfiguration: input.deferredConfiguration ?? { incidentId: input.clearedIncidentId, serverRefreshExpected: true }
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
function workspaceAutomaticSyncIsAdmitted(input) {
|
|
1550
|
+
return input.workspaceConfigured && input.activeIncidentId === null && input.deferredConfiguration === null;
|
|
1551
|
+
}
|
|
1542
1552
|
function deferredWorkspaceTargetIsAllowed(deferredConfiguration, activeIncidentId, target) {
|
|
1543
1553
|
const canonicalTarget = target.type === "workspace" && target.rootProfile === "canonical_sync";
|
|
1544
1554
|
if (deferredConfiguration) return activeIncidentId === deferredConfiguration.incidentId && canonicalTarget;
|
|
@@ -1560,6 +1570,9 @@ function deferredCredentialTransitionMustWait(input) {
|
|
|
1560
1570
|
function workspaceConfigurationIncidentSnapshotIsCurrent(capturedIncidentId, activeIncidentId) {
|
|
1561
1571
|
return capturedIncidentId === null ? activeIncidentId === null : activeIncidentId === null || activeIncidentId === capturedIncidentId;
|
|
1562
1572
|
}
|
|
1573
|
+
function workspaceConfigurationResetToCanonicalIsAllowed(requested, incidentId) {
|
|
1574
|
+
return requested === true && incidentId === null;
|
|
1575
|
+
}
|
|
1563
1576
|
function deferredWorkspaceRefreshWatchdogIsCurrent(input) {
|
|
1564
1577
|
return input.deferredConfiguration?.incidentId === input.capturedIncidentId && input.activeIncidentId === null;
|
|
1565
1578
|
}
|
|
@@ -1632,12 +1645,15 @@ const workerGitSecurityTestHarness = {
|
|
|
1632
1645
|
fenceUnsafeWorkspaceSyncFailure,
|
|
1633
1646
|
workspaceConfigurationIncidentDeferral,
|
|
1634
1647
|
workspaceIncidentTerminalClearDisposition,
|
|
1648
|
+
workspaceIncidentTerminalRefreshFence,
|
|
1649
|
+
workspaceAutomaticSyncIsAdmitted,
|
|
1635
1650
|
deferredWorkspaceTargetIsAllowed,
|
|
1636
1651
|
deferredWorkspaceSyncTriggerIsAllowed,
|
|
1637
1652
|
workspaceOperationsAreFenced,
|
|
1638
1653
|
workspaceCommandTransportIsAllowed,
|
|
1639
1654
|
deferredCredentialTransitionMustWait,
|
|
1640
1655
|
workspaceConfigurationIncidentSnapshotIsCurrent,
|
|
1656
|
+
workspaceConfigurationResetToCanonicalIsAllowed,
|
|
1641
1657
|
deferredWorkspaceRefreshWatchdogIsCurrent,
|
|
1642
1658
|
incidentDeferredProjectCheckouts,
|
|
1643
1659
|
deferredIncidentWorkspaceConfigurationResult,
|
|
@@ -3398,6 +3414,11 @@ async function startWorker(options) {
|
|
|
3398
3414
|
let cliUpdateInProgress = false;
|
|
3399
3415
|
let reloadAfterClose = false;
|
|
3400
3416
|
let shutdownAfterClose = false;
|
|
3417
|
+
const automaticWorkspaceSyncIsCurrentlyAdmitted = () => workspaceAutomaticSyncIsAdmitted({
|
|
3418
|
+
workspaceConfigured,
|
|
3419
|
+
activeIncidentId: activeWorkspaceIncidentId,
|
|
3420
|
+
deferredConfiguration: deferredWorkspaceConfiguration
|
|
3421
|
+
});
|
|
3401
3422
|
const bearerAuthHeader = `Authorization: Bearer ${token}`;
|
|
3402
3423
|
const workerCredentialUsername = credentialUsernameForAuthHeader(bearerAuthHeader);
|
|
3403
3424
|
const projectBranchKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
@@ -3679,6 +3700,17 @@ async function startWorker(options) {
|
|
|
3679
3700
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
3680
3701
|
return projectConfigById.get(projectId)?.executionDisabled === true || hasProjectWorktree(branchPath) && (0, import_project_worktrees.projectWorktreeOperationInProgress)(branchPath) || canonicalWorkspaceMutationIsActive(activeTargets) || (0, import_workspace_automatic_sync_policy.projectBranchHasActiveWorkspaceTarget)(projectId, branchName, activeTargets);
|
|
3681
3702
|
};
|
|
3703
|
+
const projectBranchMountExclusiveSyncBusy = (projectId, branchName, branchPath) => {
|
|
3704
|
+
const activeTargets = activeWorkspaceMutationTargets();
|
|
3705
|
+
return (0, import_workspace_automatic_sync_policy.projectBranchMountBusyDuringExclusiveSync)({
|
|
3706
|
+
projectId,
|
|
3707
|
+
branchName,
|
|
3708
|
+
executionDisabled: projectConfigById.get(projectId)?.executionDisabled === true,
|
|
3709
|
+
worktreeOperationInProgress: hasProjectWorktree(branchPath) && (0, import_project_worktrees.projectWorktreeOperationInProgress)(branchPath),
|
|
3710
|
+
retainedCanonicalProcessGroup: canonicalWorkspaceMutationIsActive([...credentialBearingProcessGroupTargets.values()]),
|
|
3711
|
+
activeTargets
|
|
3712
|
+
});
|
|
3713
|
+
};
|
|
3682
3714
|
const projectBranchMountBusy = (projectId, branchName, branchPath) => !readyProjectIds.has(projectId) || projectBranchMountActivityBusy(projectId, branchName, branchPath);
|
|
3683
3715
|
const buildWorkspaceMounts = (projects = projectConfigById.values()) => {
|
|
3684
3716
|
const mounts = [];
|
|
@@ -3711,7 +3743,7 @@ async function startWorker(options) {
|
|
|
3711
3743
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
3712
3744
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
3713
3745
|
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
3714
|
-
busyForRecovery: () =>
|
|
3746
|
+
busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
|
|
3715
3747
|
});
|
|
3716
3748
|
mounts.push({
|
|
3717
3749
|
id: projectPlanMountId(project.projectId, branch.branchName),
|
|
@@ -3725,7 +3757,7 @@ async function startWorker(options) {
|
|
|
3725
3757
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
3726
3758
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
3727
3759
|
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
3728
|
-
busyForRecovery: () =>
|
|
3760
|
+
busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
|
|
3729
3761
|
});
|
|
3730
3762
|
}
|
|
3731
3763
|
void projectRoot;
|
|
@@ -3748,7 +3780,10 @@ async function startWorker(options) {
|
|
|
3748
3780
|
mutationToken: () => String(visibleWorkspaceMutationEpoch),
|
|
3749
3781
|
busyForRecovery: () => {
|
|
3750
3782
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
3751
|
-
return
|
|
3783
|
+
return (0, import_workspace_automatic_sync_policy.workspacePlansMountBusyDuringExclusiveSync)(
|
|
3784
|
+
activeTargets,
|
|
3785
|
+
canonicalWorkspaceMutationIsActive([...credentialBearingProcessGroupTargets.values()])
|
|
3786
|
+
);
|
|
3752
3787
|
}
|
|
3753
3788
|
});
|
|
3754
3789
|
for (const deletion of pendingMirrorDeletes.values()) {
|
|
@@ -4129,9 +4164,9 @@ async function startWorker(options) {
|
|
|
4129
4164
|
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
4130
4165
|
throw new Error("Worker workspace configuration has not been received");
|
|
4131
4166
|
}
|
|
4132
|
-
const
|
|
4167
|
+
const configuredMounts = buildWorkspaceMounts();
|
|
4133
4168
|
if (input.resetToCanonical) {
|
|
4134
|
-
const resetHasBusyLiveMount =
|
|
4169
|
+
const resetHasBusyLiveMount = configuredMounts.some(
|
|
4135
4170
|
(mount) => !(mount.deleteWhenSourceMissing && !import_node_fs.default.existsSync(mount.sourcePath)) && mount.busy?.()
|
|
4136
4171
|
);
|
|
4137
4172
|
if (resetHasBusyLiveMount) {
|
|
@@ -4147,7 +4182,7 @@ async function startWorker(options) {
|
|
|
4147
4182
|
credentialHelper: workspaceCredentialHelper,
|
|
4148
4183
|
credentialUsername: workspaceCredentialUsername,
|
|
4149
4184
|
gitIdentity: workspaceGitIdentity,
|
|
4150
|
-
mounts
|
|
4185
|
+
mounts: configuredMounts
|
|
4151
4186
|
});
|
|
4152
4187
|
applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads2, reset.activeMountIds, true);
|
|
4153
4188
|
return {
|
|
@@ -4171,6 +4206,7 @@ async function startWorker(options) {
|
|
|
4171
4206
|
};
|
|
4172
4207
|
}
|
|
4173
4208
|
const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
|
|
4209
|
+
const mounts = outerRemediation ? (0, import_workspace_git_sync.workspaceGitMountsForRemediation)(configuredMounts) : configuredMounts;
|
|
4174
4210
|
const observedProjectHeads = outerRemediation ? [] : observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
|
|
4175
4211
|
const inboundMoveMountIds = new Set(
|
|
4176
4212
|
observedProjectHeads.flatMap(
|
|
@@ -4322,7 +4358,7 @@ async function startWorker(options) {
|
|
|
4322
4358
|
pendingCreatedBranchPublicationNotBefore
|
|
4323
4359
|
);
|
|
4324
4360
|
const initialDeferral = pendingBranchDeferral();
|
|
4325
|
-
if (!
|
|
4361
|
+
if (!automaticWorkspaceSyncIsCurrentlyAdmitted() || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || automaticSyncInFlight || initialDeferral?.kind === "active_target") {
|
|
4326
4362
|
return;
|
|
4327
4363
|
}
|
|
4328
4364
|
if (workspaceAutomaticTimer) clearTimeout(workspaceAutomaticTimer);
|
|
@@ -4330,8 +4366,7 @@ async function startWorker(options) {
|
|
|
4330
4366
|
() => {
|
|
4331
4367
|
workspaceAutomaticTimer = void 0;
|
|
4332
4368
|
const scheduledTrigger = pendingAutomaticTrigger;
|
|
4333
|
-
if (!scheduledTrigger || !
|
|
4334
|
-
return;
|
|
4369
|
+
if (!scheduledTrigger || !automaticWorkspaceSyncIsCurrentlyAdmitted() || currentWorkerSocket !== ws) return;
|
|
4335
4370
|
const currentDeferral = pendingBranchDeferral();
|
|
4336
4371
|
if (currentDeferral?.kind === "active_target") return;
|
|
4337
4372
|
if (currentDeferral?.kind === "creation_grace") {
|
|
@@ -4353,7 +4388,7 @@ async function startWorker(options) {
|
|
|
4353
4388
|
}).finally(() => {
|
|
4354
4389
|
automaticSyncInFlight = false;
|
|
4355
4390
|
heartbeatBusyGrace = (0, import_heartbeat.grantWorkerHeartbeatBusyGrace)(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
4356
|
-
if (pendingAutomaticTrigger &&
|
|
4391
|
+
if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
4357
4392
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, WORKSPACE_GIT_QUIET_MS);
|
|
4358
4393
|
}
|
|
4359
4394
|
});
|
|
@@ -4762,6 +4797,7 @@ async function startWorker(options) {
|
|
|
4762
4797
|
const result = await performWorkspaceSync({
|
|
4763
4798
|
attemptId: crypto.randomUUID(),
|
|
4764
4799
|
trigger: { type: "connect" },
|
|
4800
|
+
resetToCanonical: workspaceConfigurationResetToCanonicalIsAllowed(message.resetToCanonical, incidentDeferral.incidentId),
|
|
4765
4801
|
assertStillAdmitted: assertConfigurationSyncStillAdmitted
|
|
4766
4802
|
});
|
|
4767
4803
|
assertConfigurationSyncStillAdmitted();
|
|
@@ -4860,7 +4896,7 @@ async function startWorker(options) {
|
|
|
4860
4896
|
workspaceAutomaticTimer = void 0;
|
|
4861
4897
|
}
|
|
4862
4898
|
void (async () => {
|
|
4863
|
-
if (
|
|
4899
|
+
if (automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
4864
4900
|
await runWorkspaceSync({
|
|
4865
4901
|
trigger: { type: "manual", detail: "graceful worker shutdown" }
|
|
4866
4902
|
});
|
|
@@ -4911,7 +4947,8 @@ async function startWorker(options) {
|
|
|
4911
4947
|
execStdinV1: true,
|
|
4912
4948
|
ptyEnvFilesV1: true,
|
|
4913
4949
|
workspaceRemediationAncestorGuardV1: true,
|
|
4914
|
-
workspaceIncidentConfigDeferralV1: true
|
|
4950
|
+
workspaceIncidentConfigDeferralV1: true,
|
|
4951
|
+
workspaceConfigResetToCanonicalV1: true
|
|
4915
4952
|
},
|
|
4916
4953
|
projectRoot: projectsRoot,
|
|
4917
4954
|
artifactRoot,
|
|
@@ -4975,7 +5012,7 @@ async function startWorker(options) {
|
|
|
4975
5012
|
}, WORKSPACE_GIT_PERIODIC_MS);
|
|
4976
5013
|
workspacePeriodicTimer.unref();
|
|
4977
5014
|
}
|
|
4978
|
-
if (pendingAutomaticTrigger &&
|
|
5015
|
+
if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
4979
5016
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, 0);
|
|
4980
5017
|
}
|
|
4981
5018
|
return;
|
|
@@ -5215,35 +5252,33 @@ async function startWorker(options) {
|
|
|
5215
5252
|
if (nextIncidentId !== previousIncidentId) advanceWorkerAdmissionGeneration();
|
|
5216
5253
|
activeWorkspaceIncidentId = nextIncidentId;
|
|
5217
5254
|
if (previousIncidentId && !activeWorkspaceIncidentId && (message.status === "resolved" || message.status === "confirmed" || message.status === "reset")) {
|
|
5218
|
-
const
|
|
5255
|
+
const refresh = workspaceIncidentTerminalRefreshFence({
|
|
5219
5256
|
deferredConfiguration: deferredWorkspaceConfiguration,
|
|
5220
5257
|
clearedIncidentId: previousIncidentId
|
|
5221
5258
|
});
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
}
|
|
5244
|
-
return;
|
|
5259
|
+
deferredWorkspaceConfiguration = refresh.deferredConfiguration;
|
|
5260
|
+
pendingAutomaticTrigger ??= { type: "periodic", detail: "resume after workspace incident" };
|
|
5261
|
+
workspaceConfigured = false;
|
|
5262
|
+
advanceWorkerAdmissionGeneration();
|
|
5263
|
+
if (refresh.disposition === "reconnect_for_refresh") {
|
|
5264
|
+
ws.close(1012, "Refreshing deferred workspace configuration");
|
|
5265
|
+
} else {
|
|
5266
|
+
if (deferredWorkspaceConfigurationRefreshTimer) clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
5267
|
+
const deferredIncidentId = deferredWorkspaceConfiguration.incidentId;
|
|
5268
|
+
deferredWorkspaceConfigurationRefreshTimer = setTimeout(() => {
|
|
5269
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
5270
|
+
if (!deferredWorkspaceRefreshWatchdogIsCurrent({
|
|
5271
|
+
capturedIncidentId: deferredIncidentId,
|
|
5272
|
+
deferredConfiguration: deferredWorkspaceConfiguration,
|
|
5273
|
+
activeIncidentId: activeWorkspaceIncidentId
|
|
5274
|
+
}) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
5275
|
+
return;
|
|
5276
|
+
}
|
|
5277
|
+
ws.close(1012, "Timed out waiting for deferred workspace configuration refresh");
|
|
5278
|
+
}, WORKSPACE_INCIDENT_CONFIG_REFRESH_TIMEOUT_MS);
|
|
5279
|
+
deferredWorkspaceConfigurationRefreshTimer.unref();
|
|
5245
5280
|
}
|
|
5246
|
-
|
|
5281
|
+
return;
|
|
5247
5282
|
}
|
|
5248
5283
|
return;
|
|
5249
5284
|
}
|
|
@@ -5616,7 +5651,12 @@ async function startWorker(options) {
|
|
|
5616
5651
|
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
5617
5652
|
});
|
|
5618
5653
|
}
|
|
5619
|
-
await (0, import_workspace_command_sync_policy.
|
|
5654
|
+
await (0, import_workspace_command_sync_policy.runReservedWorkspaceCommand)(message.target, workspaceSyncSingleFlight, runCommand, () => {
|
|
5655
|
+
if (targetReserved) {
|
|
5656
|
+
workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
5657
|
+
targetReserved = false;
|
|
5658
|
+
}
|
|
5659
|
+
});
|
|
5620
5660
|
} finally {
|
|
5621
5661
|
if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
5622
5662
|
}
|
|
@@ -5690,7 +5730,7 @@ async function startWorker(options) {
|
|
|
5690
5730
|
} finally {
|
|
5691
5731
|
if (reservesVisibleWorkspace) {
|
|
5692
5732
|
workspaceSyncPriorityOperationTargets.delete(message.requestId);
|
|
5693
|
-
if (pendingAutomaticTrigger &&
|
|
5733
|
+
if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
5694
5734
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, dirtyTrigger ? WORKSPACE_GIT_QUIET_MS : 0);
|
|
5695
5735
|
}
|
|
5696
5736
|
}
|
package/dist/cjs/package.json
CHANGED
|
@@ -23,7 +23,9 @@ __export(workspace_automatic_sync_policy_exports, {
|
|
|
23
23
|
pendingCreatedBranchAutomaticSyncDeferral: () => pendingCreatedBranchAutomaticSyncDeferral,
|
|
24
24
|
pendingCreatedBranchKey: () => pendingCreatedBranchKey,
|
|
25
25
|
projectBranchHasActiveWorkspaceTarget: () => projectBranchHasActiveWorkspaceTarget,
|
|
26
|
-
|
|
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
|
-
|
|
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
|
});
|
|
@@ -42,6 +42,7 @@ __export(workspace_git_sync_exports, {
|
|
|
42
42
|
resetWorkspaceGit: () => resetWorkspaceGit,
|
|
43
43
|
synchronizeWorkspaceGit: () => synchronizeWorkspaceGit,
|
|
44
44
|
workspaceGitHydrationIsCurrent: () => workspaceGitHydrationIsCurrent,
|
|
45
|
+
workspaceGitMountsForRemediation: () => workspaceGitMountsForRemediation,
|
|
45
46
|
workspaceGitSyncTestHarness: () => workspaceGitSyncTestHarness
|
|
46
47
|
});
|
|
47
48
|
module.exports = __toCommonJS(workspace_git_sync_exports);
|
|
@@ -58,6 +59,9 @@ const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
|
|
|
58
59
|
const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
|
|
59
60
|
const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
|
|
60
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
|
+
}
|
|
61
65
|
class WorkspaceRemediationAncestryError extends Error {
|
|
62
66
|
constructor(message) {
|
|
63
67
|
super(message);
|
|
@@ -1342,6 +1346,9 @@ function resetWorkspaceGit(input) {
|
|
|
1342
1346
|
const outerPath = import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
1343
1347
|
return import_node_fs.default.existsSync(outerPath);
|
|
1344
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");
|
|
1345
1352
|
const hydrationMounts = [...selected.active, ...bootstrapMounts];
|
|
1346
1353
|
const requiredLiveMounts = [
|
|
1347
1354
|
...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
|
|
@@ -1349,7 +1356,7 @@ function resetWorkspaceGit(input) {
|
|
|
1349
1356
|
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1350
1357
|
workspacePath,
|
|
1351
1358
|
mounts: hydrationMounts,
|
|
1352
|
-
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones],
|
|
1359
|
+
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
|
|
1353
1360
|
receiptMounts: input.mounts,
|
|
1354
1361
|
requiredMounts: requiredLiveMounts,
|
|
1355
1362
|
recordCurrentHead: true
|
|
@@ -1365,7 +1372,7 @@ function resetWorkspaceGit(input) {
|
|
|
1365
1372
|
remoteHead,
|
|
1366
1373
|
discardedPaths,
|
|
1367
1374
|
activeMountIds: hydration.hydratedMountIds,
|
|
1368
|
-
skippedMountIds: [.../* @__PURE__ */ new Set([...
|
|
1375
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...uncoveredSkippedMounts.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
|
|
1369
1376
|
};
|
|
1370
1377
|
}
|
|
1371
1378
|
function stagedPaths(workspacePath) {
|
|
@@ -1881,5 +1888,6 @@ const workspaceGitSyncTestHarness = {
|
|
|
1881
1888
|
resetWorkspaceGit,
|
|
1882
1889
|
synchronizeWorkspaceGit,
|
|
1883
1890
|
workspaceGitHydrationIsCurrent,
|
|
1891
|
+
workspaceGitMountsForRemediation,
|
|
1884
1892
|
workspaceGitSyncTestHarness
|
|
1885
1893
|
});
|
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 {
|
|
@@ -1539,10 +1543,20 @@ function workspaceConfigurationIncidentDeferral(input) {
|
|
|
1539
1543
|
return { incidentId };
|
|
1540
1544
|
}
|
|
1541
1545
|
function workspaceIncidentTerminalClearDisposition(input) {
|
|
1542
|
-
if (!input.deferredConfiguration) return "
|
|
1543
|
-
|
|
1546
|
+
if (!input.deferredConfiguration) return "await_server_refresh";
|
|
1547
|
+
if (input.deferredConfiguration.incidentId !== input.clearedIncidentId) return "reconnect_for_refresh";
|
|
1544
1548
|
return input.deferredConfiguration.serverRefreshExpected ? "await_server_refresh" : "reconnect_for_refresh";
|
|
1545
1549
|
}
|
|
1550
|
+
function workspaceIncidentTerminalRefreshFence(input) {
|
|
1551
|
+
const disposition = workspaceIncidentTerminalClearDisposition(input);
|
|
1552
|
+
return {
|
|
1553
|
+
disposition,
|
|
1554
|
+
deferredConfiguration: input.deferredConfiguration ?? { incidentId: input.clearedIncidentId, serverRefreshExpected: true }
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
function workspaceAutomaticSyncIsAdmitted(input) {
|
|
1558
|
+
return input.workspaceConfigured && input.activeIncidentId === null && input.deferredConfiguration === null;
|
|
1559
|
+
}
|
|
1546
1560
|
function deferredWorkspaceTargetIsAllowed(deferredConfiguration, activeIncidentId, target) {
|
|
1547
1561
|
const canonicalTarget = target.type === "workspace" && target.rootProfile === "canonical_sync";
|
|
1548
1562
|
if (deferredConfiguration) return activeIncidentId === deferredConfiguration.incidentId && canonicalTarget;
|
|
@@ -1564,6 +1578,9 @@ function deferredCredentialTransitionMustWait(input) {
|
|
|
1564
1578
|
function workspaceConfigurationIncidentSnapshotIsCurrent(capturedIncidentId, activeIncidentId) {
|
|
1565
1579
|
return capturedIncidentId === null ? activeIncidentId === null : activeIncidentId === null || activeIncidentId === capturedIncidentId;
|
|
1566
1580
|
}
|
|
1581
|
+
function workspaceConfigurationResetToCanonicalIsAllowed(requested, incidentId) {
|
|
1582
|
+
return requested === true && incidentId === null;
|
|
1583
|
+
}
|
|
1567
1584
|
function deferredWorkspaceRefreshWatchdogIsCurrent(input) {
|
|
1568
1585
|
return input.deferredConfiguration?.incidentId === input.capturedIncidentId && input.activeIncidentId === null;
|
|
1569
1586
|
}
|
|
@@ -1636,12 +1653,15 @@ const workerGitSecurityTestHarness = {
|
|
|
1636
1653
|
fenceUnsafeWorkspaceSyncFailure,
|
|
1637
1654
|
workspaceConfigurationIncidentDeferral,
|
|
1638
1655
|
workspaceIncidentTerminalClearDisposition,
|
|
1656
|
+
workspaceIncidentTerminalRefreshFence,
|
|
1657
|
+
workspaceAutomaticSyncIsAdmitted,
|
|
1639
1658
|
deferredWorkspaceTargetIsAllowed,
|
|
1640
1659
|
deferredWorkspaceSyncTriggerIsAllowed,
|
|
1641
1660
|
workspaceOperationsAreFenced,
|
|
1642
1661
|
workspaceCommandTransportIsAllowed,
|
|
1643
1662
|
deferredCredentialTransitionMustWait,
|
|
1644
1663
|
workspaceConfigurationIncidentSnapshotIsCurrent,
|
|
1664
|
+
workspaceConfigurationResetToCanonicalIsAllowed,
|
|
1645
1665
|
deferredWorkspaceRefreshWatchdogIsCurrent,
|
|
1646
1666
|
incidentDeferredProjectCheckouts,
|
|
1647
1667
|
deferredIncidentWorkspaceConfigurationResult,
|
|
@@ -3402,6 +3422,11 @@ async function startWorker(options) {
|
|
|
3402
3422
|
let cliUpdateInProgress = false;
|
|
3403
3423
|
let reloadAfterClose = false;
|
|
3404
3424
|
let shutdownAfterClose = false;
|
|
3425
|
+
const automaticWorkspaceSyncIsCurrentlyAdmitted = () => workspaceAutomaticSyncIsAdmitted({
|
|
3426
|
+
workspaceConfigured,
|
|
3427
|
+
activeIncidentId: activeWorkspaceIncidentId,
|
|
3428
|
+
deferredConfiguration: deferredWorkspaceConfiguration
|
|
3429
|
+
});
|
|
3405
3430
|
const bearerAuthHeader = `Authorization: Bearer ${token}`;
|
|
3406
3431
|
const workerCredentialUsername = credentialUsernameForAuthHeader(bearerAuthHeader);
|
|
3407
3432
|
const projectBranchKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
@@ -3683,6 +3708,17 @@ async function startWorker(options) {
|
|
|
3683
3708
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
3684
3709
|
return projectConfigById.get(projectId)?.executionDisabled === true || hasProjectWorktree(branchPath) && projectWorktreeOperationInProgress(branchPath) || canonicalWorkspaceMutationIsActive(activeTargets) || projectBranchHasActiveWorkspaceTarget(projectId, branchName, activeTargets);
|
|
3685
3710
|
};
|
|
3711
|
+
const projectBranchMountExclusiveSyncBusy = (projectId, branchName, branchPath) => {
|
|
3712
|
+
const activeTargets = activeWorkspaceMutationTargets();
|
|
3713
|
+
return projectBranchMountBusyDuringExclusiveSync({
|
|
3714
|
+
projectId,
|
|
3715
|
+
branchName,
|
|
3716
|
+
executionDisabled: projectConfigById.get(projectId)?.executionDisabled === true,
|
|
3717
|
+
worktreeOperationInProgress: hasProjectWorktree(branchPath) && projectWorktreeOperationInProgress(branchPath),
|
|
3718
|
+
retainedCanonicalProcessGroup: canonicalWorkspaceMutationIsActive([...credentialBearingProcessGroupTargets.values()]),
|
|
3719
|
+
activeTargets
|
|
3720
|
+
});
|
|
3721
|
+
};
|
|
3686
3722
|
const projectBranchMountBusy = (projectId, branchName, branchPath) => !readyProjectIds.has(projectId) || projectBranchMountActivityBusy(projectId, branchName, branchPath);
|
|
3687
3723
|
const buildWorkspaceMounts = (projects = projectConfigById.values()) => {
|
|
3688
3724
|
const mounts = [];
|
|
@@ -3715,7 +3751,7 @@ async function startWorker(options) {
|
|
|
3715
3751
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
3716
3752
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
3717
3753
|
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
3718
|
-
busyForRecovery: () =>
|
|
3754
|
+
busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
|
|
3719
3755
|
});
|
|
3720
3756
|
mounts.push({
|
|
3721
3757
|
id: projectPlanMountId(project.projectId, branch.branchName),
|
|
@@ -3729,7 +3765,7 @@ async function startWorker(options) {
|
|
|
3729
3765
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
3730
3766
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
3731
3767
|
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
3732
|
-
busyForRecovery: () =>
|
|
3768
|
+
busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
|
|
3733
3769
|
});
|
|
3734
3770
|
}
|
|
3735
3771
|
void projectRoot;
|
|
@@ -3752,7 +3788,10 @@ async function startWorker(options) {
|
|
|
3752
3788
|
mutationToken: () => String(visibleWorkspaceMutationEpoch),
|
|
3753
3789
|
busyForRecovery: () => {
|
|
3754
3790
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
3755
|
-
return
|
|
3791
|
+
return workspacePlansMountBusyDuringExclusiveSync(
|
|
3792
|
+
activeTargets,
|
|
3793
|
+
canonicalWorkspaceMutationIsActive([...credentialBearingProcessGroupTargets.values()])
|
|
3794
|
+
);
|
|
3756
3795
|
}
|
|
3757
3796
|
});
|
|
3758
3797
|
for (const deletion of pendingMirrorDeletes.values()) {
|
|
@@ -4133,9 +4172,9 @@ async function startWorker(options) {
|
|
|
4133
4172
|
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
4134
4173
|
throw new Error("Worker workspace configuration has not been received");
|
|
4135
4174
|
}
|
|
4136
|
-
const
|
|
4175
|
+
const configuredMounts = buildWorkspaceMounts();
|
|
4137
4176
|
if (input.resetToCanonical) {
|
|
4138
|
-
const resetHasBusyLiveMount =
|
|
4177
|
+
const resetHasBusyLiveMount = configuredMounts.some(
|
|
4139
4178
|
(mount) => !(mount.deleteWhenSourceMissing && !fs.existsSync(mount.sourcePath)) && mount.busy?.()
|
|
4140
4179
|
);
|
|
4141
4180
|
if (resetHasBusyLiveMount) {
|
|
@@ -4151,7 +4190,7 @@ async function startWorker(options) {
|
|
|
4151
4190
|
credentialHelper: workspaceCredentialHelper,
|
|
4152
4191
|
credentialUsername: workspaceCredentialUsername,
|
|
4153
4192
|
gitIdentity: workspaceGitIdentity,
|
|
4154
|
-
mounts
|
|
4193
|
+
mounts: configuredMounts
|
|
4155
4194
|
});
|
|
4156
4195
|
applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads2, reset.activeMountIds, true);
|
|
4157
4196
|
return {
|
|
@@ -4175,6 +4214,7 @@ async function startWorker(options) {
|
|
|
4175
4214
|
};
|
|
4176
4215
|
}
|
|
4177
4216
|
const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
|
|
4217
|
+
const mounts = outerRemediation ? workspaceGitMountsForRemediation(configuredMounts) : configuredMounts;
|
|
4178
4218
|
const observedProjectHeads = outerRemediation ? [] : observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
|
|
4179
4219
|
const inboundMoveMountIds = new Set(
|
|
4180
4220
|
observedProjectHeads.flatMap(
|
|
@@ -4326,7 +4366,7 @@ async function startWorker(options) {
|
|
|
4326
4366
|
pendingCreatedBranchPublicationNotBefore
|
|
4327
4367
|
);
|
|
4328
4368
|
const initialDeferral = pendingBranchDeferral();
|
|
4329
|
-
if (!
|
|
4369
|
+
if (!automaticWorkspaceSyncIsCurrentlyAdmitted() || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || automaticSyncInFlight || initialDeferral?.kind === "active_target") {
|
|
4330
4370
|
return;
|
|
4331
4371
|
}
|
|
4332
4372
|
if (workspaceAutomaticTimer) clearTimeout(workspaceAutomaticTimer);
|
|
@@ -4334,8 +4374,7 @@ async function startWorker(options) {
|
|
|
4334
4374
|
() => {
|
|
4335
4375
|
workspaceAutomaticTimer = void 0;
|
|
4336
4376
|
const scheduledTrigger = pendingAutomaticTrigger;
|
|
4337
|
-
if (!scheduledTrigger || !
|
|
4338
|
-
return;
|
|
4377
|
+
if (!scheduledTrigger || !automaticWorkspaceSyncIsCurrentlyAdmitted() || currentWorkerSocket !== ws) return;
|
|
4339
4378
|
const currentDeferral = pendingBranchDeferral();
|
|
4340
4379
|
if (currentDeferral?.kind === "active_target") return;
|
|
4341
4380
|
if (currentDeferral?.kind === "creation_grace") {
|
|
@@ -4357,7 +4396,7 @@ async function startWorker(options) {
|
|
|
4357
4396
|
}).finally(() => {
|
|
4358
4397
|
automaticSyncInFlight = false;
|
|
4359
4398
|
heartbeatBusyGrace = grantWorkerHeartbeatBusyGrace(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
4360
|
-
if (pendingAutomaticTrigger &&
|
|
4399
|
+
if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
4361
4400
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, WORKSPACE_GIT_QUIET_MS);
|
|
4362
4401
|
}
|
|
4363
4402
|
});
|
|
@@ -4766,6 +4805,7 @@ async function startWorker(options) {
|
|
|
4766
4805
|
const result = await performWorkspaceSync({
|
|
4767
4806
|
attemptId: crypto.randomUUID(),
|
|
4768
4807
|
trigger: { type: "connect" },
|
|
4808
|
+
resetToCanonical: workspaceConfigurationResetToCanonicalIsAllowed(message.resetToCanonical, incidentDeferral.incidentId),
|
|
4769
4809
|
assertStillAdmitted: assertConfigurationSyncStillAdmitted
|
|
4770
4810
|
});
|
|
4771
4811
|
assertConfigurationSyncStillAdmitted();
|
|
@@ -4864,7 +4904,7 @@ async function startWorker(options) {
|
|
|
4864
4904
|
workspaceAutomaticTimer = void 0;
|
|
4865
4905
|
}
|
|
4866
4906
|
void (async () => {
|
|
4867
|
-
if (
|
|
4907
|
+
if (automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
4868
4908
|
await runWorkspaceSync({
|
|
4869
4909
|
trigger: { type: "manual", detail: "graceful worker shutdown" }
|
|
4870
4910
|
});
|
|
@@ -4915,7 +4955,8 @@ async function startWorker(options) {
|
|
|
4915
4955
|
execStdinV1: true,
|
|
4916
4956
|
ptyEnvFilesV1: true,
|
|
4917
4957
|
workspaceRemediationAncestorGuardV1: true,
|
|
4918
|
-
workspaceIncidentConfigDeferralV1: true
|
|
4958
|
+
workspaceIncidentConfigDeferralV1: true,
|
|
4959
|
+
workspaceConfigResetToCanonicalV1: true
|
|
4919
4960
|
},
|
|
4920
4961
|
projectRoot: projectsRoot,
|
|
4921
4962
|
artifactRoot,
|
|
@@ -4979,7 +5020,7 @@ async function startWorker(options) {
|
|
|
4979
5020
|
}, WORKSPACE_GIT_PERIODIC_MS);
|
|
4980
5021
|
workspacePeriodicTimer.unref();
|
|
4981
5022
|
}
|
|
4982
|
-
if (pendingAutomaticTrigger &&
|
|
5023
|
+
if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
4983
5024
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, 0);
|
|
4984
5025
|
}
|
|
4985
5026
|
return;
|
|
@@ -5219,35 +5260,33 @@ async function startWorker(options) {
|
|
|
5219
5260
|
if (nextIncidentId !== previousIncidentId) advanceWorkerAdmissionGeneration();
|
|
5220
5261
|
activeWorkspaceIncidentId = nextIncidentId;
|
|
5221
5262
|
if (previousIncidentId && !activeWorkspaceIncidentId && (message.status === "resolved" || message.status === "confirmed" || message.status === "reset")) {
|
|
5222
|
-
const
|
|
5263
|
+
const refresh = workspaceIncidentTerminalRefreshFence({
|
|
5223
5264
|
deferredConfiguration: deferredWorkspaceConfiguration,
|
|
5224
5265
|
clearedIncidentId: previousIncidentId
|
|
5225
5266
|
});
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
}
|
|
5248
|
-
return;
|
|
5267
|
+
deferredWorkspaceConfiguration = refresh.deferredConfiguration;
|
|
5268
|
+
pendingAutomaticTrigger ??= { type: "periodic", detail: "resume after workspace incident" };
|
|
5269
|
+
workspaceConfigured = false;
|
|
5270
|
+
advanceWorkerAdmissionGeneration();
|
|
5271
|
+
if (refresh.disposition === "reconnect_for_refresh") {
|
|
5272
|
+
ws.close(1012, "Refreshing deferred workspace configuration");
|
|
5273
|
+
} else {
|
|
5274
|
+
if (deferredWorkspaceConfigurationRefreshTimer) clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
5275
|
+
const deferredIncidentId = deferredWorkspaceConfiguration.incidentId;
|
|
5276
|
+
deferredWorkspaceConfigurationRefreshTimer = setTimeout(() => {
|
|
5277
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
5278
|
+
if (!deferredWorkspaceRefreshWatchdogIsCurrent({
|
|
5279
|
+
capturedIncidentId: deferredIncidentId,
|
|
5280
|
+
deferredConfiguration: deferredWorkspaceConfiguration,
|
|
5281
|
+
activeIncidentId: activeWorkspaceIncidentId
|
|
5282
|
+
}) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
5283
|
+
return;
|
|
5284
|
+
}
|
|
5285
|
+
ws.close(1012, "Timed out waiting for deferred workspace configuration refresh");
|
|
5286
|
+
}, WORKSPACE_INCIDENT_CONFIG_REFRESH_TIMEOUT_MS);
|
|
5287
|
+
deferredWorkspaceConfigurationRefreshTimer.unref();
|
|
5249
5288
|
}
|
|
5250
|
-
|
|
5289
|
+
return;
|
|
5251
5290
|
}
|
|
5252
5291
|
return;
|
|
5253
5292
|
}
|
|
@@ -5620,7 +5659,12 @@ async function startWorker(options) {
|
|
|
5620
5659
|
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
5621
5660
|
});
|
|
5622
5661
|
}
|
|
5623
|
-
await
|
|
5662
|
+
await runReservedWorkspaceCommand(message.target, workspaceSyncSingleFlight, runCommand, () => {
|
|
5663
|
+
if (targetReserved) {
|
|
5664
|
+
workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
5665
|
+
targetReserved = false;
|
|
5666
|
+
}
|
|
5667
|
+
});
|
|
5624
5668
|
} finally {
|
|
5625
5669
|
if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
5626
5670
|
}
|
|
@@ -5694,7 +5738,7 @@ async function startWorker(options) {
|
|
|
5694
5738
|
} finally {
|
|
5695
5739
|
if (reservesVisibleWorkspace) {
|
|
5696
5740
|
workspaceSyncPriorityOperationTargets.delete(message.requestId);
|
|
5697
|
-
if (pendingAutomaticTrigger &&
|
|
5741
|
+
if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
5698
5742
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, dirtyTrigger ? WORKSPACE_GIT_QUIET_MS : 0);
|
|
5699
5743
|
}
|
|
5700
5744
|
}
|
package/dist/mjs/package.json
CHANGED
|
@@ -15,6 +15,12 @@ function projectBranchHasActiveWorkspaceTarget(projectId, branchName, activeTarg
|
|
|
15
15
|
}
|
|
16
16
|
return false;
|
|
17
17
|
}
|
|
18
|
+
function projectBranchMountBusyDuringExclusiveSync(input) {
|
|
19
|
+
return input.executionDisabled || input.worktreeOperationInProgress || input.retainedCanonicalProcessGroup || projectBranchHasActiveWorkspaceTarget(input.projectId, input.branchName, input.activeTargets);
|
|
20
|
+
}
|
|
21
|
+
function workspacePlansMountBusyDuringExclusiveSync(activeTargets, retainedCanonicalProcessGroup = false) {
|
|
22
|
+
return retainedCanonicalProcessGroup || hasActiveVisibleProjectsWorkspaceTarget(activeTargets);
|
|
23
|
+
}
|
|
18
24
|
function shouldDeferAutomaticWorkspaceSyncForPendingBranch(pendingCreatedBranches, activeTargets) {
|
|
19
25
|
if (pendingCreatedBranches.length === 0) return false;
|
|
20
26
|
const targets = [...activeTargets];
|
|
@@ -36,5 +42,7 @@ export {
|
|
|
36
42
|
pendingCreatedBranchAutomaticSyncDeferral,
|
|
37
43
|
pendingCreatedBranchKey,
|
|
38
44
|
projectBranchHasActiveWorkspaceTarget,
|
|
39
|
-
|
|
45
|
+
projectBranchMountBusyDuringExclusiveSync,
|
|
46
|
+
shouldDeferAutomaticWorkspaceSyncForPendingBranch,
|
|
47
|
+
workspacePlansMountBusyDuringExclusiveSync
|
|
40
48
|
};
|
|
@@ -20,12 +20,32 @@ function shouldSerializeWorkspaceCommand(target) {
|
|
|
20
20
|
function runWorkspaceCommand(target, coordinator, operation) {
|
|
21
21
|
return shouldSerializeWorkspaceCommand(target) ? coordinator.runMutation(operation) : Promise.resolve().then(operation);
|
|
22
22
|
}
|
|
23
|
+
async function runReservedWorkspaceCommand(target, coordinator, operation, releaseReservation) {
|
|
24
|
+
let released = false;
|
|
25
|
+
const releaseOnce = () => {
|
|
26
|
+
if (released) return;
|
|
27
|
+
released = true;
|
|
28
|
+
releaseReservation();
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
return await runWorkspaceCommand(target, coordinator, async () => {
|
|
32
|
+
try {
|
|
33
|
+
return await operation();
|
|
34
|
+
} finally {
|
|
35
|
+
releaseOnce();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
} finally {
|
|
39
|
+
releaseOnce();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
23
42
|
function acquireWorkspaceCommandMutation(target, coordinator) {
|
|
24
43
|
return shouldSerializeWorkspaceCommand(target) ? coordinator.acquireMutation() : Promise.resolve(void 0);
|
|
25
44
|
}
|
|
26
45
|
export {
|
|
27
46
|
acquireWorkspaceCommandMutation,
|
|
28
47
|
reserveWorkspaceCommandAfterCurrentSync,
|
|
48
|
+
runReservedWorkspaceCommand,
|
|
29
49
|
runWorkspaceCommand,
|
|
30
50
|
shouldSerializeWorkspaceCommand
|
|
31
51
|
};
|
|
@@ -15,6 +15,9 @@ const WORKSPACE_GIT_INTEGRATED_REF = "refs/r5d/workspace-local/integrated";
|
|
|
15
15
|
const WORKSPACE_GIT_HYDRATED_RECEIPT = "r5d/workspace-hydrated-head";
|
|
16
16
|
const WORKSPACE_GIT_HYDRATION_TRANSACTION = "r5d/workspace-hydration-transaction";
|
|
17
17
|
const WORKSPACE_GIT_CHECKOUT_DURABILITY = "r5d/workspace-checkout-durability";
|
|
18
|
+
function workspaceGitMountsForRemediation(mounts) {
|
|
19
|
+
return mounts.map((mount) => ({ ...mount, busy: mount.busyForRecovery ?? mount.busy }));
|
|
20
|
+
}
|
|
18
21
|
class WorkspaceRemediationAncestryError extends Error {
|
|
19
22
|
constructor(message) {
|
|
20
23
|
super(message);
|
|
@@ -1299,6 +1302,9 @@ function resetWorkspaceGit(input) {
|
|
|
1299
1302
|
const outerPath = path.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
1300
1303
|
return fs.existsSync(outerPath);
|
|
1301
1304
|
});
|
|
1305
|
+
const bootstrapMountIds = new Set(bootstrapMounts.map(({ id }) => id));
|
|
1306
|
+
const durablyAbsentMounts = selected.skipped.filter((mount) => !bootstrapMountIds.has(mount.id) && mount.sourceMode === "all");
|
|
1307
|
+
const uncoveredSkippedMounts = selected.skipped.filter((mount) => !bootstrapMountIds.has(mount.id) && mount.sourceMode !== "all");
|
|
1302
1308
|
const hydrationMounts = [...selected.active, ...bootstrapMounts];
|
|
1303
1309
|
const requiredLiveMounts = [
|
|
1304
1310
|
...new Map([...requiredLiveMountsBeforeHydration, ...bootstrapMounts].map((mount) => [mount.id, mount])).values()
|
|
@@ -1306,7 +1312,7 @@ function resetWorkspaceGit(input) {
|
|
|
1306
1312
|
const hydration = hydrateWorkspaceGitMountsTransactionally({
|
|
1307
1313
|
workspacePath,
|
|
1308
1314
|
mounts: hydrationMounts,
|
|
1309
|
-
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones],
|
|
1315
|
+
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
|
|
1310
1316
|
receiptMounts: input.mounts,
|
|
1311
1317
|
requiredMounts: requiredLiveMounts,
|
|
1312
1318
|
recordCurrentHead: true
|
|
@@ -1322,7 +1328,7 @@ function resetWorkspaceGit(input) {
|
|
|
1322
1328
|
remoteHead,
|
|
1323
1329
|
discardedPaths,
|
|
1324
1330
|
activeMountIds: hydration.hydratedMountIds,
|
|
1325
|
-
skippedMountIds: [.../* @__PURE__ */ new Set([...
|
|
1331
|
+
skippedMountIds: [.../* @__PURE__ */ new Set([...uncoveredSkippedMounts.map(({ id }) => id), ...hydration.skippedMountIds])].sort()
|
|
1326
1332
|
};
|
|
1327
1333
|
}
|
|
1328
1334
|
function stagedPaths(workspacePath) {
|
|
@@ -1837,5 +1843,6 @@ export {
|
|
|
1837
1843
|
resetWorkspaceGit,
|
|
1838
1844
|
synchronizeWorkspaceGit,
|
|
1839
1845
|
workspaceGitHydrationIsCurrent,
|
|
1846
|
+
workspaceGitMountsForRemediation,
|
|
1840
1847
|
workspaceGitSyncTestHarness
|
|
1841
1848
|
};
|
package/dist/types/main.d.ts
CHANGED
|
@@ -91,6 +91,7 @@ type WorkerServerMessage = {
|
|
|
91
91
|
gitIdentity: WorkerGitIdentity;
|
|
92
92
|
githubCredential: WorkerGitHubCredential | null;
|
|
93
93
|
deferWorkspaceSyncForIncidentId?: string;
|
|
94
|
+
resetToCanonical?: boolean;
|
|
94
95
|
} | {
|
|
95
96
|
type: "sync_workspace";
|
|
96
97
|
requestId: string;
|
|
@@ -565,8 +566,20 @@ type DeferredWorkspaceConfiguration = {
|
|
|
565
566
|
};
|
|
566
567
|
declare function workspaceIncidentTerminalClearDisposition(input: {
|
|
567
568
|
deferredConfiguration: DeferredWorkspaceConfiguration | null;
|
|
568
|
-
clearedIncidentId: string
|
|
569
|
-
}): "
|
|
569
|
+
clearedIncidentId: string;
|
|
570
|
+
}): "await_server_refresh" | "reconnect_for_refresh";
|
|
571
|
+
declare function workspaceIncidentTerminalRefreshFence(input: {
|
|
572
|
+
deferredConfiguration: DeferredWorkspaceConfiguration | null;
|
|
573
|
+
clearedIncidentId: string;
|
|
574
|
+
}): {
|
|
575
|
+
disposition: "await_server_refresh" | "reconnect_for_refresh";
|
|
576
|
+
deferredConfiguration: DeferredWorkspaceConfiguration;
|
|
577
|
+
};
|
|
578
|
+
declare function workspaceAutomaticSyncIsAdmitted(input: {
|
|
579
|
+
workspaceConfigured: boolean;
|
|
580
|
+
activeIncidentId: string | null;
|
|
581
|
+
deferredConfiguration: DeferredWorkspaceConfiguration | null;
|
|
582
|
+
}): boolean;
|
|
570
583
|
declare function deferredWorkspaceTargetIsAllowed(deferredConfiguration: DeferredWorkspaceConfiguration | null, activeIncidentId: string | null, target: WorkerSessionTarget): boolean;
|
|
571
584
|
declare function deferredWorkspaceSyncTriggerIsAllowed(deferredConfiguration: DeferredWorkspaceConfiguration | null, activeIncidentId: string | null, trigger: WorkspaceSyncTrigger): boolean;
|
|
572
585
|
declare function workspaceOperationsAreFenced(deferredConfiguration: DeferredWorkspaceConfiguration | null, activeIncidentId: string | null): boolean;
|
|
@@ -577,6 +590,7 @@ declare function deferredCredentialTransitionMustWait(input: {
|
|
|
577
590
|
canonicalRemediationActive: boolean;
|
|
578
591
|
}): boolean;
|
|
579
592
|
declare function workspaceConfigurationIncidentSnapshotIsCurrent(capturedIncidentId: string | null, activeIncidentId: string | null): boolean;
|
|
593
|
+
declare function workspaceConfigurationResetToCanonicalIsAllowed(requested: boolean | undefined, incidentId: string | null): boolean;
|
|
580
594
|
declare function deferredWorkspaceRefreshWatchdogIsCurrent(input: {
|
|
581
595
|
capturedIncidentId: string;
|
|
582
596
|
deferredConfiguration: DeferredWorkspaceConfiguration | null;
|
|
@@ -629,12 +643,15 @@ export declare const workerGitSecurityTestHarness: {
|
|
|
629
643
|
fenceUnsafeWorkspaceSyncFailure: typeof fenceUnsafeWorkspaceSyncFailure;
|
|
630
644
|
workspaceConfigurationIncidentDeferral: typeof workspaceConfigurationIncidentDeferral;
|
|
631
645
|
workspaceIncidentTerminalClearDisposition: typeof workspaceIncidentTerminalClearDisposition;
|
|
646
|
+
workspaceIncidentTerminalRefreshFence: typeof workspaceIncidentTerminalRefreshFence;
|
|
647
|
+
workspaceAutomaticSyncIsAdmitted: typeof workspaceAutomaticSyncIsAdmitted;
|
|
632
648
|
deferredWorkspaceTargetIsAllowed: typeof deferredWorkspaceTargetIsAllowed;
|
|
633
649
|
deferredWorkspaceSyncTriggerIsAllowed: typeof deferredWorkspaceSyncTriggerIsAllowed;
|
|
634
650
|
workspaceOperationsAreFenced: typeof workspaceOperationsAreFenced;
|
|
635
651
|
workspaceCommandTransportIsAllowed: typeof workspaceCommandTransportIsAllowed;
|
|
636
652
|
deferredCredentialTransitionMustWait: typeof deferredCredentialTransitionMustWait;
|
|
637
653
|
workspaceConfigurationIncidentSnapshotIsCurrent: typeof workspaceConfigurationIncidentSnapshotIsCurrent;
|
|
654
|
+
workspaceConfigurationResetToCanonicalIsAllowed: typeof workspaceConfigurationResetToCanonicalIsAllowed;
|
|
638
655
|
deferredWorkspaceRefreshWatchdogIsCurrent: typeof deferredWorkspaceRefreshWatchdogIsCurrent;
|
|
639
656
|
incidentDeferredProjectCheckouts: typeof incidentDeferredProjectCheckouts;
|
|
640
657
|
deferredIncidentWorkspaceConfigurationResult: typeof deferredIncidentWorkspaceConfigurationResult;
|
|
@@ -21,6 +21,23 @@ export type PendingCreatedBranchAutomaticSyncDeferral = {
|
|
|
21
21
|
export declare function pendingCreatedBranchKey(projectId: string, branchName: string): string;
|
|
22
22
|
export declare function hasActiveVisibleProjectsWorkspaceTarget(activeTargets: Iterable<ProjectTarget | WorkspaceTarget>): boolean;
|
|
23
23
|
export declare function projectBranchHasActiveWorkspaceTarget(projectId: string, branchName: string, activeTargets: Iterable<ProjectTarget | WorkspaceTarget>): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Activity fence evaluated from inside the workspace mutation gate's exclusive
|
|
26
|
+
* sync lease. Canonical-sync targets are deliberately absent: an immediately
|
|
27
|
+
* reserved follow-up remediation command may already be queued, but the gate
|
|
28
|
+
* proves that it cannot overlap this sync. A credential-bearing process group
|
|
29
|
+
* that survived command teardown is tracked separately and still fails closed.
|
|
30
|
+
*/
|
|
31
|
+
export declare function projectBranchMountBusyDuringExclusiveSync(input: {
|
|
32
|
+
projectId: string;
|
|
33
|
+
branchName: string;
|
|
34
|
+
executionDisabled: boolean;
|
|
35
|
+
worktreeOperationInProgress: boolean;
|
|
36
|
+
retainedCanonicalProcessGroup: boolean;
|
|
37
|
+
activeTargets: Iterable<ProjectTarget | WorkspaceTarget>;
|
|
38
|
+
}): boolean;
|
|
39
|
+
/** Fence visible activity and any retained canonical descendant that outlived its command lease. */
|
|
40
|
+
export declare function workspacePlansMountBusyDuringExclusiveSync(activeTargets: Iterable<ProjectTarget | WorkspaceTarget>, retainedCanonicalProcessGroup?: boolean): boolean;
|
|
24
41
|
/**
|
|
25
42
|
* A workspace sync is global and synchronous enough to monopolize the worker
|
|
26
43
|
* event loop. Keep automatic intent pending while an agent process is active
|
|
@@ -25,5 +25,12 @@ export declare function reserveWorkspaceCommandAfterCurrentSync(target: Workspac
|
|
|
25
25
|
*/
|
|
26
26
|
export declare function shouldSerializeWorkspaceCommand(target: WorkspaceCommandTarget): boolean;
|
|
27
27
|
export declare function runWorkspaceCommand<T>(target: WorkspaceCommandTarget, coordinator: WorkspaceCommandMutationCoordinator, operation: () => Promise<T> | T): Promise<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Run a command whose target was reserved before asynchronous preparation.
|
|
30
|
+
* For a serialized canonical command, the reservation must disappear inside
|
|
31
|
+
* the mutation operation: removing it after `runMutation` resolves is too
|
|
32
|
+
* late because releasing the lease can synchronously wake a queued sync.
|
|
33
|
+
*/
|
|
34
|
+
export declare function runReservedWorkspaceCommand<T>(target: WorkspaceCommandTarget, coordinator: WorkspaceCommandMutationCoordinator, operation: () => Promise<T> | T, releaseReservation: () => void): Promise<T>;
|
|
28
35
|
export declare function acquireWorkspaceCommandMutation(target: WorkspaceCommandTarget, coordinator: WorkspaceCommandMutationCoordinator): Promise<(() => void) | undefined>;
|
|
29
36
|
export {};
|
|
@@ -34,6 +34,14 @@ export type WorkspaceGitMount = {
|
|
|
34
34
|
/** Activity-only fence used before checkout readiness is established on reconnect. */
|
|
35
35
|
busyForRecovery?: () => boolean;
|
|
36
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Incident-bound configuration keeps project checkouts unready while the
|
|
39
|
+
* canonical remediation owns the workspace. The guarded remediation sync is
|
|
40
|
+
* nevertheless allowed to hydrate an existing checkout when its activity-only
|
|
41
|
+
* fence is clear; the ordinary `busy` predicate would mistake that deliberate
|
|
42
|
+
* unready state for concurrent visible work.
|
|
43
|
+
*/
|
|
44
|
+
export declare function workspaceGitMountsForRemediation(mounts: readonly WorkspaceGitMount[]): WorkspaceGitMount[];
|
|
37
45
|
export type WorkspaceGitSyncOutcome = "no_change" | "updated" | "pushed" | "large_diff_blocked" | "conflict_blocked";
|
|
38
46
|
export type WorkspaceGitSyncResult = {
|
|
39
47
|
outcome: WorkspaceGitSyncOutcome;
|