@ricsam/r5d-worker 0.0.119 → 0.0.121
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 +111 -44
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-git-sync.cjs +3 -1
- package/dist/cjs/workspace-incident-state.cjs +7 -2
- package/dist/mjs/main.mjs +112 -45
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-git-sync.mjs +3 -1
- package/dist/mjs/workspace-incident-state.mjs +5 -1
- package/dist/types/main.d.ts +9 -4
- package/dist/types/workspace-incident-state.d.ts +14 -0
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -940,24 +940,43 @@ function tryGit(args, options = {}) {
|
|
|
940
940
|
return false;
|
|
941
941
|
}
|
|
942
942
|
}
|
|
943
|
+
class GitTransportTimeoutError extends Error {
|
|
944
|
+
}
|
|
943
945
|
async function runGitAsync(args, options = {}) {
|
|
944
946
|
const command = workerGitCommand.commandArgs(args);
|
|
947
|
+
const bounded = options.timeoutMs !== void 0;
|
|
945
948
|
const subprocess = Bun.spawn(command, {
|
|
946
949
|
cwd: options.cwd,
|
|
947
950
|
stdin: "ignore",
|
|
948
951
|
stdout: "pipe",
|
|
949
952
|
stderr: "pipe",
|
|
950
|
-
env: (0, import_git_process_environment.workerGitProcessEnvironment)()
|
|
953
|
+
env: (0, import_git_process_environment.workerGitProcessEnvironment)(),
|
|
954
|
+
...bounded ? { detached: true } : {}
|
|
951
955
|
});
|
|
952
|
-
const
|
|
956
|
+
const completion = Promise.all([
|
|
953
957
|
new Response(subprocess.stdout).text(),
|
|
954
958
|
new Response(subprocess.stderr).text(),
|
|
955
959
|
subprocess.exited
|
|
956
|
-
]);
|
|
957
|
-
|
|
958
|
-
|
|
960
|
+
]).then(([stdout, stderr, exitCode]) => ({ stdout, stderr, exitCode }));
|
|
961
|
+
let timer;
|
|
962
|
+
const timedOut = bounded ? new Promise((_resolve, reject) => {
|
|
963
|
+
timer = setTimeout(() => reject(new GitTransportTimeoutError(`git ${args.join(" ")} timed out after ${options.timeoutMs}ms`)), options.timeoutMs);
|
|
964
|
+
}) : null;
|
|
965
|
+
try {
|
|
966
|
+
const { stdout, stderr, exitCode } = timedOut ? await Promise.race([completion, timedOut]) : await completion;
|
|
967
|
+
if (exitCode !== 0) {
|
|
968
|
+
throw new Error(`git ${args.join(" ")} failed: ${stderr.trim() || stdout.trim() || `exit ${exitCode}`}`);
|
|
969
|
+
}
|
|
970
|
+
return stdout.trim();
|
|
971
|
+
} catch (error) {
|
|
972
|
+
if (error instanceof GitTransportTimeoutError) {
|
|
973
|
+
void (0, import_process_tree.terminateProcessTree)(subprocess, { graceMs: 0 }).catch(() => void 0);
|
|
974
|
+
void completion.catch(() => void 0);
|
|
975
|
+
}
|
|
976
|
+
throw error;
|
|
977
|
+
} finally {
|
|
978
|
+
if (timer) clearTimeout(timer);
|
|
959
979
|
}
|
|
960
|
-
return stdout.trim();
|
|
961
980
|
}
|
|
962
981
|
async function tryGitAsync(args, options = {}) {
|
|
963
982
|
try {
|
|
@@ -1818,14 +1837,13 @@ const workerGitSecurityTestHarness = {
|
|
|
1818
1837
|
executeEditFileOperation,
|
|
1819
1838
|
terminateCredentialBearingChildren,
|
|
1820
1839
|
terminateCredentialBearingChildrenWithRetention,
|
|
1821
|
-
async commitCredentialGeneration(prepared, credential,
|
|
1822
|
-
return await commitCredentialGeneration(
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
);
|
|
1840
|
+
async commitCredentialGeneration(prepared, credential, options) {
|
|
1841
|
+
return await commitCredentialGeneration(prepared, credential, {
|
|
1842
|
+
secretMaterialChanged: options.secretMaterialChanged,
|
|
1843
|
+
terminatePreviousGeneration: async () => await terminateCredentialBearingChildren(options.children),
|
|
1844
|
+
beforeMutation: options.beforeMutation,
|
|
1845
|
+
previousGenerationFenced: options.previousGenerationFenced ?? false
|
|
1846
|
+
});
|
|
1829
1847
|
},
|
|
1830
1848
|
credentialGenerationReceiptPath,
|
|
1831
1849
|
credentialAuthorityLockPath,
|
|
@@ -1863,14 +1881,14 @@ function installGitHubCredentialGeneration(credential) {
|
|
|
1863
1881
|
`);
|
|
1864
1882
|
}
|
|
1865
1883
|
}
|
|
1866
|
-
async function commitCredentialGeneration(prepared, credential,
|
|
1884
|
+
async function commitCredentialGeneration(prepared, credential, options) {
|
|
1867
1885
|
const changed = prepared.changed;
|
|
1868
1886
|
try {
|
|
1869
|
-
if (changed && !previousGenerationFenced) {
|
|
1887
|
+
if (changed && options.secretMaterialChanged && !options.previousGenerationFenced) {
|
|
1870
1888
|
githubCredential = null;
|
|
1871
|
-
await terminatePreviousGeneration();
|
|
1889
|
+
await options.terminatePreviousGeneration();
|
|
1872
1890
|
}
|
|
1873
|
-
prepared.commit(beforeMutation);
|
|
1891
|
+
prepared.commit(options.beforeMutation);
|
|
1874
1892
|
} catch (error) {
|
|
1875
1893
|
prepared.discard();
|
|
1876
1894
|
throw error instanceof import_registry_auth.RegistryAuthConfigurationError ? error : new import_registry_auth.RegistryAuthConfigurationError(error);
|
|
@@ -4319,33 +4337,56 @@ async function startWorker(options, projectRuntime = {
|
|
|
4319
4337
|
return reconciledProjectIds;
|
|
4320
4338
|
};
|
|
4321
4339
|
const ORIGIN_DIVERGENCE_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
4340
|
+
const ORIGIN_DIVERGENCE_FETCH_TIMEOUT_MS = 3e4;
|
|
4341
|
+
const ORIGIN_DIVERGENCE_REFRESH_BUDGET_MS = 9e4;
|
|
4322
4342
|
const lastOriginDivergenceFetchMs = /* @__PURE__ */ new Map();
|
|
4323
|
-
const collectAheadOfOriginBranches = async (refetchOriginProjectIds) => {
|
|
4343
|
+
const collectAheadOfOriginBranches = async (refetchOriginProjectIds, onProgress) => {
|
|
4324
4344
|
const aheadBranches = [];
|
|
4325
4345
|
for (const projectId of [...lastOriginDivergenceFetchMs.keys()]) {
|
|
4326
4346
|
if (!projectConfigById.has(projectId)) lastOriginDivergenceFetchMs.delete(projectId);
|
|
4327
4347
|
}
|
|
4328
|
-
|
|
4348
|
+
const passStartedAtMs = Date.now();
|
|
4349
|
+
let budgetSpentReported = false;
|
|
4350
|
+
const projects = [...projectConfigById.values()];
|
|
4351
|
+
for (const [projectIndex, project] of projects.entries()) {
|
|
4329
4352
|
if (project.executionDisabled || !readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
|
|
4353
|
+
onProgress?.({
|
|
4354
|
+
detail: `Checking origin divergence for ${project.projectPath}`,
|
|
4355
|
+
completedItems: projectIndex,
|
|
4356
|
+
totalItems: projects.length
|
|
4357
|
+
});
|
|
4330
4358
|
const connection = projectConnection(project);
|
|
4331
4359
|
const primaryPath = configuredProjectBranchPath(projectsRoot, project, primaryProjectBranch(project));
|
|
4332
4360
|
const lastFetchedAtMs = lastOriginDivergenceFetchMs.get(project.projectId);
|
|
4333
4361
|
if (refetchOriginProjectIds.has(project.projectId) || lastFetchedAtMs === void 0 || Date.now() - lastFetchedAtMs >= ORIGIN_DIVERGENCE_REFRESH_INTERVAL_MS) {
|
|
4334
|
-
if (
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
],
|
|
4343
|
-
{ cwd: primaryPath }
|
|
4344
|
-
)) {
|
|
4345
|
-
lastOriginDivergenceFetchMs.set(project.projectId, Date.now());
|
|
4362
|
+
if (Date.now() - passStartedAtMs >= ORIGIN_DIVERGENCE_REFRESH_BUDGET_MS) {
|
|
4363
|
+
if (!budgetSpentReported) {
|
|
4364
|
+
budgetSpentReported = true;
|
|
4365
|
+
process.stderr.write(
|
|
4366
|
+
`[r5d-worker] origin divergence refresh budget of ${ORIGIN_DIVERGENCE_REFRESH_BUDGET_MS}ms is spent; remaining origins keep their previous comparison until the next refresh
|
|
4367
|
+
`
|
|
4368
|
+
);
|
|
4369
|
+
}
|
|
4346
4370
|
} else {
|
|
4347
|
-
|
|
4348
|
-
|
|
4371
|
+
try {
|
|
4372
|
+
await runGitAsync(
|
|
4373
|
+
[
|
|
4374
|
+
...(0, import_git_process_environment.gitTransportSecurityArgs)(connection.originUrl, connection.credentialHelper, connection.originCredentialUsername),
|
|
4375
|
+
"fetch",
|
|
4376
|
+
"--no-recurse-submodules",
|
|
4377
|
+
"--prune",
|
|
4378
|
+
"origin",
|
|
4379
|
+
"+refs/heads/*:refs/remotes/origin/*"
|
|
4380
|
+
],
|
|
4381
|
+
{ cwd: primaryPath, timeoutMs: ORIGIN_DIVERGENCE_FETCH_TIMEOUT_MS }
|
|
4382
|
+
);
|
|
4383
|
+
lastOriginDivergenceFetchMs.set(project.projectId, Date.now());
|
|
4384
|
+
} catch (error) {
|
|
4385
|
+
process.stderr.write(
|
|
4386
|
+
`[r5d-worker] could not refresh origin divergence for ${project.projectPath}: ${error instanceof Error ? error.message : String(error)}
|
|
4387
|
+
`
|
|
4388
|
+
);
|
|
4389
|
+
}
|
|
4349
4390
|
}
|
|
4350
4391
|
}
|
|
4351
4392
|
for (const branch of project.branches) {
|
|
@@ -5378,13 +5419,11 @@ async function startWorker(options, projectRuntime = {
|
|
|
5378
5419
|
} catch (error) {
|
|
5379
5420
|
throw error instanceof import_registry_auth.RegistryAuthConfigurationError ? error : new import_registry_auth.RegistryAuthConfigurationError(error);
|
|
5380
5421
|
}
|
|
5381
|
-
await commitCredentialGeneration(
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
credentialPublicationPreauthorized
|
|
5387
|
-
);
|
|
5422
|
+
await commitCredentialGeneration(preparedAuthGeneration, message.githubCredential, {
|
|
5423
|
+
secretMaterialChanged: credentialTransitionPhase !== "current",
|
|
5424
|
+
terminatePreviousGeneration: terminateActiveCredentialBearingChildren,
|
|
5425
|
+
previousGenerationFenced: credentialPublicationPreauthorized
|
|
5426
|
+
});
|
|
5388
5427
|
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
5389
5428
|
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
5390
5429
|
}
|
|
@@ -5519,7 +5558,15 @@ async function startWorker(options, projectRuntime = {
|
|
|
5519
5558
|
}
|
|
5520
5559
|
};
|
|
5521
5560
|
const hasDurableBranchDeletion = projectWorkspaceState.tombstones.some((tombstone) => tombstone.kind === "branch");
|
|
5522
|
-
|
|
5561
|
+
sendLifecycleProgress({
|
|
5562
|
+
phase: "configuring",
|
|
5563
|
+
operationId: configurationOperationId,
|
|
5564
|
+
detail: `Synchronizing the workspace after configuring ${message.projects.length} project(s)`,
|
|
5565
|
+
completedItems: message.projects.length,
|
|
5566
|
+
totalItems: message.projects.length
|
|
5567
|
+
});
|
|
5568
|
+
const connectSyncStartedAtMs = Date.now();
|
|
5569
|
+
const syncResult = await performWorkspaceSync({
|
|
5523
5570
|
attemptId: crypto.randomUUID(),
|
|
5524
5571
|
trigger: { type: "connect" },
|
|
5525
5572
|
confirmedLargeDiff: hasDurableBranchDeletion,
|
|
@@ -5527,6 +5574,11 @@ async function startWorker(options, projectRuntime = {
|
|
|
5527
5574
|
resetToCanonical: workspaceConfigurationResetToCanonicalIsAllowed(message.resetToCanonical, incidentDeferral.incidentId),
|
|
5528
5575
|
assertStillAdmitted: assertConfigurationSyncStillAdmitted
|
|
5529
5576
|
});
|
|
5577
|
+
const connectSyncMs = Date.now() - connectSyncStartedAtMs;
|
|
5578
|
+
const result = syncResult.telemetry ? syncResult : {
|
|
5579
|
+
...syncResult,
|
|
5580
|
+
telemetry: { totalMs: connectSyncMs, queueMs: 0, prepareMs: lastWorkspaceSyncMirrorObservationMs, synchronizeMs: connectSyncMs }
|
|
5581
|
+
};
|
|
5530
5582
|
assertConfigurationSyncStillAdmitted();
|
|
5531
5583
|
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
5532
5584
|
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
@@ -5559,7 +5611,14 @@ async function startWorker(options, projectRuntime = {
|
|
|
5559
5611
|
const pending = [...pendingCheckouts.values()].sort(
|
|
5560
5612
|
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
5561
5613
|
);
|
|
5562
|
-
return {
|
|
5614
|
+
return {
|
|
5615
|
+
result,
|
|
5616
|
+
pending,
|
|
5617
|
+
aheadOfOriginBranches: await collectAheadOfOriginBranches(
|
|
5618
|
+
reconciledProjectIds,
|
|
5619
|
+
(progress) => sendLifecycleProgress({ phase: "configuring", operationId: configurationOperationId, ...progress })
|
|
5620
|
+
)
|
|
5621
|
+
};
|
|
5563
5622
|
});
|
|
5564
5623
|
} catch (error) {
|
|
5565
5624
|
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
@@ -5913,7 +5972,15 @@ async function startWorker(options, projectRuntime = {
|
|
|
5913
5972
|
}
|
|
5914
5973
|
if (message.type === "workspace_config") {
|
|
5915
5974
|
const receiptGeneration = ++workspaceConfigurationReceiptGeneration;
|
|
5916
|
-
const refreshesDeferredConfiguration =
|
|
5975
|
+
const refreshesDeferredConfiguration = (0, import_workspace_incident_state.workspaceConfigurationRefreshesDeferredConfiguration)({
|
|
5976
|
+
deferredIncidentId: deferredWorkspaceConfiguration?.incidentId ?? null,
|
|
5977
|
+
activeIncidentId: activeWorkspaceIncidentId,
|
|
5978
|
+
requestedDeferredIncidentId: message.deferWorkspaceSyncForIncidentId
|
|
5979
|
+
});
|
|
5980
|
+
if (refreshesDeferredConfiguration && deferredWorkspaceConfigurationRefreshTimer) {
|
|
5981
|
+
clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
5982
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
5983
|
+
}
|
|
5917
5984
|
const digest = (0, import_recovery_store.workerOperationDigest)(message);
|
|
5918
5985
|
const cached = projectRuntime.configuration;
|
|
5919
5986
|
const canReuseConfiguration = cached?.digest === digest && cached.workspaceSync?.remoteUrl === message.workspaceRemoteUrl && Boolean(cached.workspaceSync.credentialHelper) && cached.workspaceSync.credentialUsername === workerCredentialUsername && cached.workspaceSync.gitIdentity?.name === message.gitIdentity.name && cached.workspaceSync.gitIdentity?.email === message.gitIdentity.email && !unsafeWorkspaceSyncBouncePending && credentialGenerationFingerprint({ ...message, workerBaseUrl: baseUrl, workerAuthHeader: bearerAuthHeader }) === configuredCredentialGenerationFingerprint;
|
package/dist/cjs/package.json
CHANGED
|
@@ -2367,7 +2367,9 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2367
2367
|
}
|
|
2368
2368
|
restoreDeferredWorkspaceMountsFromHead(
|
|
2369
2369
|
workspacePath,
|
|
2370
|
-
input.mounts.filter(
|
|
2370
|
+
input.mounts.filter(
|
|
2371
|
+
(mount) => deferredMountIds.has(mount.id) || mount.preserveLocalOnHydrationBasisChange === true && cycleSkippedMountIds.has(mount.id)
|
|
2372
|
+
)
|
|
2371
2373
|
);
|
|
2372
2374
|
await stageAndCommitWorkspace();
|
|
2373
2375
|
await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
|
|
@@ -18,7 +18,8 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var workspace_incident_state_exports = {};
|
|
20
20
|
__export(workspace_incident_state_exports, {
|
|
21
|
-
applyWorkspaceIncidentUpdate: () => applyWorkspaceIncidentUpdate
|
|
21
|
+
applyWorkspaceIncidentUpdate: () => applyWorkspaceIncidentUpdate,
|
|
22
|
+
workspaceConfigurationRefreshesDeferredConfiguration: () => workspaceConfigurationRefreshesDeferredConfiguration
|
|
22
23
|
});
|
|
23
24
|
module.exports = __toCommonJS(workspace_incident_state_exports);
|
|
24
25
|
function applyWorkspaceIncidentUpdate(currentIncidentId, update) {
|
|
@@ -26,7 +27,11 @@ function applyWorkspaceIncidentUpdate(currentIncidentId, update) {
|
|
|
26
27
|
if (currentIncidentId && update.incidentId && update.incidentId !== currentIncidentId) return currentIncidentId;
|
|
27
28
|
return null;
|
|
28
29
|
}
|
|
30
|
+
function workspaceConfigurationRefreshesDeferredConfiguration(input) {
|
|
31
|
+
return input.deferredIncidentId !== null && input.activeIncidentId === null && input.requestedDeferredIncidentId === void 0;
|
|
32
|
+
}
|
|
29
33
|
// Annotate the CommonJS export names for ESM import in node:
|
|
30
34
|
0 && (module.exports = {
|
|
31
|
-
applyWorkspaceIncidentUpdate
|
|
35
|
+
applyWorkspaceIncidentUpdate,
|
|
36
|
+
workspaceConfigurationRefreshesDeferredConfiguration
|
|
32
37
|
});
|
package/dist/mjs/main.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
registryAuthHasAppManagedGitHubCredential,
|
|
35
35
|
RegistryAuthConfigurationError
|
|
36
36
|
} from "./registry-auth.mjs";
|
|
37
|
-
import { applyWorkspaceIncidentUpdate } from "./workspace-incident-state.mjs";
|
|
37
|
+
import { applyWorkspaceIncidentUpdate, workspaceConfigurationRefreshesDeferredConfiguration } from "./workspace-incident-state.mjs";
|
|
38
38
|
import {
|
|
39
39
|
hasActiveVisibleProjectsWorkspaceTarget,
|
|
40
40
|
PENDING_CREATED_BRANCH_AUTOMATIC_SYNC_GRACE_MS,
|
|
@@ -959,24 +959,43 @@ function tryGit(args, options = {}) {
|
|
|
959
959
|
return false;
|
|
960
960
|
}
|
|
961
961
|
}
|
|
962
|
+
class GitTransportTimeoutError extends Error {
|
|
963
|
+
}
|
|
962
964
|
async function runGitAsync(args, options = {}) {
|
|
963
965
|
const command = workerGitCommand.commandArgs(args);
|
|
966
|
+
const bounded = options.timeoutMs !== void 0;
|
|
964
967
|
const subprocess = Bun.spawn(command, {
|
|
965
968
|
cwd: options.cwd,
|
|
966
969
|
stdin: "ignore",
|
|
967
970
|
stdout: "pipe",
|
|
968
971
|
stderr: "pipe",
|
|
969
|
-
env: workerGitProcessEnvironment()
|
|
972
|
+
env: workerGitProcessEnvironment(),
|
|
973
|
+
...bounded ? { detached: true } : {}
|
|
970
974
|
});
|
|
971
|
-
const
|
|
975
|
+
const completion = Promise.all([
|
|
972
976
|
new Response(subprocess.stdout).text(),
|
|
973
977
|
new Response(subprocess.stderr).text(),
|
|
974
978
|
subprocess.exited
|
|
975
|
-
]);
|
|
976
|
-
|
|
977
|
-
|
|
979
|
+
]).then(([stdout, stderr, exitCode]) => ({ stdout, stderr, exitCode }));
|
|
980
|
+
let timer;
|
|
981
|
+
const timedOut = bounded ? new Promise((_resolve, reject) => {
|
|
982
|
+
timer = setTimeout(() => reject(new GitTransportTimeoutError(`git ${args.join(" ")} timed out after ${options.timeoutMs}ms`)), options.timeoutMs);
|
|
983
|
+
}) : null;
|
|
984
|
+
try {
|
|
985
|
+
const { stdout, stderr, exitCode } = timedOut ? await Promise.race([completion, timedOut]) : await completion;
|
|
986
|
+
if (exitCode !== 0) {
|
|
987
|
+
throw new Error(`git ${args.join(" ")} failed: ${stderr.trim() || stdout.trim() || `exit ${exitCode}`}`);
|
|
988
|
+
}
|
|
989
|
+
return stdout.trim();
|
|
990
|
+
} catch (error) {
|
|
991
|
+
if (error instanceof GitTransportTimeoutError) {
|
|
992
|
+
void terminateProcessTree(subprocess, { graceMs: 0 }).catch(() => void 0);
|
|
993
|
+
void completion.catch(() => void 0);
|
|
994
|
+
}
|
|
995
|
+
throw error;
|
|
996
|
+
} finally {
|
|
997
|
+
if (timer) clearTimeout(timer);
|
|
978
998
|
}
|
|
979
|
-
return stdout.trim();
|
|
980
999
|
}
|
|
981
1000
|
async function tryGitAsync(args, options = {}) {
|
|
982
1001
|
try {
|
|
@@ -1837,14 +1856,13 @@ const workerGitSecurityTestHarness = {
|
|
|
1837
1856
|
executeEditFileOperation,
|
|
1838
1857
|
terminateCredentialBearingChildren,
|
|
1839
1858
|
terminateCredentialBearingChildrenWithRetention,
|
|
1840
|
-
async commitCredentialGeneration(prepared, credential,
|
|
1841
|
-
return await commitCredentialGeneration(
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
);
|
|
1859
|
+
async commitCredentialGeneration(prepared, credential, options) {
|
|
1860
|
+
return await commitCredentialGeneration(prepared, credential, {
|
|
1861
|
+
secretMaterialChanged: options.secretMaterialChanged,
|
|
1862
|
+
terminatePreviousGeneration: async () => await terminateCredentialBearingChildren(options.children),
|
|
1863
|
+
beforeMutation: options.beforeMutation,
|
|
1864
|
+
previousGenerationFenced: options.previousGenerationFenced ?? false
|
|
1865
|
+
});
|
|
1848
1866
|
},
|
|
1849
1867
|
credentialGenerationReceiptPath,
|
|
1850
1868
|
credentialAuthorityLockPath,
|
|
@@ -1882,14 +1900,14 @@ function installGitHubCredentialGeneration(credential) {
|
|
|
1882
1900
|
`);
|
|
1883
1901
|
}
|
|
1884
1902
|
}
|
|
1885
|
-
async function commitCredentialGeneration(prepared, credential,
|
|
1903
|
+
async function commitCredentialGeneration(prepared, credential, options) {
|
|
1886
1904
|
const changed = prepared.changed;
|
|
1887
1905
|
try {
|
|
1888
|
-
if (changed && !previousGenerationFenced) {
|
|
1906
|
+
if (changed && options.secretMaterialChanged && !options.previousGenerationFenced) {
|
|
1889
1907
|
githubCredential = null;
|
|
1890
|
-
await terminatePreviousGeneration();
|
|
1908
|
+
await options.terminatePreviousGeneration();
|
|
1891
1909
|
}
|
|
1892
|
-
prepared.commit(beforeMutation);
|
|
1910
|
+
prepared.commit(options.beforeMutation);
|
|
1893
1911
|
} catch (error) {
|
|
1894
1912
|
prepared.discard();
|
|
1895
1913
|
throw error instanceof RegistryAuthConfigurationError ? error : new RegistryAuthConfigurationError(error);
|
|
@@ -4338,33 +4356,56 @@ async function startWorker(options, projectRuntime = {
|
|
|
4338
4356
|
return reconciledProjectIds;
|
|
4339
4357
|
};
|
|
4340
4358
|
const ORIGIN_DIVERGENCE_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
|
|
4359
|
+
const ORIGIN_DIVERGENCE_FETCH_TIMEOUT_MS = 3e4;
|
|
4360
|
+
const ORIGIN_DIVERGENCE_REFRESH_BUDGET_MS = 9e4;
|
|
4341
4361
|
const lastOriginDivergenceFetchMs = /* @__PURE__ */ new Map();
|
|
4342
|
-
const collectAheadOfOriginBranches = async (refetchOriginProjectIds) => {
|
|
4362
|
+
const collectAheadOfOriginBranches = async (refetchOriginProjectIds, onProgress) => {
|
|
4343
4363
|
const aheadBranches = [];
|
|
4344
4364
|
for (const projectId of [...lastOriginDivergenceFetchMs.keys()]) {
|
|
4345
4365
|
if (!projectConfigById.has(projectId)) lastOriginDivergenceFetchMs.delete(projectId);
|
|
4346
4366
|
}
|
|
4347
|
-
|
|
4367
|
+
const passStartedAtMs = Date.now();
|
|
4368
|
+
let budgetSpentReported = false;
|
|
4369
|
+
const projects = [...projectConfigById.values()];
|
|
4370
|
+
for (const [projectIndex, project] of projects.entries()) {
|
|
4348
4371
|
if (project.executionDisabled || !readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
|
|
4372
|
+
onProgress?.({
|
|
4373
|
+
detail: `Checking origin divergence for ${project.projectPath}`,
|
|
4374
|
+
completedItems: projectIndex,
|
|
4375
|
+
totalItems: projects.length
|
|
4376
|
+
});
|
|
4349
4377
|
const connection = projectConnection(project);
|
|
4350
4378
|
const primaryPath = configuredProjectBranchPath(projectsRoot, project, primaryProjectBranch(project));
|
|
4351
4379
|
const lastFetchedAtMs = lastOriginDivergenceFetchMs.get(project.projectId);
|
|
4352
4380
|
if (refetchOriginProjectIds.has(project.projectId) || lastFetchedAtMs === void 0 || Date.now() - lastFetchedAtMs >= ORIGIN_DIVERGENCE_REFRESH_INTERVAL_MS) {
|
|
4353
|
-
if (
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
],
|
|
4362
|
-
{ cwd: primaryPath }
|
|
4363
|
-
)) {
|
|
4364
|
-
lastOriginDivergenceFetchMs.set(project.projectId, Date.now());
|
|
4381
|
+
if (Date.now() - passStartedAtMs >= ORIGIN_DIVERGENCE_REFRESH_BUDGET_MS) {
|
|
4382
|
+
if (!budgetSpentReported) {
|
|
4383
|
+
budgetSpentReported = true;
|
|
4384
|
+
process.stderr.write(
|
|
4385
|
+
`[r5d-worker] origin divergence refresh budget of ${ORIGIN_DIVERGENCE_REFRESH_BUDGET_MS}ms is spent; remaining origins keep their previous comparison until the next refresh
|
|
4386
|
+
`
|
|
4387
|
+
);
|
|
4388
|
+
}
|
|
4365
4389
|
} else {
|
|
4366
|
-
|
|
4367
|
-
|
|
4390
|
+
try {
|
|
4391
|
+
await runGitAsync(
|
|
4392
|
+
[
|
|
4393
|
+
...gitTransportSecurityArgs(connection.originUrl, connection.credentialHelper, connection.originCredentialUsername),
|
|
4394
|
+
"fetch",
|
|
4395
|
+
"--no-recurse-submodules",
|
|
4396
|
+
"--prune",
|
|
4397
|
+
"origin",
|
|
4398
|
+
"+refs/heads/*:refs/remotes/origin/*"
|
|
4399
|
+
],
|
|
4400
|
+
{ cwd: primaryPath, timeoutMs: ORIGIN_DIVERGENCE_FETCH_TIMEOUT_MS }
|
|
4401
|
+
);
|
|
4402
|
+
lastOriginDivergenceFetchMs.set(project.projectId, Date.now());
|
|
4403
|
+
} catch (error) {
|
|
4404
|
+
process.stderr.write(
|
|
4405
|
+
`[r5d-worker] could not refresh origin divergence for ${project.projectPath}: ${error instanceof Error ? error.message : String(error)}
|
|
4406
|
+
`
|
|
4407
|
+
);
|
|
4408
|
+
}
|
|
4368
4409
|
}
|
|
4369
4410
|
}
|
|
4370
4411
|
for (const branch of project.branches) {
|
|
@@ -5397,13 +5438,11 @@ async function startWorker(options, projectRuntime = {
|
|
|
5397
5438
|
} catch (error) {
|
|
5398
5439
|
throw error instanceof RegistryAuthConfigurationError ? error : new RegistryAuthConfigurationError(error);
|
|
5399
5440
|
}
|
|
5400
|
-
await commitCredentialGeneration(
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
credentialPublicationPreauthorized
|
|
5406
|
-
);
|
|
5441
|
+
await commitCredentialGeneration(preparedAuthGeneration, message.githubCredential, {
|
|
5442
|
+
secretMaterialChanged: credentialTransitionPhase !== "current",
|
|
5443
|
+
terminatePreviousGeneration: terminateActiveCredentialBearingChildren,
|
|
5444
|
+
previousGenerationFenced: credentialPublicationPreauthorized
|
|
5445
|
+
});
|
|
5407
5446
|
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
5408
5447
|
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
5409
5448
|
}
|
|
@@ -5538,7 +5577,15 @@ async function startWorker(options, projectRuntime = {
|
|
|
5538
5577
|
}
|
|
5539
5578
|
};
|
|
5540
5579
|
const hasDurableBranchDeletion = projectWorkspaceState.tombstones.some((tombstone) => tombstone.kind === "branch");
|
|
5541
|
-
|
|
5580
|
+
sendLifecycleProgress({
|
|
5581
|
+
phase: "configuring",
|
|
5582
|
+
operationId: configurationOperationId,
|
|
5583
|
+
detail: `Synchronizing the workspace after configuring ${message.projects.length} project(s)`,
|
|
5584
|
+
completedItems: message.projects.length,
|
|
5585
|
+
totalItems: message.projects.length
|
|
5586
|
+
});
|
|
5587
|
+
const connectSyncStartedAtMs = Date.now();
|
|
5588
|
+
const syncResult = await performWorkspaceSync({
|
|
5542
5589
|
attemptId: crypto.randomUUID(),
|
|
5543
5590
|
trigger: { type: "connect" },
|
|
5544
5591
|
confirmedLargeDiff: hasDurableBranchDeletion,
|
|
@@ -5546,6 +5593,11 @@ async function startWorker(options, projectRuntime = {
|
|
|
5546
5593
|
resetToCanonical: workspaceConfigurationResetToCanonicalIsAllowed(message.resetToCanonical, incidentDeferral.incidentId),
|
|
5547
5594
|
assertStillAdmitted: assertConfigurationSyncStillAdmitted
|
|
5548
5595
|
});
|
|
5596
|
+
const connectSyncMs = Date.now() - connectSyncStartedAtMs;
|
|
5597
|
+
const result = syncResult.telemetry ? syncResult : {
|
|
5598
|
+
...syncResult,
|
|
5599
|
+
telemetry: { totalMs: connectSyncMs, queueMs: 0, prepareMs: lastWorkspaceSyncMirrorObservationMs, synchronizeMs: connectSyncMs }
|
|
5600
|
+
};
|
|
5549
5601
|
assertConfigurationSyncStillAdmitted();
|
|
5550
5602
|
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
5551
5603
|
throw new Error("Workspace configuration was superseded by a newer server generation");
|
|
@@ -5578,7 +5630,14 @@ async function startWorker(options, projectRuntime = {
|
|
|
5578
5630
|
const pending = [...pendingCheckouts.values()].sort(
|
|
5579
5631
|
(left, right) => left.projectId.localeCompare(right.projectId) || left.branchName.localeCompare(right.branchName)
|
|
5580
5632
|
);
|
|
5581
|
-
return {
|
|
5633
|
+
return {
|
|
5634
|
+
result,
|
|
5635
|
+
pending,
|
|
5636
|
+
aheadOfOriginBranches: await collectAheadOfOriginBranches(
|
|
5637
|
+
reconciledProjectIds,
|
|
5638
|
+
(progress) => sendLifecycleProgress({ phase: "configuring", operationId: configurationOperationId, ...progress })
|
|
5639
|
+
)
|
|
5640
|
+
};
|
|
5582
5641
|
});
|
|
5583
5642
|
} catch (error) {
|
|
5584
5643
|
if (receiptGeneration !== workspaceConfigurationReceiptGeneration) {
|
|
@@ -5932,7 +5991,15 @@ async function startWorker(options, projectRuntime = {
|
|
|
5932
5991
|
}
|
|
5933
5992
|
if (message.type === "workspace_config") {
|
|
5934
5993
|
const receiptGeneration = ++workspaceConfigurationReceiptGeneration;
|
|
5935
|
-
const refreshesDeferredConfiguration =
|
|
5994
|
+
const refreshesDeferredConfiguration = workspaceConfigurationRefreshesDeferredConfiguration({
|
|
5995
|
+
deferredIncidentId: deferredWorkspaceConfiguration?.incidentId ?? null,
|
|
5996
|
+
activeIncidentId: activeWorkspaceIncidentId,
|
|
5997
|
+
requestedDeferredIncidentId: message.deferWorkspaceSyncForIncidentId
|
|
5998
|
+
});
|
|
5999
|
+
if (refreshesDeferredConfiguration && deferredWorkspaceConfigurationRefreshTimer) {
|
|
6000
|
+
clearTimeout(deferredWorkspaceConfigurationRefreshTimer);
|
|
6001
|
+
deferredWorkspaceConfigurationRefreshTimer = void 0;
|
|
6002
|
+
}
|
|
5936
6003
|
const digest = workerOperationDigest(message);
|
|
5937
6004
|
const cached = projectRuntime.configuration;
|
|
5938
6005
|
const canReuseConfiguration = cached?.digest === digest && cached.workspaceSync?.remoteUrl === message.workspaceRemoteUrl && Boolean(cached.workspaceSync.credentialHelper) && cached.workspaceSync.credentialUsername === workerCredentialUsername && cached.workspaceSync.gitIdentity?.name === message.gitIdentity.name && cached.workspaceSync.gitIdentity?.email === message.gitIdentity.email && !unsafeWorkspaceSyncBouncePending && credentialGenerationFingerprint({ ...message, workerBaseUrl: baseUrl, workerAuthHeader: bearerAuthHeader }) === configuredCredentialGenerationFingerprint;
|
package/dist/mjs/package.json
CHANGED
|
@@ -2330,7 +2330,9 @@ async function runWorkspaceGitSynchronization(input, remoteHeadFetches) {
|
|
|
2330
2330
|
}
|
|
2331
2331
|
restoreDeferredWorkspaceMountsFromHead(
|
|
2332
2332
|
workspacePath,
|
|
2333
|
-
input.mounts.filter(
|
|
2333
|
+
input.mounts.filter(
|
|
2334
|
+
(mount) => deferredMountIds.has(mount.id) || mount.preserveLocalOnHydrationBasisChange === true && cycleSkippedMountIds.has(mount.id)
|
|
2335
|
+
)
|
|
2334
2336
|
);
|
|
2335
2337
|
await stageAndCommitWorkspace();
|
|
2336
2338
|
await fsyncWorkspaceCheckoutTreeYielding(workspacePath);
|
|
@@ -3,6 +3,10 @@ function applyWorkspaceIncidentUpdate(currentIncidentId, update) {
|
|
|
3
3
|
if (currentIncidentId && update.incidentId && update.incidentId !== currentIncidentId) return currentIncidentId;
|
|
4
4
|
return null;
|
|
5
5
|
}
|
|
6
|
+
function workspaceConfigurationRefreshesDeferredConfiguration(input) {
|
|
7
|
+
return input.deferredIncidentId !== null && input.activeIncidentId === null && input.requestedDeferredIncidentId === void 0;
|
|
8
|
+
}
|
|
6
9
|
export {
|
|
7
|
-
applyWorkspaceIncidentUpdate
|
|
10
|
+
applyWorkspaceIncidentUpdate,
|
|
11
|
+
workspaceConfigurationRefreshesDeferredConfiguration
|
|
8
12
|
};
|
package/dist/types/main.d.ts
CHANGED
|
@@ -957,10 +957,15 @@ export declare const workerGitSecurityTestHarness: {
|
|
|
957
957
|
executeEditFileOperation: typeof executeEditFileOperation;
|
|
958
958
|
terminateCredentialBearingChildren: typeof terminateCredentialBearingChildren;
|
|
959
959
|
terminateCredentialBearingChildrenWithRetention: typeof terminateCredentialBearingChildrenWithRetention;
|
|
960
|
-
commitCredentialGeneration(prepared: PreparedPrivateAuthFileGeneration, credential: WorkerGitHubCredential | null,
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
960
|
+
commitCredentialGeneration(prepared: PreparedPrivateAuthFileGeneration, credential: WorkerGitHubCredential | null, options: {
|
|
961
|
+
children: readonly {
|
|
962
|
+
label: string;
|
|
963
|
+
terminate: () => void | Promise<void>;
|
|
964
|
+
}[];
|
|
965
|
+
secretMaterialChanged: boolean;
|
|
966
|
+
beforeMutation?: (filePath: string, index: number) => void;
|
|
967
|
+
previousGenerationFenced?: boolean;
|
|
968
|
+
}): Promise<boolean>;
|
|
964
969
|
credentialGenerationReceiptPath: typeof credentialGenerationReceiptPath;
|
|
965
970
|
credentialAuthorityLockPath: typeof credentialAuthorityLockPath;
|
|
966
971
|
acquireCredentialAuthorityLock: typeof acquireCredentialAuthorityLock;
|
|
@@ -3,3 +3,17 @@ export type WorkspaceIncidentUpdate = {
|
|
|
3
3
|
status: "remediating" | "waiting_for_worker" | "resolved" | "confirmed" | "reset" | null;
|
|
4
4
|
};
|
|
5
5
|
export declare function applyWorkspaceIncidentUpdate(currentIncidentId: string | null, update: WorkspaceIncidentUpdate): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Whether an incoming `workspace_config` is the full configuration a terminal
|
|
8
|
+
* incident update promised: the worker still holds an incident-deferred
|
|
9
|
+
* configuration, no incident is active any more, and the server did not ask
|
|
10
|
+
* for another deferral. Arrival of that configuration disarms the refresh
|
|
11
|
+
* watchdog; from then on the configuration's own generation checks and its
|
|
12
|
+
* failure close govern, so a legitimately long full configuration is never
|
|
13
|
+
* cut off mid-flight by the arrival timer.
|
|
14
|
+
*/
|
|
15
|
+
export declare function workspaceConfigurationRefreshesDeferredConfiguration(input: {
|
|
16
|
+
deferredIncidentId: string | null;
|
|
17
|
+
activeIncidentId: string | null;
|
|
18
|
+
requestedDeferredIncidentId: string | undefined;
|
|
19
|
+
}): boolean;
|