@ricsam/r5d-worker 0.0.75 → 0.0.77

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 CHANGED
@@ -48,6 +48,7 @@ __export(main_exports, {
48
48
  resolveWorkerFilePath: () => resolveWorkerFilePath,
49
49
  resolveWorkerSessionTarget: () => resolveWorkerSessionTarget,
50
50
  syncSessionArtifacts: () => syncSessionArtifacts,
51
+ workerGitSecurityTestHarness: () => workerGitSecurityTestHarness,
51
52
  writeWorkerTextFile: () => writeWorkerTextFile
52
53
  });
53
54
  module.exports = __toCommonJS(main_exports);
@@ -57,6 +58,7 @@ var import_node_crypto = require("node:crypto");
57
58
  var import_node_os = __toESM(require("node:os"), 1);
58
59
  var import_node_child_process = require("node:child_process");
59
60
  var import_cli_update = require("./cli-update.cjs");
61
+ var import_git_process_environment = require("./git-process-environment.cjs");
60
62
  var import_heartbeat = require("./heartbeat.cjs");
61
63
  var import_process_tree = require("./process-tree.cjs");
62
64
  var import_pty_output_coalescer = require("./pty-output-coalescer.cjs");
@@ -659,29 +661,16 @@ const NON_RECURSIVE_GIT_CONFIG_ARGS = [
659
661
  "-c",
660
662
  "push.recurseSubmodules=false"
661
663
  ];
662
- function gitExtraHeaderConfigKey(extraHeaderUrl) {
663
- return `http.${normalizeBaseUrl(extraHeaderUrl)}/.extraHeader`;
664
- }
665
- function gitAuthArgs(auth) {
666
- if (!auth) {
667
- return [];
668
- }
669
- const scopedKey = gitExtraHeaderConfigKey(auth.extraHeaderUrl);
670
- return ["-c", "http.extraHeader=", "-c", `${scopedKey}=`, "-c", `${scopedKey}=${auth.header}`];
671
- }
672
664
  const workerGitCommand = {
673
- commandArgs: (args, auth) => ["git", ...NON_RECURSIVE_GIT_CONFIG_ARGS, ...gitAuthArgs(auth), ...args]
665
+ commandArgs: (args) => ["git", ...NON_RECURSIVE_GIT_CONFIG_ARGS, ...args]
674
666
  };
