@ricsam/r5d-worker 0.0.82 → 0.0.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/main.cjs +470 -38
- 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 +79 -3
- package/dist/mjs/main.mjs +478 -40
- 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 +76 -3
- package/dist/types/main.d.ts +349 -0
- 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 +36 -0
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -97,6 +97,7 @@ const DEFAULT_READ_MAX_BYTES = 5e4;
|
|
|
97
97
|
const MAX_LINE_LENGTH = 2e3;
|
|
98
98
|
const WORKSPACE_GIT_QUIET_MS = 5e3;
|
|
99
99
|
const WORKSPACE_GIT_PERIODIC_MS = 6e4;
|
|
100
|
+
const WORKSPACE_INCIDENT_CONFIG_REFRESH_TIMEOUT_MS = 6e4;
|
|
100
101
|
const PTY_INPUT_BUSY_GRACE_MS = 3e3;
|
|
101
102
|
const PTY_FOREGROUND_POLL_MS = 1e3;
|
|
102
103
|
const PTY_FOREGROUND_IDLE_ENABLED = process.env.R5D_PTY_FOREGROUND_IDLE !== "0";
|
|
@@ -255,6 +256,7 @@ const workerPtyTestHarness = {
|
|
|
255
256
|
removeEnvFiles: removePtyEnvFiles,
|
|
256
257
|
resolveEnvFileReferences: resolvePtyEnvFileReferences,
|
|
257
258
|
commandHasWorkspaceEffect: workerCommandHasWorkspaceEffect,
|
|
259
|
+
canonicalSyncTerminalHead,
|
|
258
260
|
ptyIsWorkspaceBusy: workerPtyIsWorkspaceBusy,
|
|
259
261
|
parseLinuxForegroundBusy: parseLinuxPtyForegroundBusy
|
|
260
262
|
};
|
|
@@ -1516,6 +1518,99 @@ function credentialReapContractStatus(probe) {
|
|
|
1516
1518
|
function verifiedCredentialReapContract(probe) {
|
|
1517
1519
|
return credentialReapContractStatus(probe) === "verified_systemd";
|
|
1518
1520
|
}
|
|
1521
|
+
function workspaceConfigurationIncidentDeferral(input) {
|
|
1522
|
+
const incidentId = input.requestedIncidentId ?? input.activeIncidentId;
|
|
1523
|
+
if (incidentId === null) return { incidentId: null };
|
|
1524
|
+
if (!/^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(incidentId)) {
|
|
1525
|
+
return { incidentId: null, error: new Error("Workspace configuration incident deferral requires a canonical incident UUID") };
|
|
1526
|
+
}
|
|
1527
|
+
if (input.requestedIncidentId !== void 0 && input.requestedIncidentId !== input.activeIncidentId) {
|
|
1528
|
+
return {
|
|
1529
|
+
incidentId: null,
|
|
1530
|
+
error: new Error(
|
|
1531
|
+
`Workspace configuration incident deferral ${input.requestedIncidentId} does not match active incident ${input.activeIncidentId ?? "none"}`
|
|
1532
|
+
)
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
return { incidentId };
|
|
1536
|
+
}
|
|
1537
|
+
function workspaceIncidentTerminalClearDisposition(input) {
|
|
1538
|
+
if (!input.deferredConfiguration) return "await_server_refresh";
|
|
1539
|
+
if (input.deferredConfiguration.incidentId !== input.clearedIncidentId) return "reconnect_for_refresh";
|
|
1540
|
+
return input.deferredConfiguration.serverRefreshExpected ? "await_server_refresh" : "reconnect_for_refresh";
|
|
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
|
+
}
|
|
1552
|
+
function deferredWorkspaceTargetIsAllowed(deferredConfiguration, activeIncidentId, target) {
|
|
1553
|
+
const canonicalTarget = target.type === "workspace" && target.rootProfile === "canonical_sync";
|
|
1554
|
+
if (deferredConfiguration) return activeIncidentId === deferredConfiguration.incidentId && canonicalTarget;
|
|
1555
|
+
return activeIncidentId === null || canonicalTarget;
|
|
1556
|
+
}
|
|
1557
|
+
function deferredWorkspaceSyncTriggerIsAllowed(deferredConfiguration, activeIncidentId, trigger) {
|
|
1558
|
+
const incidentId = deferredConfiguration?.incidentId ?? activeIncidentId;
|
|
1559
|
+
return incidentId === null || (deferredConfiguration === null || activeIncidentId === deferredConfiguration.incidentId) && (trigger.type === "remediation" || trigger.type === "remediation_confirm" || trigger.type === "remediation_reset");
|
|
1560
|
+
}
|
|
1561
|
+
function workspaceOperationsAreFenced(deferredConfiguration, activeIncidentId) {
|
|
1562
|
+
return deferredConfiguration !== null || activeIncidentId !== null;
|
|
1563
|
+
}
|
|
1564
|
+
function workspaceCommandTransportIsAllowed(deferredConfiguration, activeIncidentId, transport) {
|
|
1565
|
+
return !workspaceOperationsAreFenced(deferredConfiguration, activeIncidentId) || transport === "exec_start";
|
|
1566
|
+
}
|
|
1567
|
+
function deferredCredentialTransitionMustWait(input) {
|
|
1568
|
+
return input.incidentId !== null && input.transitionPhase !== "current" && input.canonicalRemediationActive;
|
|
1569
|
+
}
|
|
1570
|
+
function workspaceConfigurationIncidentSnapshotIsCurrent(capturedIncidentId, activeIncidentId) {
|
|
1571
|
+
return capturedIncidentId === null ? activeIncidentId === null : activeIncidentId === null || activeIncidentId === capturedIncidentId;
|
|
1572
|
+
}
|
|
1573
|
+
function workspaceConfigurationResetToCanonicalIsAllowed(requested, incidentId) {
|
|
1574
|
+
return requested === true && incidentId === null;
|
|
1575
|
+
}
|
|
1576
|
+
function deferredWorkspaceRefreshWatchdogIsCurrent(input) {
|
|
1577
|
+
return input.deferredConfiguration?.incidentId === input.capturedIncidentId && input.activeIncidentId === null;
|
|
1578
|
+
}
|
|
1579
|
+
function incidentDeferredProjectCheckouts(projects) {
|
|
1580
|
+
return projects.flatMap(
|
|
1581
|
+
(project) => project.executionDisabled ? [] : project.branches.map(({ branchName }) => ({ projectId: project.projectId, branchName }))
|
|
1582
|
+
).sort((left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName));
|
|
1583
|
+
}
|
|
1584
|
+
function deferredIncidentWorkspaceConfigurationResult(input) {
|
|
1585
|
+
return {
|
|
1586
|
+
type: "workspace_sync",
|
|
1587
|
+
attemptId: input.attemptId,
|
|
1588
|
+
workerLabel: input.workerLabel,
|
|
1589
|
+
trigger: { type: "connect", detail: "workspace synchronization deferred for active remediation" },
|
|
1590
|
+
outcome: "no_change",
|
|
1591
|
+
startingHead: input.head,
|
|
1592
|
+
...input.head ? { localHead: input.head } : {},
|
|
1593
|
+
rebaseCount: 0,
|
|
1594
|
+
diffSizeBytes: 0,
|
|
1595
|
+
gitStatus: "",
|
|
1596
|
+
affectedProjects: [],
|
|
1597
|
+
affectedPaths: [],
|
|
1598
|
+
activeMountIds: [],
|
|
1599
|
+
skippedMountIds: [...input.skippedMountIds].sort(),
|
|
1600
|
+
activeProjectBranchPublications: [],
|
|
1601
|
+
discardedPaths: [],
|
|
1602
|
+
localChangesDiscarded: false
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
function workspaceSyncFailureHydrationIsSafe(input) {
|
|
1606
|
+
if (input.error instanceof import_workspace_git_sync.WorkspaceRemediationAncestryError) return true;
|
|
1607
|
+
if (input.resetToCanonical) return false;
|
|
1608
|
+
try {
|
|
1609
|
+
return input.inspectCurrentHydration();
|
|
1610
|
+
} catch {
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1519
1614
|
function fenceUnsafeWorkspaceSyncFailure(input) {
|
|
1520
1615
|
if (input.hydrationCurrent) return false;
|
|
1521
1616
|
input.invalidateExecution();
|
|
@@ -1548,7 +1643,24 @@ const workerGitSecurityTestHarness = {
|
|
|
1548
1643
|
credentialReapContractStatus,
|
|
1549
1644
|
verifiedCredentialReapContract,
|
|
1550
1645
|
fenceUnsafeWorkspaceSyncFailure,
|
|
1646
|
+
workspaceConfigurationIncidentDeferral,
|
|
1647
|
+
workspaceIncidentTerminalClearDisposition,
|
|
1648
|
+
workspaceIncidentTerminalRefreshFence,
|
|
1649
|
+
workspaceAutomaticSyncIsAdmitted,
|
|
1650
|
+
deferredWorkspaceTargetIsAllowed,
|
|
1651
|
+
deferredWorkspaceSyncTriggerIsAllowed,
|
|
1652
|
+
workspaceOperationsAreFenced,
|
|
1653
|
+
workspaceCommandTransportIsAllowed,
|
|
1654
|
+
deferredCredentialTransitionMustWait,
|
|
1655
|
+
workspaceConfigurationIncidentSnapshotIsCurrent,
|
|
1656
|
+
workspaceConfigurationResetToCanonicalIsAllowed,
|
|
1657
|
+
deferredWorkspaceRefreshWatchdogIsCurrent,
|
|
1658
|
+
incidentDeferredProjectCheckouts,
|
|
1659
|
+
deferredIncidentWorkspaceConfigurationResult,
|
|
1660
|
+
workspaceSyncFailureHydrationIsSafe,
|
|
1551
1661
|
assertWorkerChildAdmission,
|
|
1662
|
+
executeWriteFileOperation,
|
|
1663
|
+
executeEditFileOperation,
|
|
1552
1664
|
terminateCredentialBearingChildren,
|
|
1553
1665
|
terminateCredentialBearingChildrenWithRetention,
|
|
1554
1666
|
async commitCredentialGeneration(prepared, credential, children, beforeMutation, previousGenerationFenced = false) {
|
|
@@ -1699,6 +1811,15 @@ function hasProjectWorktree(checkoutPath) {
|
|
|
1699
1811
|
return false;
|
|
1700
1812
|
}
|
|
1701
1813
|
}
|
|
1814
|
+
function canonicalSyncTerminalHead(resolvedTarget, readHead = (rootPath) => runGit(["rev-parse", "HEAD"], { cwd: rootPath })) {
|
|
1815
|
+
if (resolvedTarget.target.type !== "workspace" || resolvedTarget.target.rootProfile !== "canonical_sync") return void 0;
|
|
1816
|
+
try {
|
|
1817
|
+
const head = readHead(resolvedTarget.rootPath);
|
|
1818
|
+
return /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(head) ? head : void 0;
|
|
1819
|
+
} catch {
|
|
1820
|
+
return void 0;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1702
1823
|
function describeWorkerSessionTarget(target) {
|
|
1703
1824
|
return target.type === "project" ? `${target.projectId}/${target.branchName}` : `${target.ownerUserId}/${target.rootProfile}`;
|
|
1704
1825
|
}
|
|
@@ -2003,8 +2124,10 @@ async function executeWriteFileOperation(input) {
|
|
|
2003
2124
|
planRoot: input.planRoot,
|
|
2004
2125
|
access: "write"
|
|
2005
2126
|
});
|
|
2127
|
+
input.assertAdmission();
|
|
2006
2128
|
const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
|
|
2007
2129
|
return withFileMutationQueue(mutationQueueKey(input.resolvedTarget.target, resolved), async () => {
|
|
2130
|
+
input.assertAdmission();
|
|
2008
2131
|
return writeWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.content, builtInPaths);
|
|
2009
2132
|
});
|
|
2010
2133
|
}
|
|
@@ -2020,8 +2143,10 @@ async function executeEditFileOperation(input) {
|
|
|
2020
2143
|
planRoot: input.planRoot,
|
|
2021
2144
|
access: "write"
|
|
2022
2145
|
});
|
|
2146
|
+
input.assertAdmission();
|
|
2023
2147
|
const resolved = resolveWorkerFilePath(input.resolvedTarget.rootPath, input.message.filePath, builtInPaths);
|
|
2024
2148
|
return withFileMutationQueue(mutationQueueKey(input.resolvedTarget.target, resolved), async () => {
|
|
2149
|
+
input.assertAdmission();
|
|
2025
2150
|
return editWorkerTextFile(input.resolvedTarget.rootPath, input.message.filePath, input.message.edits, builtInPaths);
|
|
2026
2151
|
});
|
|
2027
2152
|
}
|
|
@@ -2597,13 +2722,15 @@ async function executeStreamingCommand(input) {
|
|
|
2597
2722
|
});
|
|
2598
2723
|
})
|
|
2599
2724
|
]);
|
|
2725
|
+
const canonicalWorkspaceHead = canonicalSyncTerminalHead(input.resolvedTarget);
|
|
2600
2726
|
const terminal = {
|
|
2601
2727
|
type: "exec_exit",
|
|
2602
2728
|
runId: input.message.runId,
|
|
2603
2729
|
exitCode,
|
|
2604
2730
|
durationMs: Date.now() - startedAt,
|
|
2605
2731
|
...timedOut ? { timedOut: true } : {},
|
|
2606
|
-
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2732
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
2733
|
+
...canonicalWorkspaceHead ? { canonicalWorkspaceHead } : {}
|
|
2607
2734
|
};
|
|
2608
2735
|
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
2609
2736
|
sendWorkerMessage(input.ws, terminal);
|
|
@@ -2611,12 +2738,14 @@ async function executeStreamingCommand(input) {
|
|
|
2611
2738
|
if (!started && error instanceof StaleWorkerAdmissionError) throw error;
|
|
2612
2739
|
const message = error instanceof Error ? error.message : String(error);
|
|
2613
2740
|
if (started) {
|
|
2741
|
+
const canonicalWorkspaceHead = canonicalSyncTerminalHead(input.resolvedTarget);
|
|
2614
2742
|
const terminal = {
|
|
2615
2743
|
type: "exec_error",
|
|
2616
2744
|
runId: input.message.runId,
|
|
2617
2745
|
error: message,
|
|
2618
2746
|
durationMs: Date.now() - startedAt,
|
|
2619
|
-
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {}
|
|
2747
|
+
...input.message.workspaceEffect === "none" ? { workspaceEffect: "none" } : {},
|
|
2748
|
+
...canonicalWorkspaceHead ? { canonicalWorkspaceHead } : {}
|
|
2620
2749
|
};
|
|
2621
2750
|
pendingProcessTerminals.set(input.message.runId, terminal);
|
|
2622
2751
|
sendWorkerMessage(input.ws, terminal);
|
|
@@ -3271,6 +3400,9 @@ async function startWorker(options) {
|
|
|
3271
3400
|
let workspaceGitIdentity = null;
|
|
3272
3401
|
let workspaceConfigured = false;
|
|
3273
3402
|
let activeWorkspaceIncidentId = null;
|
|
3403
|
+
let deferredWorkspaceConfiguration = null;
|
|
3404
|
+
let deferredWorkspaceConfigurationRefreshTimer;
|
|
3405
|
+
let workspaceConfigurationReceiptGeneration = 0;
|
|
3274
3406
|
let workspaceAutomaticTimer;
|
|
3275
3407
|
let workspacePeriodicTimer;
|
|
3276
3408
|
let pendingAutomaticTrigger;
|
|
@@ -3282,6 +3414,11 @@ async function startWorker(options) {
|
|
|
3282
3414
|
let cliUpdateInProgress = false;
|
|
3283
3415
|
let reloadAfterClose = false;
|
|
3284
3416
|
let shutdownAfterClose = false;
|
|
3417
|
+
const automaticWorkspaceSyncIsCurrentlyAdmitted = () => workspaceAutomaticSyncIsAdmitted({
|
|
3418
|
+
workspaceConfigured,
|
|
3419
|
+
activeIncidentId: activeWorkspaceIncidentId,
|
|
3420
|
+
deferredConfiguration: deferredWorkspaceConfiguration
|
|
3421
|
+
});
|
|
3285
3422
|
const bearerAuthHeader = `Authorization: Bearer ${token}`;
|
|
3286
3423
|
const workerCredentialUsername = credentialUsernameForAuthHeader(bearerAuthHeader);
|
|
3287
3424
|
const projectBranchKey = (projectId, branchName) => `${projectId}\0${branchName}`;
|
|
@@ -3563,6 +3700,17 @@ async function startWorker(options) {
|
|
|
3563
3700
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
3564
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);
|
|
3565
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
|
+
};
|
|
3566
3714
|
const projectBranchMountBusy = (projectId, branchName, branchPath) => !readyProjectIds.has(projectId) || projectBranchMountActivityBusy(projectId, branchName, branchPath);
|
|
3567
3715
|
const buildWorkspaceMounts = (projects = projectConfigById.values()) => {
|
|
3568
3716
|
const mounts = [];
|
|
@@ -3595,7 +3743,7 @@ async function startWorker(options) {
|
|
|
3595
3743
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
3596
3744
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
3597
3745
|
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
3598
|
-
busyForRecovery: () =>
|
|
3746
|
+
busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
|
|
3599
3747
|
});
|
|
3600
3748
|
mounts.push({
|
|
3601
3749
|
id: projectPlanMountId(project.projectId, branch.branchName),
|
|
@@ -3609,7 +3757,7 @@ async function startWorker(options) {
|
|
|
3609
3757
|
preserveLocalOnHydrationBasisChange: preservesCheckoutPathMove,
|
|
3610
3758
|
busy: () => projectBranchMountBusy(project.projectId, branch.branchName, branchPath),
|
|
3611
3759
|
mutationToken: () => projectWorkspaceMutationToken(project.projectId, branch.branchName),
|
|
3612
|
-
busyForRecovery: () =>
|
|
3760
|
+
busyForRecovery: () => projectBranchMountExclusiveSyncBusy(project.projectId, branch.branchName, branchPath)
|
|
3613
3761
|
});
|
|
3614
3762
|
}
|
|
3615
3763
|
void projectRoot;
|
|
@@ -3632,7 +3780,10 @@ async function startWorker(options) {
|
|
|
3632
3780
|
mutationToken: () => String(visibleWorkspaceMutationEpoch),
|
|
3633
3781
|
busyForRecovery: () => {
|
|
3634
3782
|
const activeTargets = activeWorkspaceMutationTargets();
|
|
3635
|
-
return
|
|
3783
|
+
return (0, import_workspace_automatic_sync_policy.workspacePlansMountBusyDuringExclusiveSync)(
|
|
3784
|
+
activeTargets,
|
|
3785
|
+
canonicalWorkspaceMutationIsActive([...credentialBearingProcessGroupTargets.values()])
|
|
3786
|
+
);
|
|
3636
3787
|
}
|
|
3637
3788
|
});
|
|
3638
3789
|
for (const deletion of pendingMirrorDeletes.values()) {
|
|
@@ -3988,6 +4139,7 @@ async function startWorker(options) {
|
|
|
3988
4139
|
...result.conflictPaths ? { conflictPaths: result.conflictPaths } : {},
|
|
3989
4140
|
...result.conflictSnapshotRefs ? { conflictSnapshotRefs: result.conflictSnapshotRefs } : {},
|
|
3990
4141
|
...result.conflictKind ? { conflictKind: result.conflictKind } : {},
|
|
4142
|
+
...result.verifiedAncestorHeads ? { verifiedAncestorHeads: result.verifiedAncestorHeads } : {},
|
|
3991
4143
|
...result.error ? { error: result.error } : {}
|
|
3992
4144
|
};
|
|
3993
4145
|
};
|
|
@@ -4012,9 +4164,9 @@ async function startWorker(options) {
|
|
|
4012
4164
|
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
4013
4165
|
throw new Error("Worker workspace configuration has not been received");
|
|
4014
4166
|
}
|
|
4015
|
-
const
|
|
4167
|
+
const configuredMounts = buildWorkspaceMounts();
|
|
4016
4168
|
if (input.resetToCanonical) {
|
|
4017
|
-
const resetHasBusyLiveMount =
|
|
4169
|
+
const resetHasBusyLiveMount = configuredMounts.some(
|
|
4018
4170
|
(mount) => !(mount.deleteWhenSourceMissing && !import_node_fs.default.existsSync(mount.sourcePath)) && mount.busy?.()
|
|
4019
4171
|
);
|
|
4020
4172
|
if (resetHasBusyLiveMount) {
|
|
@@ -4030,7 +4182,7 @@ async function startWorker(options) {
|
|
|
4030
4182
|
credentialHelper: workspaceCredentialHelper,
|
|
4031
4183
|
credentialUsername: workspaceCredentialUsername,
|
|
4032
4184
|
gitIdentity: workspaceGitIdentity,
|
|
4033
|
-
mounts
|
|
4185
|
+
mounts: configuredMounts
|
|
4034
4186
|
});
|
|
4035
4187
|
applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads2, reset.activeMountIds, true);
|
|
4036
4188
|
return {
|
|
@@ -4054,6 +4206,7 @@ async function startWorker(options) {
|
|
|
4054
4206
|
};
|
|
4055
4207
|
}
|
|
4056
4208
|
const outerRemediation = input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm";
|
|
4209
|
+
const mounts = outerRemediation ? (0, import_workspace_git_sync.workspaceGitMountsForRemediation)(configuredMounts) : configuredMounts;
|
|
4057
4210
|
const observedProjectHeads = outerRemediation ? [] : observeProjectHeadsBeforeOuterWorkspace({ allowNonFastForward: false, failClosed: false });
|
|
4058
4211
|
const inboundMoveMountIds = new Set(
|
|
4059
4212
|
observedProjectHeads.flatMap(
|
|
@@ -4073,10 +4226,13 @@ async function startWorker(options) {
|
|
|
4073
4226
|
commitDetail: input.confirmationReason ?? input.trigger.detail,
|
|
4074
4227
|
allowLargeDiff: input.confirmedLargeDiff,
|
|
4075
4228
|
skipMountMirror: outerRemediation,
|
|
4229
|
+
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4230
|
+
assertStillAdmitted: input.assertStillAdmitted,
|
|
4076
4231
|
afterWorkspacePublished: outerRemediation ? void 0 : ({ activeMountIds, publishedHead }) => {
|
|
4077
4232
|
pushChangedProjectHeads(activeMountIds, publishedHead, publicationHeads, inboundMoveMountIds);
|
|
4078
4233
|
}
|
|
4079
4234
|
});
|
|
4235
|
+
input.assertStillAdmitted?.();
|
|
4080
4236
|
if (["no_change", "updated", "pushed"].includes(result.outcome) && !outerRemediation) {
|
|
4081
4237
|
applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads, result.activeMountIds, false);
|
|
4082
4238
|
}
|
|
@@ -4099,6 +4255,28 @@ async function startWorker(options) {
|
|
|
4099
4255
|
const runWorkspaceSync = async (input) => {
|
|
4100
4256
|
const attemptId = input.attemptId ?? crypto.randomUUID();
|
|
4101
4257
|
const requestedAt = Date.now();
|
|
4258
|
+
const admittedGeneration = workerAdmissionGeneration;
|
|
4259
|
+
const admittedActiveIncidentId = activeWorkspaceIncidentId;
|
|
4260
|
+
const admittedDeferredIncidentId = deferredWorkspaceConfiguration?.incidentId ?? null;
|
|
4261
|
+
const assertStillAdmitted = () => {
|
|
4262
|
+
if (admittedGeneration !== workerAdmissionGeneration || admittedActiveIncidentId !== activeWorkspaceIncidentId || admittedDeferredIncidentId !== (deferredWorkspaceConfiguration?.incidentId ?? null) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || !deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4263
|
+
throw new Error("Workspace synchronization admission changed while asynchronous work was in flight");
|
|
4264
|
+
}
|
|
4265
|
+
};
|
|
4266
|
+
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4267
|
+
const result2 = {
|
|
4268
|
+
...failedWorkspaceSyncResult(
|
|
4269
|
+
attemptId,
|
|
4270
|
+
input.trigger,
|
|
4271
|
+
new Error(
|
|
4272
|
+
`Workspace synchronization is deferred for incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}; only remediation synchronization is allowed`
|
|
4273
|
+
)
|
|
4274
|
+
),
|
|
4275
|
+
telemetry: { totalMs: 0, queueMs: 0, prepareMs: 0, synchronizeMs: 0 }
|
|
4276
|
+
};
|
|
4277
|
+
if (input.sendResult !== false) sendWorkspaceSyncResult(input.requestId, result2);
|
|
4278
|
+
return result2;
|
|
4279
|
+
}
|
|
4102
4280
|
if (!input.requestId && input.sendResult !== false) {
|
|
4103
4281
|
if (currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
4104
4282
|
throw new Error("Cannot start autonomous workspace synchronization without an open worker control socket");
|
|
@@ -4113,23 +4291,32 @@ async function startWorker(options) {
|
|
|
4113
4291
|
result = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
4114
4292
|
queueEnteredAt = Date.now();
|
|
4115
4293
|
syncStartedAt = Date.now();
|
|
4294
|
+
if (!deferredWorkspaceSyncTriggerIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, input.trigger)) {
|
|
4295
|
+
return failedWorkspaceSyncResult(
|
|
4296
|
+
attemptId,
|
|
4297
|
+
input.trigger,
|
|
4298
|
+
new Error(
|
|
4299
|
+
`Workspace synchronization was fenced while queued by incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
4300
|
+
)
|
|
4301
|
+
);
|
|
4302
|
+
}
|
|
4116
4303
|
try {
|
|
4304
|
+
assertStillAdmitted();
|
|
4117
4305
|
return await performWorkspaceSync({
|
|
4118
4306
|
attemptId,
|
|
4119
4307
|
trigger: input.trigger,
|
|
4120
4308
|
confirmedLargeDiff: input.confirmedLargeDiff,
|
|
4121
4309
|
confirmationReason: input.confirmationReason,
|
|
4122
|
-
resetToCanonical: input.resetToCanonical
|
|
4310
|
+
resetToCanonical: input.resetToCanonical,
|
|
4311
|
+
requiredAncestorHeads: input.requiredAncestorHeads,
|
|
4312
|
+
assertStillAdmitted
|
|
4123
4313
|
});
|
|
4124
4314
|
} catch (error) {
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
hydrationCurrent = false;
|
|
4131
|
-
}
|
|
4132
|
-
}
|
|
4315
|
+
const hydrationCurrent = workspaceSyncFailureHydrationIsSafe({
|
|
4316
|
+
error,
|
|
4317
|
+
resetToCanonical: input.resetToCanonical === true,
|
|
4318
|
+
inspectCurrentHydration: () => (0, import_workspace_git_sync.workspaceGitHydrationIsCurrent)(workspaceShadowRoot, buildWorkspaceMounts())
|
|
4319
|
+
});
|
|
4133
4320
|
if (!hydrationCurrent) {
|
|
4134
4321
|
process.stderr.write(
|
|
4135
4322
|
`[r5d-worker] workspace synchronization failed with an incompletely hydrated visible tree; exiting for exact recovery: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -4171,7 +4358,7 @@ async function startWorker(options) {
|
|
|
4171
4358
|
pendingCreatedBranchPublicationNotBefore
|
|
4172
4359
|
);
|
|
4173
4360
|
const initialDeferral = pendingBranchDeferral();
|
|
4174
|
-
if (!
|
|
4361
|
+
if (!automaticWorkspaceSyncIsCurrentlyAdmitted() || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN || automaticSyncInFlight || initialDeferral?.kind === "active_target") {
|
|
4175
4362
|
return;
|
|
4176
4363
|
}
|
|
4177
4364
|
if (workspaceAutomaticTimer) clearTimeout(workspaceAutomaticTimer);
|
|
@@ -4179,7 +4366,7 @@ async function startWorker(options) {
|
|
|
4179
4366
|
() => {
|
|
4180
4367
|
workspaceAutomaticTimer = void 0;
|
|
4181
4368
|
const scheduledTrigger = pendingAutomaticTrigger;
|
|
4182
|
-
if (!scheduledTrigger || !
|
|
4369
|
+
if (!scheduledTrigger || !automaticWorkspaceSyncIsCurrentlyAdmitted() || currentWorkerSocket !== ws) return;
|
|
4183
4370
|
const currentDeferral = pendingBranchDeferral();
|
|
4184
4371
|
if (currentDeferral?.kind === "active_target") return;
|
|
4185
4372
|
if (currentDeferral?.kind === "creation_grace") {
|
|
@@ -4201,7 +4388,7 @@ async function startWorker(options) {
|
|
|
4201
4388
|
}).finally(() => {
|
|
4202
4389
|
automaticSyncInFlight = false;
|
|
4203
4390
|
heartbeatBusyGrace = (0, import_heartbeat.grantWorkerHeartbeatBusyGrace)(lastServerHeartbeatAt, heartbeatBusyGrace);
|
|
4204
|
-
if (pendingAutomaticTrigger &&
|
|
4391
|
+
if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
4205
4392
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, WORKSPACE_GIT_QUIET_MS);
|
|
4206
4393
|
}
|
|
4207
4394
|
});
|
|
@@ -4217,6 +4404,11 @@ async function startWorker(options) {
|
|
|
4217
4404
|
const targetMayMutateVisibleWorkspace = (target) => target.type === "project" || target.rootProfile === "visible_projects";
|
|
4218
4405
|
const resolveMessageTarget = (target) => {
|
|
4219
4406
|
if (!workspaceConfigured) throw new Error("Worker workspace configuration has not completed successfully");
|
|
4407
|
+
if (!deferredWorkspaceTargetIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, target)) {
|
|
4408
|
+
throw new Error(
|
|
4409
|
+
`Worker workspace configuration is fenced for incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}; only canonical remediation commands are allowed`
|
|
4410
|
+
);
|
|
4411
|
+
}
|
|
4220
4412
|
if (target.type === "project" && !readyProjectIds.has(target.projectId)) {
|
|
4221
4413
|
throw new Error(`Project ${target.projectId} is not ready on this worker`);
|
|
4222
4414
|
}
|
|
@@ -4230,7 +4422,26 @@ async function startWorker(options) {
|
|
|
4230
4422
|
projectConfigById
|
|
4231
4423
|
});
|
|
4232
4424
|
};
|
|
4233
|
-
const configureWorkerWorkspace = async (message) => {
|
|
4425
|
+
const configureWorkerWorkspace = async (message, receiptGeneration) => {
|
|
4426
|
+
const incidentDeferral = workspaceConfigurationIncidentDeferral({
|
|
4427
|
+
requestedIncidentId: message.deferWorkspaceSyncForIncidentId,
|
|
4428
|
+
activeIncidentId: activeWorkspaceIncidentId
|
|
4429
|
+
});
|
|
4430
|
+
if (incidentDeferral.error) {
|
|
4431
|
+
workspaceConfigured = false;
|
|
4432
|
+
return {
|
|
4433
|
+
result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, incidentDeferral.error),
|
|
4434
|
+
pending: incidentDeferredProjectCheckouts(message.projects),
|
|
4435
|
+
aheadOfOriginBranches: []
|
|
4436
|
+
};
|
|
4437
|
+
}
|
|
4438
|
+
if (incidentDeferral.incidentId) {
|
|
4439
|
+
workspaceConfigured = false;
|
|
4440
|
+
deferredWorkspaceConfiguration = {
|
|
4441
|
+
incidentId: incidentDeferral.incidentId,
|
|
4442
|
+
serverRefreshExpected: message.deferWorkspaceSyncForIncidentId === incidentDeferral.incidentId
|
|
4443
|
+
};
|
|
4444
|
+
}
|
|
4234
4445
|
const incomingCredentialGenerationFingerprint = credentialGenerationFingerprint({
|
|
4235
4446
|
...message,
|
|
4236
4447
|
workerBaseUrl: baseUrl,
|
|
@@ -4241,6 +4452,22 @@ async function startWorker(options) {
|
|
|
4241
4452
|
pendingFingerprintAtProcessStart: pendingCredentialGenerationIntentAtProcessStart?.fingerprint ?? null,
|
|
4242
4453
|
incomingFingerprint: incomingCredentialGenerationFingerprint
|
|
4243
4454
|
});
|
|
4455
|
+
if (deferredCredentialTransitionMustWait({
|
|
4456
|
+
incidentId: incidentDeferral.incidentId,
|
|
4457
|
+
transitionPhase: credentialTransitionPhase,
|
|
4458
|
+
canonicalRemediationActive: canonicalWorkspaceMutationIsActive(activeWorkspaceMutationTargets())
|
|
4459
|
+
})) {
|
|
4460
|
+
return {
|
|
4461
|
+
result: failedWorkspaceSyncResult(
|
|
4462
|
+
crypto.randomUUID(),
|
|
4463
|
+
{ type: "connect" },
|
|
4464
|
+
new Error("Workspace credential rotation is deferred until the active canonical remediation command finishes")
|
|
4465
|
+
),
|
|
4466
|
+
pending: incidentDeferredProjectCheckouts(message.projects),
|
|
4467
|
+
aheadOfOriginBranches: [],
|
|
4468
|
+
...incidentDeferral.incidentId ? { deferredWorkspaceSyncForIncidentId: incidentDeferral.incidentId } : {}
|
|
4469
|
+
};
|
|
4470
|
+
}
|
|
4244
4471
|
const credentialReapStatus = credentialReapContractStatus();
|
|
4245
4472
|
const verifiedCredentialReapContractNow = credentialReapStatus === "verified_systemd";
|
|
4246
4473
|
const credentialRestartIsContained = credentialGenerationRestartIsContained({
|
|
@@ -4354,6 +4581,12 @@ async function startWorker(options) {
|
|
|
4354
4581
|
workspaceSyncRequestsInFlight += 1;
|
|
4355
4582
|
try {
|
|
4356
4583
|
return await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
4584
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4585
|
+
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
4586
|
+
}
|
|
4587
|
+
if (!workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId)) {
|
|
4588
|
+
throw new Error("Workspace configuration was superseded by a different active workspace incident");
|
|
4589
|
+
}
|
|
4357
4590
|
for (const project of message.projects) (0, import_repository_transition_policy.assertRepositoryTransitionState)(project);
|
|
4358
4591
|
const busyConfigurationChanges = (0, import_workspace_project_config_policy.busyProjectConfigurationChangeIds)({
|
|
4359
4592
|
currentProjects: [...projectConfigById.values()],
|
|
@@ -4375,10 +4608,12 @@ async function startWorker(options) {
|
|
|
4375
4608
|
const preserveOnlyBranches = message.projects.flatMap(
|
|
4376
4609
|
(project) => project.preserveOnlyBranches.map(({ branchId, branchName }) => ({ branchId, projectId: project.projectId, branchName }))
|
|
4377
4610
|
);
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4611
|
+
if (!incidentDeferral.incidentId) {
|
|
4612
|
+
projectWorkspaceState = projectWorkspaceStateStore.reconcile({
|
|
4613
|
+
desiredProjects: message.projects,
|
|
4614
|
+
preserveOnlyBranches
|
|
4615
|
+
});
|
|
4616
|
+
}
|
|
4382
4617
|
const stillPendingCreatedBranches = new Map(
|
|
4383
4618
|
projectWorkspaceState.locallyPendingCreatedBranches.map((branch) => [
|
|
4384
4619
|
(0, import_workspace_automatic_sync_policy.pendingCreatedBranchKey)(branch.projectId, branch.branchName),
|
|
@@ -4438,6 +4673,12 @@ async function startWorker(options) {
|
|
|
4438
4673
|
void 0,
|
|
4439
4674
|
credentialPublicationPreauthorized
|
|
4440
4675
|
);
|
|
4676
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4677
|
+
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
4678
|
+
}
|
|
4679
|
+
if (!workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId)) {
|
|
4680
|
+
throw new Error("Workspace configuration was superseded by a different active workspace incident");
|
|
4681
|
+
}
|
|
4441
4682
|
configuredCredentialGenerationFingerprint = incomingCredentialGenerationFingerprint;
|
|
4442
4683
|
pendingCredentialGenerationIntentAtProcessStart = null;
|
|
4443
4684
|
bootstrapCredentialGenerationFingerprintAtProcessStart = null;
|
|
@@ -4452,6 +4693,34 @@ async function startWorker(options) {
|
|
|
4452
4693
|
branches: project.branches.filter(({ branchName }) => !pendingMirrorDeletes.has(projectBranchKey(project.projectId, branchName)))
|
|
4453
4694
|
}));
|
|
4454
4695
|
const nextProjectConfigById = new Map(effectiveProjects.map((project) => [project.projectId, project]));
|
|
4696
|
+
if (incidentDeferral.incidentId) {
|
|
4697
|
+
(0, import_workspace_git_sync.configureExistingWorkspaceGitForRemediation)({
|
|
4698
|
+
workspacePath: workspaceShadowRoot,
|
|
4699
|
+
remoteUrl: message.workspaceRemoteUrl,
|
|
4700
|
+
credentialHelper: nextWorkspaceCredentialHelper,
|
|
4701
|
+
credentialUsername: workerCredentialUsername,
|
|
4702
|
+
gitIdentity: message.gitIdentity
|
|
4703
|
+
});
|
|
4704
|
+
projectConfigById.clear();
|
|
4705
|
+
for (const project of effectiveProjects) projectConfigById.set(project.projectId, project);
|
|
4706
|
+
readyProjectIds.clear();
|
|
4707
|
+
reconciledProjectConfigFingerprints.clear();
|
|
4708
|
+
pendingCheckouts.clear();
|
|
4709
|
+
const pending2 = incidentDeferredProjectCheckouts(message.projects);
|
|
4710
|
+
for (const checkout of pending2) pendingCheckouts.set(projectBranchKey(checkout.projectId, checkout.branchName), checkout);
|
|
4711
|
+
workspaceConfigured = activeWorkspaceIncidentId === incidentDeferral.incidentId;
|
|
4712
|
+
return {
|
|
4713
|
+
result: deferredIncidentWorkspaceConfigurationResult({
|
|
4714
|
+
attemptId: crypto.randomUUID(),
|
|
4715
|
+
workerLabel: label,
|
|
4716
|
+
head: workspaceLocalHead(),
|
|
4717
|
+
skippedMountIds: buildWorkspaceMounts().map(({ id }) => id)
|
|
4718
|
+
}),
|
|
4719
|
+
pending: pending2,
|
|
4720
|
+
aheadOfOriginBranches: [],
|
|
4721
|
+
deferredWorkspaceSyncForIncidentId: incidentDeferral.incidentId
|
|
4722
|
+
};
|
|
4723
|
+
}
|
|
4455
4724
|
(0, import_workspace_git_sync.ensureWorkspaceGitClone)({
|
|
4456
4725
|
workspacePath: workspaceShadowRoot,
|
|
4457
4726
|
remoteUrl: message.workspaceRemoteUrl,
|
|
@@ -4519,10 +4788,25 @@ async function startWorker(options) {
|
|
|
4519
4788
|
{ ignoreBusy: true }
|
|
4520
4789
|
);
|
|
4521
4790
|
ensureConfiguredProjects();
|
|
4791
|
+
const configurationSyncAdmissionGeneration = workerAdmissionGeneration;
|
|
4792
|
+
const assertConfigurationSyncStillAdmitted = () => {
|
|
4793
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration || configurationSyncAdmissionGeneration !== workerAdmissionGeneration || !workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId) || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
|
|
4794
|
+
throw new Error("Workspace configuration synchronization admission changed while asynchronous work was in flight");
|
|
4795
|
+
}
|
|
4796
|
+
};
|
|
4522
4797
|
const result = await performWorkspaceSync({
|
|
4523
4798
|
attemptId: crypto.randomUUID(),
|
|
4524
|
-
trigger: { type: "connect" }
|
|
4799
|
+
trigger: { type: "connect" },
|
|
4800
|
+
resetToCanonical: workspaceConfigurationResetToCanonicalIsAllowed(message.resetToCanonical, incidentDeferral.incidentId),
|
|
4801
|
+
assertStillAdmitted: assertConfigurationSyncStillAdmitted
|
|
4525
4802
|
});
|
|
4803
|
+
assertConfigurationSyncStillAdmitted();
|
|
4804
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4805
|
+
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
4806
|
+
}
|
|
4807
|
+
if (!workspaceConfigurationIncidentSnapshotIsCurrent(incidentDeferral.incidentId, activeWorkspaceIncidentId)) {
|
|
4808
|
+
throw new Error("Workspace configuration was superseded by a different active workspace incident");
|
|
4809
|
+
}
|
|
4526
4810
|
const publishedHead = result.publishedHead ?? result.localHead ?? result.startingHead;
|
|
4527
4811
|
if (publishedHead && ["no_change", "published", "updated", "conflict_reset", "reset"].includes(result.outcome)) {
|
|
4528
4812
|
const activeMountIds = new Set(result.activeMountIds ?? []);
|
|
@@ -4539,6 +4823,11 @@ async function startWorker(options) {
|
|
|
4539
4823
|
});
|
|
4540
4824
|
}
|
|
4541
4825
|
}
|
|
4826
|
+
if (deferredWorkspaceConfigurationRefreshTimer) {
|
|
4827
|
+
clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
4828
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
4829
|
+
}
|
|
4830
|
+
deferredWorkspaceConfiguration = null;
|
|
4542
4831
|
workspaceConfigured = true;
|
|
4543
4832
|
const pending = [...pendingCheckouts.values()].sort(
|
|
4544
4833
|
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
@@ -4546,6 +4835,13 @@ async function startWorker(options) {
|
|
|
4546
4835
|
return { result, pending, aheadOfOriginBranches: collectAheadOfOriginBranches() };
|
|
4547
4836
|
});
|
|
4548
4837
|
} catch (error) {
|
|
4838
|
+
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
4839
|
+
return {
|
|
4840
|
+
result: failedWorkspaceSyncResult(crypto.randomUUID(), { type: "connect" }, error),
|
|
4841
|
+
pending: incidentDeferredProjectCheckouts(message.projects),
|
|
4842
|
+
aheadOfOriginBranches: []
|
|
4843
|
+
};
|
|
4844
|
+
}
|
|
4549
4845
|
if (error instanceof import_registry_auth.RegistryAuthConfigurationError) {
|
|
4550
4846
|
workspaceConfigured = false;
|
|
4551
4847
|
githubCredential = null;
|
|
@@ -4600,7 +4896,7 @@ async function startWorker(options) {
|
|
|
4600
4896
|
workspaceAutomaticTimer = void 0;
|
|
4601
4897
|
}
|
|
4602
4898
|
void (async () => {
|
|
4603
|
-
if (
|
|
4899
|
+
if (automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
4604
4900
|
await runWorkspaceSync({
|
|
4605
4901
|
trigger: { type: "manual", detail: "graceful worker shutdown" }
|
|
4606
4902
|
});
|
|
@@ -4649,7 +4945,10 @@ async function startWorker(options) {
|
|
|
4649
4945
|
updateClis: true,
|
|
4650
4946
|
browserPortForwarding: true,
|
|
4651
4947
|
execStdinV1: true,
|
|
4652
|
-
ptyEnvFilesV1: true
|
|
4948
|
+
ptyEnvFilesV1: true,
|
|
4949
|
+
workspaceRemediationAncestorGuardV1: true,
|
|
4950
|
+
workspaceIncidentConfigDeferralV1: true,
|
|
4951
|
+
workspaceConfigResetToCanonicalV1: true
|
|
4653
4952
|
},
|
|
4654
4953
|
projectRoot: projectsRoot,
|
|
4655
4954
|
artifactRoot,
|
|
@@ -4685,26 +4984,35 @@ async function startWorker(options) {
|
|
|
4685
4984
|
return;
|
|
4686
4985
|
}
|
|
4687
4986
|
if (message.type === "workspace_config") {
|
|
4987
|
+
const receiptGeneration = ++workspaceConfigurationReceiptGeneration;
|
|
4988
|
+
const refreshesDeferredConfiguration = deferredWorkspaceConfiguration !== null && activeWorkspaceIncidentId === null && message.deferWorkspaceSyncForIncidentId === void 0;
|
|
4688
4989
|
advanceWorkerAdmissionGeneration();
|
|
4689
|
-
const configured = await configureWorkerWorkspace(message);
|
|
4990
|
+
const configured = await configureWorkerWorkspace(message, receiptGeneration);
|
|
4690
4991
|
sendWorkerMessageFromCurrentSource(ws, {
|
|
4691
4992
|
type: "workspace_configured",
|
|
4692
4993
|
requestId: message.requestId,
|
|
4693
4994
|
result: configured.result,
|
|
4694
4995
|
pendingCheckouts: configured.pending,
|
|
4695
|
-
aheadOfOriginBranches: configured.aheadOfOriginBranches
|
|
4996
|
+
aheadOfOriginBranches: configured.aheadOfOriginBranches,
|
|
4997
|
+
...configured.deferredWorkspaceSyncForIncidentId ? { deferredWorkspaceSyncForIncidentId: configured.deferredWorkspaceSyncForIncidentId } : {}
|
|
4696
4998
|
});
|
|
4697
4999
|
process.stdout.write(
|
|
4698
5000
|
`[r5d-worker] workspace configured: ${message.projects.length} project(s), ${configured.pending.length} pending checkout(s)
|
|
4699
5001
|
`
|
|
4700
5002
|
);
|
|
5003
|
+
if (refreshesDeferredConfiguration && configured.result.outcome === "failed" && deferredWorkspaceConfiguration !== null && currentWorkerSocket === ws) {
|
|
5004
|
+
workspaceConfigured = false;
|
|
5005
|
+
advanceWorkerAdmissionGeneration();
|
|
5006
|
+
ws.close(1012, "Deferred workspace configuration refresh failed");
|
|
5007
|
+
return;
|
|
5008
|
+
}
|
|
4701
5009
|
if (!workspacePeriodicTimer) {
|
|
4702
5010
|
workspacePeriodicTimer = setInterval(() => {
|
|
4703
5011
|
scheduleAutomaticWorkspaceSync({ type: "periodic", detail: "periodic workspace reconciliation" }, 0);
|
|
4704
5012
|
}, WORKSPACE_GIT_PERIODIC_MS);
|
|
4705
5013
|
workspacePeriodicTimer.unref();
|
|
4706
5014
|
}
|
|
4707
|
-
if (pendingAutomaticTrigger &&
|
|
5015
|
+
if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
4708
5016
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, 0);
|
|
4709
5017
|
}
|
|
4710
5018
|
return;
|
|
@@ -4716,11 +5024,20 @@ async function startWorker(options) {
|
|
|
4716
5024
|
trigger: message.trigger,
|
|
4717
5025
|
confirmedLargeDiff: message.confirmedLargeDiff,
|
|
4718
5026
|
confirmationReason: message.confirmationReason,
|
|
4719
|
-
resetToCanonical: message.resetToCanonical
|
|
5027
|
+
resetToCanonical: message.resetToCanonical,
|
|
5028
|
+
requiredAncestorHeads: message.requiredAncestorHeads
|
|
4720
5029
|
});
|
|
4721
5030
|
return;
|
|
4722
5031
|
}
|
|
4723
5032
|
if (message.type === "create_project_branch") {
|
|
5033
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5034
|
+
sendWorkerMessage(ws, {
|
|
5035
|
+
type: "operation_result",
|
|
5036
|
+
requestId: message.requestId,
|
|
5037
|
+
error: `Project operations are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5038
|
+
});
|
|
5039
|
+
return;
|
|
5040
|
+
}
|
|
4724
5041
|
try {
|
|
4725
5042
|
const pendingBranch = {
|
|
4726
5043
|
branchId: message.branchId,
|
|
@@ -4728,6 +5045,11 @@ async function startWorker(options) {
|
|
|
4728
5045
|
branchName: message.targetBranch
|
|
4729
5046
|
};
|
|
4730
5047
|
const created = await workspaceSyncSingleFlight.runMutation(() => {
|
|
5048
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5049
|
+
throw new Error(
|
|
5050
|
+
`Project branch creation was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5051
|
+
);
|
|
5052
|
+
}
|
|
4731
5053
|
const project = projectConfigById.get(message.projectId);
|
|
4732
5054
|
if (!project) throw new Error(`Project ${message.projectId} is missing from the worker workspace configuration`);
|
|
4733
5055
|
(0, import_repository_transition_policy.assertRepositoryExecutionEnabled)(project);
|
|
@@ -4811,9 +5133,22 @@ async function startWorker(options) {
|
|
|
4811
5133
|
return;
|
|
4812
5134
|
}
|
|
4813
5135
|
if (message.type === "delete_project_branch") {
|
|
5136
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5137
|
+
sendWorkerMessage(ws, {
|
|
5138
|
+
type: "operation_result",
|
|
5139
|
+
requestId: message.requestId,
|
|
5140
|
+
error: `Project operations are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5141
|
+
});
|
|
5142
|
+
return;
|
|
5143
|
+
}
|
|
4814
5144
|
try {
|
|
4815
5145
|
const deletionKey = projectBranchKey(message.projectId, message.branchName);
|
|
4816
5146
|
const deletionNeedsSync = await workspaceSyncSingleFlight.runMutation(() => {
|
|
5147
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5148
|
+
throw new Error(
|
|
5149
|
+
`Project branch deletion was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5150
|
+
);
|
|
5151
|
+
}
|
|
4817
5152
|
const project = projectConfigById.get(message.projectId);
|
|
4818
5153
|
if (!project) throw new Error(`Project ${message.projectId} is missing from the worker workspace configuration`);
|
|
4819
5154
|
(0, import_repository_transition_policy.assertRepositoryExecutionEnabled)(project);
|
|
@@ -4913,9 +5248,37 @@ async function startWorker(options) {
|
|
|
4913
5248
|
}
|
|
4914
5249
|
if (message.type === "workspace_incident_updated") {
|
|
4915
5250
|
const previousIncidentId = activeWorkspaceIncidentId;
|
|
4916
|
-
|
|
5251
|
+
const nextIncidentId = (0, import_workspace_incident_state.applyWorkspaceIncidentUpdate)(activeWorkspaceIncidentId, message);
|
|
5252
|
+
if (nextIncidentId !== previousIncidentId) advanceWorkerAdmissionGeneration();
|
|
5253
|
+
activeWorkspaceIncidentId = nextIncidentId;
|
|
4917
5254
|
if (previousIncidentId && !activeWorkspaceIncidentId && (message.status === "resolved" || message.status === "confirmed" || message.status === "reset")) {
|
|
4918
|
-
|
|
5255
|
+
const refresh = workspaceIncidentTerminalRefreshFence({
|
|
5256
|
+
deferredConfiguration: deferredWorkspaceConfiguration,
|
|
5257
|
+
clearedIncidentId: previousIncidentId
|
|
5258
|
+
});
|
|
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();
|
|
5280
|
+
}
|
|
5281
|
+
return;
|
|
4919
5282
|
}
|
|
4920
5283
|
return;
|
|
4921
5284
|
}
|
|
@@ -5018,6 +5381,12 @@ async function startWorker(options) {
|
|
|
5018
5381
|
sendAck({ error: `Process run ${message.runId} is not active on this worker` });
|
|
5019
5382
|
return;
|
|
5020
5383
|
}
|
|
5384
|
+
if (!deferredWorkspaceTargetIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, active.target)) {
|
|
5385
|
+
sendAck({
|
|
5386
|
+
error: `Process input is deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
5387
|
+
});
|
|
5388
|
+
return;
|
|
5389
|
+
}
|
|
5021
5390
|
if (!active.interactive || !active.stdin) {
|
|
5022
5391
|
sendAck({
|
|
5023
5392
|
error: `Process run ${message.runId} has no open stdin. Start a new shell command with "interactive": true to write to its stdin.`
|
|
@@ -5063,6 +5432,15 @@ async function startWorker(options) {
|
|
|
5063
5432
|
});
|
|
5064
5433
|
return;
|
|
5065
5434
|
}
|
|
5435
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5436
|
+
sendWorkerMessage(ws, {
|
|
5437
|
+
type: "pty_error",
|
|
5438
|
+
requestId: message.requestId,
|
|
5439
|
+
ptyId: message.ptyId,
|
|
5440
|
+
error: `Shells are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5441
|
+
});
|
|
5442
|
+
return;
|
|
5443
|
+
}
|
|
5066
5444
|
await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(message.target, workspaceSyncSingleFlight, () => {
|
|
5067
5445
|
workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
|
|
5068
5446
|
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
@@ -5071,6 +5449,11 @@ async function startWorker(options) {
|
|
|
5071
5449
|
let mutationLeaseTransferred = false;
|
|
5072
5450
|
try {
|
|
5073
5451
|
releaseWorkspaceMutation = await (0, import_workspace_command_sync_policy.acquireWorkspaceCommandMutation)(message.target, workspaceSyncSingleFlight);
|
|
5452
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5453
|
+
throw new Error(
|
|
5454
|
+
`Shell opening was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5455
|
+
);
|
|
5456
|
+
}
|
|
5074
5457
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
5075
5458
|
process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}
|
|
5076
5459
|
`);
|
|
@@ -5103,6 +5486,14 @@ async function startWorker(options) {
|
|
|
5103
5486
|
}
|
|
5104
5487
|
if (message.type === "pty_input") {
|
|
5105
5488
|
const activePty = activePtys.get(message.ptyId);
|
|
5489
|
+
if (activePty && workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5490
|
+
sendWorkerMessage(ws, {
|
|
5491
|
+
type: "pty_error",
|
|
5492
|
+
ptyId: message.ptyId,
|
|
5493
|
+
error: `Shell input is deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId ?? "unknown"}`
|
|
5494
|
+
});
|
|
5495
|
+
return;
|
|
5496
|
+
}
|
|
5106
5497
|
if (activePty && targetMayMutateVisibleWorkspace(activePty.target)) {
|
|
5107
5498
|
markWorkspaceDirty({ type: "shell_inline", detail: `pty ${message.ptyId}` }, false, activePty.target);
|
|
5108
5499
|
}
|
|
@@ -5132,6 +5523,17 @@ async function startWorker(options) {
|
|
|
5132
5523
|
});
|
|
5133
5524
|
return;
|
|
5134
5525
|
}
|
|
5526
|
+
if (!workspaceCommandTransportIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, "exec")) {
|
|
5527
|
+
sendWorkerMessage(ws, {
|
|
5528
|
+
type: "exec_result",
|
|
5529
|
+
requestId: message.requestId,
|
|
5530
|
+
stdout: "",
|
|
5531
|
+
stderr: "",
|
|
5532
|
+
exitCode: 1,
|
|
5533
|
+
error: `One-shot commands are deferred for workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5534
|
+
});
|
|
5535
|
+
return;
|
|
5536
|
+
}
|
|
5135
5537
|
let result;
|
|
5136
5538
|
let targetReserved = false;
|
|
5137
5539
|
const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
|
|
@@ -5144,6 +5546,11 @@ async function startWorker(options) {
|
|
|
5144
5546
|
});
|
|
5145
5547
|
}
|
|
5146
5548
|
const runCommand = async () => {
|
|
5549
|
+
if (!workspaceCommandTransportIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, "exec")) {
|
|
5550
|
+
throw new Error(
|
|
5551
|
+
`One-shot command was fenced while queued by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5552
|
+
);
|
|
5553
|
+
}
|
|
5147
5554
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
5148
5555
|
process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
|
|
5149
5556
|
`);
|
|
@@ -5154,7 +5561,14 @@ async function startWorker(options) {
|
|
|
5154
5561
|
token,
|
|
5155
5562
|
artifactRoot,
|
|
5156
5563
|
planRoot,
|
|
5157
|
-
assertAdmission:
|
|
5564
|
+
assertAdmission: () => {
|
|
5565
|
+
if (!workspaceCommandTransportIsAllowed(deferredWorkspaceConfiguration, activeWorkspaceIncidentId, "exec")) {
|
|
5566
|
+
throw new Error(
|
|
5567
|
+
`One-shot command was fenced before spawn by workspace incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}`
|
|
5568
|
+
);
|
|
5569
|
+
}
|
|
5570
|
+
assertMessageAdmission();
|
|
5571
|
+
}
|
|
5158
5572
|
});
|
|
5159
5573
|
};
|
|
5160
5574
|
result = await (0, import_workspace_command_sync_policy.runWorkspaceCommand)(message.target, workspaceSyncSingleFlight, runCommand);
|
|
@@ -5237,7 +5651,12 @@ async function startWorker(options) {
|
|
|
5237
5651
|
if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
|
|
5238
5652
|
});
|
|
5239
5653
|
}
|
|
5240
|
-
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
|
+
});
|
|
5241
5660
|
} finally {
|
|
5242
5661
|
if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
|
|
5243
5662
|
}
|
|
@@ -5254,6 +5673,14 @@ async function startWorker(options) {
|
|
|
5254
5673
|
return;
|
|
5255
5674
|
}
|
|
5256
5675
|
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes" || message.type === "code_list" || message.type === "code_read") {
|
|
5676
|
+
if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
|
|
5677
|
+
sendWorkerMessage(ws, {
|
|
5678
|
+
type: "operation_result",
|
|
5679
|
+
requestId: message.requestId,
|
|
5680
|
+
error: `Workspace operations are deferred for incident ${deferredWorkspaceConfiguration?.incidentId ?? activeWorkspaceIncidentId}; use a canonical remediation shell`
|
|
5681
|
+
});
|
|
5682
|
+
return;
|
|
5683
|
+
}
|
|
5257
5684
|
const reservesVisibleWorkspace = targetMayMutateVisibleWorkspace(message.target);
|
|
5258
5685
|
const mutatesVisibleWorkspace = (message.type === "write" || message.type === "edit") && targetMayMutateVisibleWorkspace(message.target);
|
|
5259
5686
|
if (reservesVisibleWorkspace) {
|
|
@@ -5272,7 +5699,8 @@ async function startWorker(options) {
|
|
|
5272
5699
|
baseUrl,
|
|
5273
5700
|
token,
|
|
5274
5701
|
artifactRoot,
|
|
5275
|
-
planRoot
|
|
5702
|
+
planRoot,
|
|
5703
|
+
assertAdmission: assertMessageAdmission
|
|
5276
5704
|
});
|
|
5277
5705
|
});
|
|
5278
5706
|
ws.send(
|
|
@@ -5302,7 +5730,7 @@ async function startWorker(options) {
|
|
|
5302
5730
|
} finally {
|
|
5303
5731
|
if (reservesVisibleWorkspace) {
|
|
5304
5732
|
workspaceSyncPriorityOperationTargets.delete(message.requestId);
|
|
5305
|
-
if (pendingAutomaticTrigger &&
|
|
5733
|
+
if (pendingAutomaticTrigger && automaticWorkspaceSyncIsCurrentlyAdmitted()) {
|
|
5306
5734
|
scheduleAutomaticWorkspaceSync(pendingAutomaticTrigger, dirtyTrigger ? WORKSPACE_GIT_QUIET_MS : 0);
|
|
5307
5735
|
}
|
|
5308
5736
|
}
|
|
@@ -5338,6 +5766,10 @@ async function startWorker(options) {
|
|
|
5338
5766
|
clearInterval(workspacePeriodicTimer);
|
|
5339
5767
|
workspacePeriodicTimer = void 0;
|
|
5340
5768
|
}
|
|
5769
|
+
if (deferredWorkspaceConfigurationRefreshTimer) {
|
|
5770
|
+
clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
5771
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
5772
|
+
}
|
|
5341
5773
|
if (terminalReplayTimer) {
|
|
5342
5774
|
clearInterval(terminalReplayTimer);
|
|
5343
5775
|
terminalReplayTimer = void 0;
|