675
667
  function runGit(args, options = {}) {
676
- const command = workerGitCommand.commandArgs(args, options.auth);
668
+ const command = workerGitCommand.commandArgs(args);
677
669
  const result = Bun.spawnSync(command, {
678
670
  cwd: options.cwd,
679
671
  stdout: "pipe",
680
672
  stderr: "pipe",
681
- env: {
682
- ...process.env,
683
- GIT_TERMINAL_PROMPT: "0"
684
- }
673
+ env: (0, import_git_process_environment.workerGitProcessEnvironment)()
685
674
  });
686
675
  if (result.exitCode !== 0) {
687
676
  throw new Error(
@@ -814,28 +803,92 @@ function resolveWorkerFilePath(branchPath, inputPath, builtInPaths) {
814
803
  function canonicalProjectRemoteUrl(baseUrl, projectId) {
815
804
  return new URL(`/git/${projectId}.git`, baseUrl).toString();
816
805
  }
817
- function gitExtraHeaderUrlForRemote(remoteUrl) {
818
- try {
819
- return new URL(remoteUrl).origin;
820
- } catch {
821
- return remoteUrl;
822
- }
823
- }
824
- function gitAuthForRemote(remoteUrl, authHeader) {
825
- if (!authHeader) {
826
- return void 0;
827
- }
828
- return {
829
- extraHeaderUrl: gitExtraHeaderUrlForRemote(remoteUrl),
830
- header: authHeader
831
- };
832
- }
833
806
  function shellQuote(value) {
834
807
  return `'${value.replace(/'/g, "'\\''")}'`;
835
808
  }
836
- function githubCredentialsPath() {
809
+ function readOnlyCredentialStoreHelper(storePath) {
810
+ return `!f() { if [ "$1" = get ]; then git credential-store --file=${shellQuote(storePath)} get; else return 0; fi; }; f`;
811
+ }
812
+ function credentialStoreDirectory() {
813
+ return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "git-credentials");
814
+ }
815
+ function legacySharedCredentialStorePath() {
837
816
  return import_node_path.default.join(import_node_os.default.homedir(), ".r5d", "github-credentials");
838
817
  }
818
+ function credentialStorePathForProject(projectId) {
819
+ validateProjectId(projectId);
820
+ const digest = (0, import_node_crypto.createHash)("sha256").update(`project\0${projectId}`).digest("hex");
821
+ return import_node_path.default.join(credentialStoreDirectory(), `project-${digest}.store`);
822
+ }
823
+ function credentialStorePathForWorkspace(remoteUrl) {
824
+ const digest = (0, import_node_crypto.createHash)("sha256").update(`workspace\0${remoteUrl}`).digest("hex");
825
+ return import_node_path.default.join(credentialStoreDirectory(), `workspace-${digest}.store`);
826
+ }
827
+ function lstatIfExists(filePath) {
828
+ try {
829
+ return import_node_fs.default.lstatSync(filePath);
830
+ } catch (error) {
831
+ if (error.code === "ENOENT") return null;
832
+ throw error;
833
+ }
834
+ }
835
+ function ensurePrivateCredentialStoreDirectory(directoryPath) {
836
+ const stat = lstatIfExists(directoryPath);
837
+ if (stat) {
838
+ if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error("Git credential-store path must be a private directory");
839
+ } else {
840
+ import_node_fs.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
841
+ }
842
+ import_node_fs.default.chmodSync(directoryPath, 448);
843
+ }
844
+ function removeLegacySharedCredentialStore(storePath = legacySharedCredentialStorePath()) {
845
+ import_node_fs.default.rmSync(storePath, { force: true });
846
+ }
847
+ let credentialStoreDirectoryInitialized = false;
848
+ function resetCredentialStoreDirectory(directoryPath = credentialStoreDirectory()) {
849
+ const stat = lstatIfExists(directoryPath);
850
+ if (stat) {
851
+ if (stat.isSymbolicLink() || !stat.isDirectory()) import_node_fs.default.unlinkSync(directoryPath);
852
+ else import_node_fs.default.rmSync(directoryPath, { recursive: true, force: true });
853
+ }
854
+ ensurePrivateCredentialStoreDirectory(directoryPath);
855
+ }
856
+ function initializeCredentialStoreDirectoryOnce() {
857
+ if (credentialStoreDirectoryInitialized) return;
858
+ removeLegacySharedCredentialStore();
859
+ resetCredentialStoreDirectory();
860
+ credentialStoreDirectoryInitialized = true;
861
+ }
862
+ const APP_CREDENTIAL_STORE_FILE = /^(?:project|workspace)-[0-9a-f]{64}\.store$/;
863
+ function pruneCredentialStoreFiles(requiredStorePaths, directoryPath = credentialStoreDirectory()) {
864
+ ensurePrivateCredentialStoreDirectory(directoryPath);
865
+ const resolvedDirectory = import_node_path.default.resolve(directoryPath);
866
+ const requiredNames = /* @__PURE__ */ new Set();
867
+ for (const storePath of requiredStorePaths) {
868
+ const resolvedStorePath = import_node_path.default.resolve(storePath);
869
+ if (import_node_path.default.dirname(resolvedStorePath) !== resolvedDirectory || !APP_CREDENTIAL_STORE_FILE.test(import_node_path.default.basename(resolvedStorePath))) {
870
+ throw new Error("Invalid app-owned Git credential-store path");
871
+ }
872
+ requiredNames.add(import_node_path.default.basename(resolvedStorePath));
873
+ }
874
+ const removed = [];
875
+ for (const name of import_node_fs.default.readdirSync(directoryPath)) {
876
+ if (!APP_CREDENTIAL_STORE_FILE.test(name) || requiredNames.has(name)) continue;
877
+ const storePath = import_node_path.default.join(directoryPath, name);
878
+ const stat = import_node_fs.default.lstatSync(storePath);
879
+ if (!stat.isFile() && !stat.isSymbolicLink()) continue;
880
+ import_node_fs.default.unlinkSync(storePath);
881
+ removed.push(storePath);
882
+ }
883
+ return removed;
884
+ }
885
+ function removeProjectCredentialStore(projectId) {
886
+ const storePath = credentialStorePathForProject(projectId);
887
+ const stat = lstatIfExists(storePath);
888
+ if (!stat) return;
889
+ if (!stat.isFile() && !stat.isSymbolicLink()) throw new Error("Git credential store must be a regular file");
890
+ import_node_fs.default.unlinkSync(storePath);
891
+ }
839
892
  function decodeGitAuthHeader(authHeader) {
840
893
  const match = /^Authorization:\s*Basic\s+(.+)$/i.exec(authHeader.trim());
841
894
  if (match) {
@@ -847,45 +900,84 @@ function decodeGitAuthHeader(authHeader) {
847
900
  const bearer = /^Authorization:\s*Bearer\s+(.+)$/i.exec(authHeader.trim());
848
901
  return bearer?.[1] ? { username: "r5d-worker", password: bearer[1] } : null;
849
902
  }
850
- function writeCredentialStoreEntry(remoteUrl, authHeader) {
903
+ function credentialUsernameForAuthHeader(authHeader) {
851
904
  const credentials = decodeGitAuthHeader(authHeader);
852
- if (!credentials) {
853
- return null;
854
- }
855
- let remote;
905
+ if (!credentials) throw new Error("Unsupported Git authorization header");
906
+ if (/[\0\r\n]/.test(credentials.username)) throw new Error("Invalid Git credential username");
907
+ return credentials.username;
908
+ }
909
+ function replaceCredentialStoreFile(storePath, content) {
910
+ const temporaryPath = import_node_path.default.join(import_node_path.default.dirname(storePath), `.${import_node_path.default.basename(storePath)}.${process.pid}.${crypto.randomUUID()}.tmp`);
856
911
  try {
857
- remote = new URL(remoteUrl);
858
- } catch {
859
- return null;
860
- }
861
- if (remote.protocol !== "https:") {
862
- return null;
912
+ import_node_fs.default.writeFileSync(temporaryPath, content, { flag: "wx", mode: 384 });
913
+ import_node_fs.default.chmodSync(temporaryPath, 384);
914
+ import_node_fs.default.renameSync(temporaryPath, storePath);
915
+ } finally {
916
+ import_node_fs.default.rmSync(temporaryPath, { force: true });
863
917
  }
864
- const storePath = githubCredentialsPath();
865
- import_node_fs.default.mkdirSync(import_node_path.default.dirname(storePath), { recursive: true });
866
- const hostKey = `${remote.protocol}//${remote.host}`;
867
- const existing = import_node_fs.default.existsSync(storePath) ? import_node_fs.default.readFileSync(storePath, "utf8").split(/\r?\n/).filter(Boolean) : [];
868
- const next = existing.filter((line) => {
918
+ }
919
+ function writeCredentialStoreEntries(remotes, storePath) {
920
+ const authoritativeEntries = /* @__PURE__ */ new Map();
921
+ for (const input of remotes) {
922
+ let remote;
869
923
  try {
870
- const parsed = new URL(line);
871
- return `${parsed.protocol}//${parsed.host}` !== hostKey;
924
+ remote = new URL(input.remoteUrl);
872
925
  } catch {
873
- return true;
926
+ if (input.authHeader != null) throw new Error("Invalid authenticated Git remote URL");
927
+ continue;
874
928
  }
875
- });
876
- const credentialUrl = new URL(hostKey);
877
- credentialUrl.username = credentials.username;
878
- credentialUrl.password = credentials.password;
879
- next.push(credentialUrl.toString());
880
- import_node_fs.default.writeFileSync(storePath, `${next.join("\n")}
881
- `, { mode: 384 });
882
- import_node_fs.default.chmodSync(storePath, 384);
883
- return storePath;
884
- }
885
- function credentialHelperForRemote(remoteUrl, authHeader) {
886
- const credentialStore = writeCredentialStoreEntry(remoteUrl, authHeader);
887
- return credentialStore ? `store --file=${shellQuote(credentialStore)}` : null;
888
- }
929
+ if ((remote.protocol === "https:" || remote.protocol === "http:") && (remote.username || remote.password)) {
930
+ throw new Error("Git credentials must not be embedded in a remote URL");
931
+ }
932
+ if (remote.protocol !== "https:" && remote.protocol !== "http:") {
933
+ if (input.authHeader != null) throw new Error("Unsupported authenticated Git remote protocol");
934
+ continue;
935
+ }
936
+ const hostKey = `${remote.protocol}//${remote.host}`;
937
+ let replacement = null;
938
+ if (input.authHeader != null) {
939
+ const credentials = decodeGitAuthHeader(input.authHeader);
940
+ if (!credentials) throw new Error("Unsupported Git authorization header");
941
+ const credentialUrl = new URL(hostKey);
942
+ credentialUrl.username = credentials.username;
943
+ credentialUrl.password = credentials.password;
944
+ replacement = credentialUrl.toString();
945
+ }
946
+ if (authoritativeEntries.has(hostKey) && authoritativeEntries.get(hostKey) !== replacement) {
947
+ throw new Error("Conflicting Git credentials for one transport host");
948
+ }
949
+ authoritativeEntries.set(hostKey, replacement);
950
+ }
951
+ const storeStat = lstatIfExists(storePath);
952
+ if (storeStat) {
953
+ if (storeStat.isSymbolicLink() || !storeStat.isFile()) throw new Error("Git credential store must be a regular file");
954
+ }
955
+ const replacements = [...authoritativeEntries.values()].filter((entry) => entry !== null);
956
+ if (replacements.length === 0) {
957
+ if (storeStat) import_node_fs.default.unlinkSync(storePath);
958
+ return null;
959
+ }
960
+ ensurePrivateCredentialStoreDirectory(import_node_path.default.dirname(storePath));
961
+ replaceCredentialStoreFile(storePath, `${replacements.join("\n")}
962
+ `);
963
+ return { hasCredentials: true, storePath };
964
+ }
965
+ function credentialHelperForRemotes(remotes, storePath) {
966
+ const credentialStore = writeCredentialStoreEntries(remotes, storePath);
967
+ return credentialStore?.hasCredentials ? readOnlyCredentialStoreHelper(credentialStore.storePath) : null;
968
+ }
969
+ const workerGitSecurityTestHarness = {
970
+ commandArgs: workerGitCommand.commandArgs,
971
+ credentialHelperForRemotes,
972
+ credentialStorePathForProject,
973
+ credentialStorePathForWorkspace,
974
+ legacySharedCredentialStorePath,
975
+ removeLegacySharedCredentialStore,
976
+ pruneCredentialStoreFiles,
977
+ readOnlyCredentialStoreHelper,
978
+ resetCredentialStoreDirectory,
979
+ replaceCredentialStoreFile
980
+ };
889
981
  function dockerConfigPath() {
890
982
  return import_node_path.default.join(import_node_os.default.homedir(), ".docker", "config.json");
891
983
  }
@@ -2271,6 +2363,7 @@ async function startWorker(options) {
2271
2363
  });
2272
2364
  const config = readConfig(options.configPath);
2273
2365
  const { baseUrl, token } = resolveCredentials(options, config);
2366
+ initializeCredentialStoreDirectoryOnce();
2274
2367
  const label = requireValue(options.label, "Missing required --label <label>");
2275
2368
  validateLabel(label);
2276
2369
  const projectsRoot = import_node_path.default.resolve(options.projectRoot ?? defaultProjectsRoot());
@@ -2290,6 +2383,15 @@ async function startWorker(options) {
2290
2383
  `);
2291
2384
  process.stdout.write(`[r5d-worker] server: ${baseUrl}
2292
2385
  `);
2386
+ const snapshotCleanup = (0, import_project_worktrees.cleanupStaleProjectWorktreeSnapshots)();
2387
+ if (snapshotCleanup.removed.length > 0) {
2388
+ process.stdout.write(`[r5d-worker] removed ${snapshotCleanup.removed.length} stale project snapshot(s)
2389
+ `);
2390
+ }
2391
+ for (const failure of snapshotCleanup.failed) {
2392
+ process.stderr.write(`[r5d-worker] failed to remove stale project snapshot ${failure.path}: ${failure.error}
2393
+ `);
2394
+ }
2293
2395
  runGit(["--version"]);
2294
2396
  await verifyR5dctlAuth(baseUrl, token);
2295
2397
  import_node_fs.default.mkdirSync(projectsRoot, { recursive: true });
@@ -2308,6 +2410,8 @@ async function startWorker(options) {
2308
2410
  const lastObservedProjectHeads = /* @__PURE__ */ new Map();
2309
2411
  const pendingMirrorDeletes = /* @__PURE__ */ new Map();
2310
2412
  let workspaceRemoteUrl = null;
2413
+ let workspaceCredentialHelper = null;
2414
+ let workspaceCredentialUsername = null;
2311
2415
  let workspaceGitIdentity = null;
2312
2416
  let workspaceConfigured = false;
2313
2417
  let activeWorkspaceIncidentId = null;
@@ -2322,6 +2426,7 @@ async function startWorker(options) {
2322
2426
  let reloadAfterClose = false;
2323
2427
  let shutdownAfterClose = false;
2324
2428
  const bearerAuthHeader = `Authorization: Bearer ${token}`;
2429
+ const workerCredentialUsername = credentialUsernameForAuthHeader(bearerAuthHeader);
2325
2430
  const projectBranchKey = (projectId, branchName) => `${projectId}\0${branchName}`;
2326
2431
  const projectMountId = (projectId, branchName) => `project:${projectId}:${encodeURIComponent(branchName)}`;
2327
2432
  const projectPlanMountId = (projectId, branchName) => `plan:${projectId}:${encodeURIComponent(branchName)}`;
@@ -2329,12 +2434,32 @@ async function startWorker(options) {
2329
2434
  const workspacePlanRelativePath = (projectId, branchName) => import_node_path.default.posix.join("plans", projectId, encodeURIComponent(branchName));
2330
2435
  const projectConnection = (project) => {
2331
2436
  const mirrorUrl = canonicalProjectRemoteUrl(baseUrl, project.projectId);
2332
- const mirrorAuth = gitAuthForRemote(mirrorUrl, bearerAuthHeader);
2333
2437
  const originUrl = project.repoHttpUrl ?? mirrorUrl;
2334
2438
  const originHeader = project.repoHttpUrl ? project.repoAuthHeader : bearerAuthHeader;
2335
- const originAuth = gitAuthForRemote(originUrl, originHeader);
2336
- const credentialHelper = originHeader ? credentialHelperForRemote(originUrl, originHeader) : null;
2337
- return { mirrorUrl, mirrorAuth, originUrl, originAuth, credentialHelper };
2439
+ const credentialHelper = credentialHelperForRemotes(
2440
+ [
2441
+ { remoteUrl: mirrorUrl, authHeader: bearerAuthHeader },
2442
+ { remoteUrl: originUrl, authHeader: originHeader }
2443
+ ],
2444
+ credentialStorePathForProject(project.projectId)
2445
+ );
2446
+ if (!credentialHelper) throw new Error("Project mirror credentials are unavailable");
2447
+ return {
2448
+ mirrorUrl,
2449
+ mirrorCredentialUsername: workerCredentialUsername,
2450
+ originUrl,
2451
+ originCredentialUsername: originHeader ? credentialUsernameForAuthHeader(originHeader) : null,
2452
+ credentialHelper
2453
+ };
2454
+ };
2455
+ const projectMirrorConnection = (projectId) => {
2456
+ const mirrorUrl = canonicalProjectRemoteUrl(baseUrl, projectId);
2457
+ const credentialHelper = credentialHelperForRemotes(
2458
+ [{ remoteUrl: mirrorUrl, authHeader: bearerAuthHeader }],
2459
+ credentialStorePathForProject(projectId)
2460
+ );
2461
+ if (!credentialHelper) throw new Error("Project mirror credentials are unavailable");
2462
+ return { mirrorUrl, credentialHelper, mirrorCredentialUsername: workerCredentialUsername };
2338
2463
  };
2339
2464
  const durableDeletionForBranch = (projectId, checkoutPathSegments, branchName) => projectWorkspaceState.tombstones.find(
2340
2465
  (tombstone) => tombstone.projectId === projectId && tombstone.checkoutPathSegments[0] === checkoutPathSegments[0] && tombstone.checkoutPathSegments[1] === checkoutPathSegments[1] && (tombstone.kind === "project" || tombstone.branchName === branchName)
@@ -2358,7 +2483,8 @@ async function startWorker(options) {
2358
2483
  branchName,
2359
2484
  gitDirectory: configuredProjectBranchPath(projectsRoot, project, primaryBranchName),
2360
2485
  mirrorUrl: connection.mirrorUrl,
2361
- mirrorAuth: connection.mirrorAuth,
2486
+ credentialHelper: connection.credentialHelper,
2487
+ credentialUsername: connection.mirrorCredentialUsername,
2362
2488
  sourcePath,
2363
2489
  workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branchName),
2364
2490
  planSourcePath,
@@ -2369,7 +2495,7 @@ async function startWorker(options) {
2369
2495
  };
2370
2496
  const stageProjectDeletion = (project) => {
2371
2497
  const projectRoot = configuredProjectRoot(projectsRoot, project);
2372
- const connection = projectConnection(project);
2498
+ const connection = projectMirrorConnection(project.projectId);
2373
2499
  const branches = project.branches.map(({ branchName }) => ({
2374
2500
  branchName,
2375
2501
  sourcePath: configuredProjectBranchPath(projectsRoot, project, branchName),
@@ -2390,7 +2516,8 @@ async function startWorker(options) {
2390
2516
  // hidden ref only after that publication succeeds.
2391
2517
  gitDirectory: workspaceShadowRoot,
2392
2518
  mirrorUrl: connection.mirrorUrl,
2393
- mirrorAuth: connection.mirrorAuth,
2519
+ credentialHelper: connection.credentialHelper,
2520
+ credentialUsername: connection.mirrorCredentialUsername,
2394
2521
  sourcePath: branch.sourcePath,
2395
2522
  workspaceRelativePath: workspaceProjectRelativePath(project.projectId, branch.branchName),
2396
2523
  planSourcePath: branch.planSourcePath,
@@ -2448,14 +2575,17 @@ async function startWorker(options) {
2448
2575
  for (const deletion of projectWorkspaceState.pendingMirrorRefDeletions) {
2449
2576
  const key = projectBranchKey(deletion.projectId, deletion.branchName);
2450
2577
  const planSourcePath = import_node_path.default.join(planRoot, deletion.projectId, ...deletion.branchName.split("/"));
2578
+ const liveProject = projectConfigById.get(deletion.projectId);
2579
+ const connection = liveProject ? projectConnection(liveProject) : projectMirrorConnection(deletion.projectId);
2451
2580
  import_node_fs.default.rmSync(planSourcePath, { recursive: true, force: true });
2452
2581
  pendingMirrorDeletes.set(key, {
2453
2582
  id: projectMountId(deletion.projectId, deletion.branchName),
2454
2583
  projectId: deletion.projectId,
2455
2584
  branchName: deletion.branchName,
2456
2585
  gitDirectory: workspaceShadowRoot,
2457
- mirrorUrl: canonicalProjectRemoteUrl(baseUrl, deletion.projectId),
2458
- mirrorAuth: gitAuthForRemote(canonicalProjectRemoteUrl(baseUrl, deletion.projectId), bearerAuthHeader),
2586
+ mirrorUrl: connection.mirrorUrl,
2587
+ credentialHelper: connection.credentialHelper,
2588
+ credentialUsername: connection.mirrorCredentialUsername,
2459
2589
  sourcePath: deletion.managedPath,
2460
2590
  workspaceRelativePath: workspaceProjectRelativePath(deletion.projectId, deletion.branchName),
2461
2591
  planSourcePath,
@@ -2603,9 +2733,9 @@ async function startWorker(options) {
2603
2733
  primaryBranchName: primaryProjectBranch(project),
2604
2734
  branches: project.branches,
2605
2735
  originUrl: connection.originUrl,
2606
- originAuth: connection.originAuth,
2736
+ originCredentialUsername: connection.originCredentialUsername,
2607
2737
  mirrorUrl: connection.mirrorUrl,
2608
- mirrorAuth: connection.mirrorAuth,
2738
+ mirrorCredentialUsername: connection.mirrorCredentialUsername,
2609
2739
  credentialHelper: connection.credentialHelper,
2610
2740
  gitIdentity: workspaceGitIdentity
2611
2741
  });
@@ -2644,10 +2774,17 @@ async function startWorker(options) {
2644
2774
  if (!readyProjectIds.has(project.projectId) || project.branches.length === 0) continue;
2645
2775
  const connection = projectConnection(project);
2646
2776
  const primaryPath = configuredProjectBranchPath(projectsRoot, project, primaryProjectBranch(project));
2647
- if (!tryGit(["fetch", "--no-recurse-submodules", "--prune", "origin", "+refs/heads/*:refs/remotes/origin/*"], {
2648
- cwd: primaryPath,
2649
- auth: connection.originAuth
2650
- })) {
2777
+ if (!tryGit(
2778
+ [
2779
+ ...(0, import_git_process_environment.gitTransportSecurityArgs)(connection.originUrl, connection.credentialHelper, connection.originCredentialUsername),
2780
+ "fetch",
2781
+ "--no-recurse-submodules",
2782
+ "--prune",
2783
+ "origin",
2784
+ "+refs/heads/*:refs/remotes/origin/*"
2785
+ ],
2786
+ { cwd: primaryPath }
2787
+ )) {
2651
2788
  process.stderr.write(`[r5d-worker] could not refresh origin divergence for ${project.projectPath}
2652
2789
  `);
2653
2790
  }
@@ -2697,7 +2834,8 @@ async function startWorker(options) {
2697
2834
  projectRoot: configuredProjectRoot(projectsRoot, project),
2698
2835
  branchNames: project.branches.map(({ branchName }) => branchName),
2699
2836
  mirrorUrl: connection.mirrorUrl,
2700
- mirrorAuth: connection.mirrorAuth,
2837
+ credentialHelper: connection.credentialHelper,
2838
+ credentialUsername: connection.mirrorCredentialUsername,
2701
2839
  onlyBranches: changed
2702
2840
  });
2703
2841
  for (const result of results) {
@@ -2724,7 +2862,8 @@ async function startWorker(options) {
2724
2862
  gitDirectory: deletion.gitDirectory,
2725
2863
  branchName: deletion.branchName,
2726
2864
  mirrorUrl: deletion.mirrorUrl,
2727
- mirrorAuth: deletion.mirrorAuth
2865
+ credentialHelper: deletion.credentialHelper,
2866
+ credentialUsername: deletion.credentialUsername
2728
2867
  });
2729
2868
  }
2730
2869
  if (deletion.tombstoneId && !deletion.projectDeleted) {
@@ -2734,6 +2873,9 @@ async function startWorker(options) {
2734
2873
  });
2735
2874
  }
2736
2875
  pendingMirrorDeletes.delete(key);
2876
+ if (deletion.projectDeleted && !projectConfigById.has(deletion.projectId) && ![...pendingMirrorDeletes.values()].some((pending) => pending.projectId === deletion.projectId)) {
2877
+ removeProjectCredentialStore(deletion.projectId);
2878
+ }
2737
2879
  lastObservedProjectHeads.delete(key);
2738
2880
  }
2739
2881
  };
@@ -2747,7 +2889,8 @@ async function startWorker(options) {
2747
2889
  primaryBranchName: primaryProjectBranch(project),
2748
2890
  branchNames: project.branches.map(({ branchName }) => branchName),
2749
2891
  mirrorUrl: connection.mirrorUrl,
2750
- mirrorAuth: connection.mirrorAuth
2892
+ credentialHelper: connection.credentialHelper,
2893
+ credentialUsername: connection.mirrorCredentialUsername
2751
2894
  });
2752
2895
  for (const state of refreshed) {
2753
2896
  lastObservedProjectHeads.set(
@@ -2772,9 +2915,9 @@ async function startWorker(options) {
2772
2915
  primaryBranchName: primaryProjectBranch(project),
2773
2916
  branches: project.branches,
2774
2917
  originUrl: connection.originUrl,
2775
- originAuth: connection.originAuth,
2918
+ originCredentialUsername: connection.originCredentialUsername,
2776
2919
  mirrorUrl: connection.mirrorUrl,
2777
- mirrorAuth: connection.mirrorAuth,
2920
+ mirrorCredentialUsername: connection.mirrorCredentialUsername,
2778
2921
  credentialHelper: connection.credentialHelper,
2779
2922
  gitIdentity: workspaceGitIdentity
2780
2923
  });
@@ -2821,16 +2964,16 @@ async function startWorker(options) {
2821
2964
  error: error instanceof Error ? error.message : String(error)
2822
2965
  });
2823
2966
  const performWorkspaceSync = async (input) => {
2824
- if (!workspaceRemoteUrl || !workspaceGitIdentity) throw new Error("Worker workspace configuration has not been received");
2967
+ if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
2968
+ throw new Error("Worker workspace configuration has not been received");
2969
+ }
2825
2970
  const mounts = buildWorkspaceMounts();
2826
- const remoteAuth = gitAuthForRemote(workspaceRemoteUrl, bearerAuthHeader);
2827
- const credentialHelper = credentialHelperForRemote(workspaceRemoteUrl, bearerAuthHeader);
2828
2971
  if (input.resetToCanonical) {
2829
2972
  const reset = (0, import_workspace_git_sync.resetWorkspaceGit)({
2830
2973
  workspacePath: workspaceShadowRoot,
2831
2974
  remoteUrl: workspaceRemoteUrl,
2832
- remoteAuth,
2833
- credentialHelper,
2975
+ credentialHelper: workspaceCredentialHelper,
2976
+ credentialUsername: workspaceCredentialUsername,
2834
2977
  gitIdentity: workspaceGitIdentity,
2835
2978
  mounts
2836
2979
  });
@@ -2859,8 +3002,8 @@ async function startWorker(options) {
2859
3002
  workerLabel: label,
2860
3003
  workspacePath: workspaceShadowRoot,
2861
3004
  remoteUrl: workspaceRemoteUrl,
2862
- remoteAuth,
2863
- credentialHelper,
3005
+ credentialHelper: workspaceCredentialHelper,
3006
+ credentialUsername: workspaceCredentialUsername,
2864
3007
  gitIdentity: workspaceGitIdentity,
2865
3008
  mounts,
2866
3009
  commitDetail: input.confirmationReason ?? input.trigger.detail,
@@ -2973,13 +3116,47 @@ async function startWorker(options) {
2973
3116
  try {
2974
3117
  return await workspaceSyncSingleFlight.runExclusive(async () => {
2975
3118
  workspaceConfigured = false;
3119
+ projectWorkspaceState = projectWorkspaceStateStore.reconcile({
3120
+ desiredProjects: message.projects
3121
+ });
3122
+ const requiredCredentialStorePaths = /* @__PURE__ */ new Set();
3123
+ const workspaceStorePath = credentialStorePathForWorkspace(message.workspaceRemoteUrl);
3124
+ requiredCredentialStorePaths.add(workspaceStorePath);
3125
+ const nextWorkspaceCredentialHelper = credentialHelperForRemotes(
3126
+ [{ remoteUrl: message.workspaceRemoteUrl, authHeader: bearerAuthHeader }],
3127
+ workspaceStorePath
3128
+ );
3129
+ if (!nextWorkspaceCredentialHelper) throw new Error("Workspace credentials are unavailable");
3130
+ const desiredProjectConfigById = new Map(message.projects.map((project) => [project.projectId, project]));
3131
+ for (const project of message.projects) {
3132
+ requiredCredentialStorePaths.add(credentialStorePathForProject(project.projectId));
3133
+ projectConnection(project);
3134
+ }
3135
+ const pendingCredentialProjectIds = /* @__PURE__ */ new Set([
3136
+ ...[...pendingMirrorDeletes.values()].map(({ projectId }) => projectId),
3137
+ ...projectWorkspaceState.pendingTreeDeletions.map(({ projectId }) => projectId),
3138
+ ...projectWorkspaceState.pendingMirrorRefDeletions.map(({ projectId }) => projectId)
3139
+ ]);
3140
+ for (const projectId of pendingCredentialProjectIds) {
3141
+ const desiredProject = desiredProjectConfigById.get(projectId);
3142
+ if (desiredProject) projectConnection(desiredProject);
3143
+ else projectMirrorConnection(projectId);
3144
+ requiredCredentialStorePaths.add(credentialStorePathForProject(projectId));
3145
+ }
3146
+ for (const deletion of pendingMirrorDeletes.values()) {
3147
+ const desiredProject = desiredProjectConfigById.get(deletion.projectId);
3148
+ const connection = desiredProject ? projectConnection(desiredProject) : projectMirrorConnection(deletion.projectId);
3149
+ deletion.mirrorUrl = connection.mirrorUrl;
3150
+ deletion.credentialHelper = connection.credentialHelper;
3151
+ deletion.credentialUsername = connection.mirrorCredentialUsername;
3152
+ }
3153
+ pruneCredentialStoreFiles(requiredCredentialStorePaths);
2976
3154
  configureGitHubAuth(message.githubCredential);
2977
3155
  visibleGitIdentity = message.gitIdentity;
2978
3156
  workspaceGitIdentity = message.gitIdentity;
2979
3157
  workspaceRemoteUrl = message.workspaceRemoteUrl;
2980
- projectWorkspaceState = projectWorkspaceStateStore.reconcile({
2981
- desiredProjects: message.projects
2982
- });
3158
+ workspaceCredentialHelper = nextWorkspaceCredentialHelper;
3159
+ workspaceCredentialUsername = workerCredentialUsername;
2983
3160
  (0, import_project_workspace_state.pruneAuthoritativelyDesiredBranchDeletions)(pendingMirrorDeletes, message.projects);
2984
3161
  const effectiveProjects = effectiveWorkerProjects(message.projects).map((project) => ({
2985
3162
  ...project,
@@ -3010,13 +3187,11 @@ async function startWorker(options) {
3010
3187
  for (const projectId of [...readyProjectIds]) {
3011
3188
  if (!projectConfigById.has(projectId)) readyProjectIds.delete(projectId);
3012
3189
  }
3013
- const remoteAuth = gitAuthForRemote(message.workspaceRemoteUrl, bearerAuthHeader);
3014
- const credentialHelper = credentialHelperForRemote(message.workspaceRemoteUrl, bearerAuthHeader);
3015
3190
  (0, import_workspace_git_sync.ensureWorkspaceGitClone)({
3016
3191
  workspacePath: workspaceShadowRoot,
3017
3192
  remoteUrl: message.workspaceRemoteUrl,
3018
- remoteAuth,
3019
- credentialHelper,
3193
+ credentialHelper: nextWorkspaceCredentialHelper,
3194
+ credentialUsername: workerCredentialUsername,
3020
3195
  gitIdentity: message.gitIdentity
3021
3196
  });
3022
3197
  pruneStaleOuterWorkspaceEntries(projectWorkspaceStateStore.discoverStaleOuterEntries(workspaceShadowRoot));
@@ -3816,5 +3991,6 @@ if (isCliEntrypoint()) {
3816
3991
  resolveWorkerFilePath,
3817
3992
  resolveWorkerSessionTarget,
3818
3993
  syncSessionArtifacts,
3994
+ workerGitSecurityTestHarness,
3819
3995
  writeWorkerTextFile
3820
3996
  });
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.75",
3
+ "version": "0.0.77",
4
4
  "type": "commonjs"
5
5
  }