@ricsam/r5d-worker 0.0.57 → 0.0.58

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.
@@ -960,7 +960,7 @@ function hydrateProjectFromShadowOptimistically(input, relativeRoot, visibleRoot
960
960
  expectedCheckoutSnapshot = checkoutGitSnapshot(visibleRoot);
961
961
  return expectedCheckoutSnapshot !== null;
962
962
  };
963
- let currentGitlinks = new Map(checkoutGitlinks(visibleRoot).map((entry) => [entry.filePath, entry]));
963
+ const currentGitlinks = new Map(checkoutGitlinks(visibleRoot).map((entry) => [entry.filePath, entry]));
964
964
  const changedPaths = [.../* @__PURE__ */ new Set([...preimage.keys(), ...desired.keys()])].filter(
965
965
  (filePath) => !hydrationEntriesEqual(preimage.get(filePath), desired.get(filePath))
966
966
  );
@@ -1582,6 +1582,33 @@ function resetUncommittedShadowSnapshot(input) {
1582
1582
  }
1583
1583
  runGit(input, input.shadowRoot, ["clean", "-fd"], "clean interrupted workspace snapshot");
1584
1584
  }
1585
+ function activeWorkspaceRebase(input) {
1586
+ for (const name of ["rebase-merge", "rebase-apply"]) {
1587
+ const resolved = runGitResult(input, input.shadowRoot, ["rev-parse", "--git-path", name]);
1588
+ if (resolved.exitCode !== 0 || !resolved.stdout) continue;
1589
+ const gitPath = import_node_path.default.isAbsolute(resolved.stdout) ? resolved.stdout : import_node_path.default.resolve(input.shadowRoot, resolved.stdout);
1590
+ if (import_node_fs.default.existsSync(gitPath)) return name;
1591
+ }
1592
+ return null;
1593
+ }
1594
+ function remediationRebaseInProgressResult(input, result) {
1595
+ if (input.trigger.type !== "remediation" && input.trigger.type !== "remediation_confirm") return null;
1596
+ const rebaseKind = activeWorkspaceRebase(input);
1597
+ if (!rebaseKind) return null;
1598
+ const conflictPaths = runGitResult(input, input.shadowRoot, ["diff", "--name-only", "--diff-filter=U", "-z"]).stdout.split("\0").filter(Boolean).sort();
1599
+ const status = reportedStatus(gitStatus(input));
1600
+ return {
1601
+ ...result,
1602
+ outcome: "conflict_blocked",
1603
+ expectedHead: revParse(input, `origin/${WORKSPACE_BRANCH}`),
1604
+ candidateHead: revParse(input, "ORIG_HEAD") ?? revParse(input, "HEAD") ?? void 0,
1605
+ rebaseCount: 1,
1606
+ gitStatus: status,
1607
+ affectedProjects: affectedProjects(conflictPaths),
1608
+ affectedPaths: reportedPaths(conflictPaths),
1609
+ error: `Workspace conflict remediation has an in-progress ${rebaseKind}; the index and worktree were preserved until the resolver completes or aborts it.`
1610
+ };
1611
+ }
1585
1612
  function fastForwardCleanShadowForNewCheckouts(input) {
1586
1613
  if (!input.newVisibleCheckouts?.length) return null;
1587
1614
  runGit(input, input.shadowRoot, ["add", "-A"], "stage existing workspace changes before checkout hydration");
@@ -1801,17 +1828,66 @@ function convergeRedundantInboundCandidate(input, result) {
1801
1828
  affectedPaths: []
1802
1829
  };
1803
1830
  }
1831
+ function preserveWorkspaceConflictCandidate(input, observed, options) {
1832
+ try {
1833
+ if (!options.baseRevision) throw new Error("the preserved workspace does not have an old-base commit");
1834
+ runGit(input, input.shadowRoot, ["reset", "--hard", options.baseRevision], "restore old-base workspace conflict candidate");
1835
+ runGit(input, input.shadowRoot, ["clean", "-fd"], "clean old-base workspace conflict candidate");
1836
+ if (!options.preserveShadowCandidate) {
1837
+ const projection = mirrorVisibleWorkspaceToShadow(input);
1838
+ restoreUnscopedCanonicalCheckoutSubtrees(input);
1839
+ const stagePathspecs = projection.opaqueRoots.flatMap((root) => [`:(exclude,literal)${root}`]);
1840
+ runGit(input, input.shadowRoot, ["add", "-A", "--", ".", ...stagePathspecs], "stage preserved workspace conflict candidate");
1841
+ forceStageCanonicalCheckoutFiles(input, projection.entries, projection.opaqueRoots);
1842
+ if (stagedPaths(input).length > 0) {
1843
+ runGit(input, input.shadowRoot, ["commit", "-m", commitMessage(input)], "commit preserved workspace conflict candidate");
1844
+ }
1845
+ }
1846
+ const candidateHead = revParse(input, "HEAD");
1847
+ if (!candidateHead) throw new Error("the preserved workspace conflict candidate does not have a commit");
1848
+ assertUnscopedCanonicalCheckoutSubtreesMatchRemote(input, candidateHead);
1849
+ const upload = uploadWorkspaceCandidate(input, candidateHead);
1850
+ if (upload.exitCode !== 0) {
1851
+ throw new Error(upload.stderr || upload.stdout || "workspace conflict candidate upload failed");
1852
+ }
1853
+ const conflictPaths = [...new Set(options.conflictPaths)].sort();
1854
+ return {
1855
+ ...observed,
1856
+ outcome: "conflict_blocked",
1857
+ expectedHead: options.expectedHead,
1858
+ candidateHead,
1859
+ quarantineRef: input.quarantineRef,
1860
+ ...options.publishedHead ? { publishedHead: options.publishedHead } : {},
1861
+ rebaseCount: options.rebaseCount ?? 0,
1862
+ affectedProjects: affectedProjects(conflictPaths),
1863
+ affectedPaths: reportedPaths(conflictPaths),
1864
+ error: options.error,
1865
+ gitStatus: gitStatus(input)
1866
+ };
1867
+ } catch (error) {
1868
+ return {
1869
+ ...observed,
1870
+ outcome: "failed",
1871
+ rebaseCount: options.rebaseCount ?? 0,
1872
+ affectedProjects: affectedProjects(options.conflictPaths),
1873
+ affectedPaths: reportedPaths(options.conflictPaths),
1874
+ error: `Workspace synchronization found a conflict but could not preserve a durable candidate: ${error instanceof Error ? error.message : String(error)}`,
1875
+ gitStatus: gitStatus(input)
1876
+ };
1877
+ }
1878
+ }
1804
1879
  async function synchronizeWorkspace(rawInput) {
1805
1880
  let input = { ...rawInput, attemptId: rawInput.attemptId ?? crypto.randomUUID() };
1806
1881
  try {
1807
1882
  input = { ...input, projects: workspaceProjectsForSync(input.projects, input.trigger) };
1883
+ const isCanonicalRemediationSync = input.skipVisibleMirror === true && (input.trigger.type === "remediation" || input.trigger.type === "remediation_confirm");
1808
1884
  let mirroredGitlinkProjection = { entries: [], opaqueRoots: [], visiblePreimages: /* @__PURE__ */ new Map() };
1809
1885
  let fastForwardedFromHead = null;
1810
1886
  const shadowWasCreated = ensureShadowWorkspace(input);
1811
1887
  let hydrationPreimages = input.skipVisibleMirror ? hydrationPreimagesFromShadowRevision(input, shadowWasCreated ? null : revParse(input, "HEAD")) : /* @__PURE__ */ new Map();
1812
1888
  const remoteHeadAtStart = revParse(input, `origin/${WORKSPACE_BRANCH}`);
1813
1889
  const result = baseResult(input, remoteHeadAtStart);
1814
- const inboundConflictsAtStart = input.skipVisibleMirror && remoteHeadAtStart ? visibleHydrationConflictPaths(input, hydrationPreimages, `origin/${WORKSPACE_BRANCH}`) : [];
1890
+ const inboundConflictsAtStart = input.skipVisibleMirror && !isCanonicalRemediationSync && remoteHeadAtStart ? visibleHydrationConflictPaths(input, hydrationPreimages, `origin/${WORKSPACE_BRANCH}`) : [];
1815
1891
  if (input.resetToCanonical) {
1816
1892
  if (input.trigger.canonicalCheckoutOnly) {
1817
1893
  throw new Error("A canonical-checkout-only synchronization cannot reset the authoritative resolver checkout");
@@ -1825,6 +1901,8 @@ async function synchronizeWorkspace(rawInput) {
1825
1901
  gitStatus: gitStatus(input)
1826
1902
  };
1827
1903
  }
1904
+ const inProgressRemediation = remediationRebaseInProgressResult(input, result);
1905
+ if (inProgressRemediation) return inProgressRemediation;
1828
1906
  if (!input.skipVisibleMirror) resetUncommittedShadowSnapshot(input);
1829
1907
  restoreUnscopedCanonicalCheckoutSubtrees(input);
1830
1908
  if (!input.skipVisibleMirror) {
@@ -1880,15 +1958,12 @@ async function synchronizeWorkspace(rawInput) {
1880
1958
  const hasUnpushedCommit = candidateHead ? !hasRemoteMain || !tryGit(input, input.shadowRoot, ["merge-base", "--is-ancestor", candidateHead, `origin/${WORKSPACE_BRANCH}`]) : false;
1881
1959
  if (hasUnpushedCommit && inboundConflictsAtStart.length > 0) {
1882
1960
  const conflictPaths = [.../* @__PURE__ */ new Set([...paths, ...inboundConflictsAtStart])].sort();
1883
- return {
1884
- ...observed,
1885
- outcome: "failed",
1886
- candidateHead: candidateHead ?? void 0,
1887
- affectedProjects: affectedProjects(conflictPaths),
1888
- affectedPaths: reportedPaths(conflictPaths),
1889
- error: "Inbound workspace update overlaps local visible changes; the old-base candidate was preserved for reconciliation.",
1890
- gitStatus: gitStatus(input)
1891
- };
1961
+ return preserveWorkspaceConflictCandidate(input, observed, {
1962
+ baseRevision: candidateHead,
1963
+ expectedHead: revParse(input, `origin/${WORKSPACE_BRANCH}`),
1964
+ conflictPaths,
1965
+ error: "Inbound workspace update overlaps local visible changes; the old-base candidate was preserved for reconciliation."
1966
+ });
1892
1967
  }
1893
1968
  if (!hasUnpushedCommit) {
1894
1969
  const localHead = revParse(input, "HEAD");
@@ -1897,22 +1972,21 @@ async function synchronizeWorkspace(rawInput) {
1897
1972
  runGit(input, input.shadowRoot, ["reset", "--hard", `origin/${WORKSPACE_BRANCH}`], "update workspace from canonical state");
1898
1973
  runGit(input, input.shadowRoot, ["clean", "-fd"], "clean updated workspace");
1899
1974
  assertPublishedCanonicalCheckoutTree(input, currentRemoteHead, sampledCanonicalCheckoutTreeHash);
1900
- mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots, hydrationPreimages);
1901
- const inboundConflicts = input.trigger.canonicalCheckoutOnly ? [] : localHead ? hydrationConflictPaths(input, hydrationPreimages, localHead, currentRemoteHead) : visibleHydrationConflictPaths(input, hydrationPreimages, currentRemoteHead);
1975
+ if (!isCanonicalRemediationSync) {
1976
+ mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots, hydrationPreimages);
1977
+ }
1978
+ const inboundConflicts = input.trigger.canonicalCheckoutOnly || isCanonicalRemediationSync ? [] : localHead ? hydrationConflictPaths(input, hydrationPreimages, localHead, currentRemoteHead) : visibleHydrationConflictPaths(input, hydrationPreimages, currentRemoteHead);
1902
1979
  if (inboundConflicts.length > 0 && localHead) {
1903
1980
  runGit(input, input.shadowRoot, ["reset", "--hard", localHead], "preserve old workspace base after inbound conflict");
1904
1981
  runGit(input, input.shadowRoot, ["clean", "-fd"], "clean preserved old workspace base");
1905
1982
  const conflictPaths = [.../* @__PURE__ */ new Set([...paths, ...inboundConflicts])].sort();
1906
- return {
1907
- ...observed,
1908
- outcome: "failed",
1909
- candidateHead: candidateHead ?? void 0,
1983
+ return preserveWorkspaceConflictCandidate(input, observed, {
1984
+ baseRevision: localHead,
1985
+ expectedHead: currentRemoteHead,
1910
1986
  publishedHead: currentRemoteHead,
1911
- affectedProjects: affectedProjects(conflictPaths),
1912
- affectedPaths: reportedPaths(conflictPaths),
1913
- error: "Inbound workspace update overlaps local visible changes; disjoint updates were hydrated and the old base was preserved for reconciliation.",
1914
- gitStatus: gitStatus(input)
1915
- };
1987
+ conflictPaths,
1988
+ error: "Inbound workspace update overlaps local visible changes; disjoint updates were hydrated and the old base was preserved for reconciliation."
1989
+ });
1916
1990
  }
1917
1991
  return {
1918
1992
  ...observed,
@@ -1925,8 +1999,10 @@ async function synchronizeWorkspace(rawInput) {
1925
1999
  const authoritativeHead = currentRemoteHead ?? localHead;
1926
2000
  if (authoritativeHead) {
1927
2001
  assertPublishedCanonicalCheckoutTree(input, authoritativeHead, sampledCanonicalCheckoutTreeHash);
1928
- mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots, hydrationPreimages);
1929
- const hydrationConflicts2 = input.trigger.canonicalCheckoutOnly ? [] : fastForwardedFromHead ? hydrationConflictPaths(input, hydrationPreimages, fastForwardedFromHead, authoritativeHead) : visibleHydrationConflictPaths(input, hydrationPreimages, authoritativeHead);
2002
+ if (!isCanonicalRemediationSync) {
2003
+ mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots, hydrationPreimages);
2004
+ }
2005
+ const hydrationConflicts2 = input.trigger.canonicalCheckoutOnly || isCanonicalRemediationSync ? [] : fastForwardedFromHead ? hydrationConflictPaths(input, hydrationPreimages, fastForwardedFromHead, authoritativeHead) : visibleHydrationConflictPaths(input, hydrationPreimages, authoritativeHead);
1930
2006
  if (hydrationConflicts2.length > 0 && fastForwardedFromHead) {
1931
2007
  runGit(
1932
2008
  input,
@@ -1935,16 +2011,13 @@ async function synchronizeWorkspace(rawInput) {
1935
2011
  "preserve old workspace base after new-checkout fast-forward conflict"
1936
2012
  );
1937
2013
  runGit(input, input.shadowRoot, ["clean", "-fd"], "clean preserved new-checkout workspace base");
1938
- return {
1939
- ...observed,
1940
- outcome: "failed",
1941
- candidateHead: candidateHead ?? void 0,
2014
+ return preserveWorkspaceConflictCandidate(input, observed, {
2015
+ baseRevision: fastForwardedFromHead,
2016
+ expectedHead: currentRemoteHead,
1942
2017
  publishedHead: currentRemoteHead ?? void 0,
1943
- affectedProjects: affectedProjects(hydrationConflicts2),
1944
- affectedPaths: reportedPaths(hydrationConflicts2),
1945
- error: "Workspace fast-forward for a new checkout overlaps local visible changes; compatible updates were hydrated and the old base was preserved for reconciliation.",
1946
- gitStatus: gitStatus(input)
1947
- };
2018
+ conflictPaths: hydrationConflicts2,
2019
+ error: "Workspace fast-forward for a new checkout overlaps local visible changes; compatible updates were hydrated and the old base was preserved for reconciliation."
2020
+ });
1948
2021
  }
1949
2022
  }
1950
2023
  return {
@@ -1964,17 +2037,14 @@ async function synchronizeWorkspace(rawInput) {
1964
2037
  const conflicted = runGitResult(input, input.shadowRoot, ["diff", "--name-only", "--diff-filter=U", "-z"]);
1965
2038
  const conflictPaths = [.../* @__PURE__ */ new Set([...paths, ...conflicted.stdout.split("\0").filter(Boolean)])].sort();
1966
2039
  runGit(input, input.shadowRoot, ["rebase", "--abort"], "preserve workspace candidate after rebase conflict");
1967
- return {
1968
- ...observed,
1969
- outcome: "failed",
2040
+ return preserveWorkspaceConflictCandidate(input, observed, {
2041
+ baseRevision: candidateHead,
1970
2042
  expectedHead,
1971
- candidateHead: candidateHead ?? void 0,
1972
2043
  rebaseCount,
1973
- affectedProjects: affectedProjects(conflictPaths),
1974
- affectedPaths: reportedPaths(conflictPaths),
1975
- error: `Workspace candidate conflicts with canonical head ${expectedHead}; local changes were preserved for reconciliation.`,
1976
- gitStatus: gitStatus(input)
1977
- };
2044
+ preserveShadowCandidate: isCanonicalRemediationSync,
2045
+ conflictPaths,
2046
+ error: `Workspace candidate conflicts with canonical head ${expectedHead}; local changes were preserved for reconciliation.`
2047
+ });
1978
2048
  }
1979
2049
  candidateHead = revParse(input, "HEAD");
1980
2050
  assertPublishedCanonicalCheckoutTree(input, "HEAD", sampledCanonicalCheckoutTreeHash);
@@ -1990,8 +2060,10 @@ async function synchronizeWorkspace(rawInput) {
1990
2060
  };
1991
2061
  }
1992
2062
  assertPublishedCanonicalCheckoutTree(input, candidateHead, sampledCanonicalCheckoutTreeHash);
1993
- mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots, hydrationPreimages);
1994
- const hydrationConflicts = input.trigger.canonicalCheckoutOnly ? [] : candidateHeadBeforeRebase ? hydrationConflictPaths(input, hydrationPreimages, candidateHeadBeforeRebase, "HEAD") : visibleHydrationConflictPaths(input, hydrationPreimages, "HEAD");
2063
+ if (!isCanonicalRemediationSync) {
2064
+ mirrorShadowWorkspaceToVisible(input, mirroredGitlinkProjection.opaqueRoots, hydrationPreimages);
2065
+ }
2066
+ const hydrationConflicts = input.trigger.canonicalCheckoutOnly || isCanonicalRemediationSync ? [] : candidateHeadBeforeRebase ? hydrationConflictPaths(input, hydrationPreimages, candidateHeadBeforeRebase, "HEAD") : visibleHydrationConflictPaths(input, hydrationPreimages, "HEAD");
1995
2067
  if (hydrationConflicts.length > 0 && candidateHeadBeforeRebase) {
1996
2068
  runGit(
1997
2069
  input,
@@ -2001,17 +2073,13 @@ async function synchronizeWorkspace(rawInput) {
2001
2073
  );
2002
2074
  runGit(input, input.shadowRoot, ["clean", "-fd"], "clean old-base workspace candidate");
2003
2075
  const conflictPaths = [.../* @__PURE__ */ new Set([...paths, ...hydrationConflicts])].sort();
2004
- return {
2005
- ...observed,
2006
- outcome: "failed",
2076
+ return preserveWorkspaceConflictCandidate(input, observed, {
2077
+ baseRevision: candidateHeadBeforeRebase,
2007
2078
  expectedHead,
2008
- candidateHead,
2009
2079
  rebaseCount,
2010
- affectedProjects: affectedProjects(conflictPaths),
2011
- affectedPaths: reportedPaths(conflictPaths),
2012
- error: "Workspace changed while its candidate was reconciling; visible changes and the old-base candidate were preserved.",
2013
- gitStatus: gitStatus(input)
2014
- };
2080
+ conflictPaths,
2081
+ error: "Workspace changed while its candidate was reconciling; visible changes and the old-base candidate were preserved."
2082
+ });
2015
2083
  }
2016
2084
  const upload = uploadWorkspaceCandidate(input, candidateHead);
2017
2085
  if (upload.exitCode !== 0) {
@@ -2096,6 +2164,9 @@ class WorkspaceSyncSingleFlight {
2096
2164
  fingerprintPrepared(prepare) {
2097
2165
  return this.enqueue(() => calculateWorkspaceDiffFingerprint(prepare()));
2098
2166
  }
2167
+ runExclusive(operation) {
2168
+ return this.enqueue(operation);
2169
+ }
2099
2170
  runMutation(operation) {
2100
2171
  return this.mutationGate.runMutation(operation);
2101
2172
  }
package/dist/mjs/main.mjs CHANGED
@@ -15,6 +15,11 @@ import { terminateProcessTree } from "./process-tree.mjs";
15
15
  import { openWorkerPortForwardRelay } from "./port-forward-client.mjs";
16
16
  import { applyWorkspaceIncidentUpdate, releasePendingWorkspaceHead } from "./workspace-incident-state.mjs";
17
17
  import { acquireWorkspaceCommandMutation, runWorkspaceCommand } from "./workspace-command-sync-policy.mjs";
18
+ import {
19
+ RecentWorkspaceSyncProofCache,
20
+ readShadowWorkspaceCanonicalHead,
21
+ workspaceSyncScopeKey
22
+ } from "./workspace-sync-fast-path.mjs";
18
23
  import {
19
24
  isRetryableWorkerServerStatus,
20
25
  superviseWorkerRuntime,
@@ -31,6 +36,9 @@ import {
31
36
  synchronizeWorkspace,
32
37
  workspaceProjectsForSync
33
38
  } from "./workspace-sync.mjs";
39
+ function workspaceSyncFastPathTelemetry(decision) {
40
+ return decision.hit ? { hit: true, reason: decision.reason, proofAgeMs: decision.proofAgeMs } : { hit: false, reason: decision.reason };
41
+ }
34
42
  class WorkerServerUnavailableError extends Error {
35
43
  name = "WorkerServerUnavailableError";
36
44
  }
@@ -2369,6 +2377,7 @@ async function startWorker(options) {
2369
2377
  let sessionArtifactSyncRequestsInFlight = 0;
2370
2378
  let cliUpdateInProgress = false;
2371
2379
  let reloadAfterClose = false;
2380
+ const recentWorkspaceSyncProof = new RecentWorkspaceSyncProofCache();
2372
2381
  const workspaceSyncInput = (trigger, overrides = {}) => {
2373
2382
  if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
2374
2383
  const projects = workspaceProjectsForSync([...manifestByProjectId.values()], trigger);
@@ -2460,19 +2469,64 @@ async function startWorker(options) {
2460
2469
  }
2461
2470
  return created;
2462
2471
  };
2463
- const runRequestedWorkspaceSync = async (authority, trigger, overrides = {}) => {
2472
+ const runRequestedWorkspaceSync = async (authority, trigger, overrides = {}, authoritativeCanonicalHead, allowRecentNoChangeFastPath) => {
2473
+ const requestedAt = Date.now();
2474
+ let queueEnteredAt = requestedAt;
2475
+ let prepareStartedAt = requestedAt;
2476
+ let synchronizeStartedAt = requestedAt;
2477
+ let synchronizeFinishedAt = requestedAt;
2478
+ let fastPathDecision = { hit: false, reason: "no_recent_proof" };
2464
2479
  workspaceSyncRequestsInFlight += 1;
2465
2480
  try {
2466
2481
  let pendingTargets = [];
2467
- const result = await workspaceSyncSingleFlight.runPrepared(() => {
2482
+ const result = await workspaceSyncSingleFlight.runExclusive(async () => {
2483
+ queueEnteredAt = Date.now();
2468
2484
  const shouldEnsureCheckouts = !overrides.skipVisibleMirror && !overrides.resetToCanonical;
2469
2485
  const syncProjects = workspaceProjectsForSync([...manifestByProjectId.values()], trigger);
2486
+ if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
2487
+ const scopeKey = workspaceSyncScopeKey(workspaceRemoteUrl, syncProjects);
2470
2488
  const allowedCheckoutKeys = new Set(
2471
2489
  syncProjects.flatMap((project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName)))
2472
2490
  );
2473
2491
  const scopedPendingTargets = [...pendingManifestCheckouts.values()].filter(
2474
2492
  (target) => allowedCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
2475
2493
  );
2494
+ const workerBusy = activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress;
2495
+ fastPathDecision = recentWorkspaceSyncProof.evaluate({
2496
+ trigger,
2497
+ allowRecentNoChangeFastPath,
2498
+ canonicalHead: authoritativeCanonicalHead,
2499
+ scopeKey,
2500
+ nowMs: Date.now(),
2501
+ skipVisibleMirror: overrides.skipVisibleMirror,
2502
+ resetToCanonical: overrides.resetToCanonical,
2503
+ incidentActive: activeWorkspaceIncidentId !== null,
2504
+ workerBusy,
2505
+ pendingCheckouts: scopedPendingTargets.length > 0 || Boolean(overrides.newVisibleCheckouts?.length)
2506
+ });
2507
+ if (fastPathDecision.hit) {
2508
+ prepareStartedAt = queueEnteredAt;
2509
+ synchronizeStartedAt = queueEnteredAt;
2510
+ synchronizeFinishedAt = queueEnteredAt;
2511
+ return {
2512
+ type: "workspace_sync",
2513
+ attemptId: overrides.attemptId ?? crypto.randomUUID(),
2514
+ workerLabel: label,
2515
+ trigger,
2516
+ outcome: "no_change",
2517
+ startingHead: fastPathDecision.canonicalHead,
2518
+ expectedHead: fastPathDecision.canonicalHead,
2519
+ publishedHead: fastPathDecision.canonicalHead,
2520
+ rebaseCount: 0,
2521
+ diffSizeBytes: 0,
2522
+ gitStatus: "",
2523
+ affectedProjects: [],
2524
+ affectedPaths: [],
2525
+ discardedPaths: [],
2526
+ localChangesDiscarded: false
2527
+ };
2528
+ }
2529
+ prepareStartedAt = Date.now();
2476
2530
  let checkoutTargets = [];
2477
2531
  if (shouldEnsureCheckouts) {
2478
2532
  checkoutTargets = workspaceSyncCheckoutTargets({
@@ -2492,21 +2546,53 @@ async function startWorker(options) {
2492
2546
  if (allowedCheckoutKeys.has(key)) newVisibleCheckoutByKey.set(key, target);
2493
2547
  }
2494
2548
  const newVisibleCheckouts = [...newVisibleCheckoutByKey.values()];
2495
- return workspaceSyncInput(trigger, {
2549
+ const input = workspaceSyncInput(trigger, {
2496
2550
  ...overrides,
2497
2551
  ...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
2498
2552
  });
2553
+ synchronizeStartedAt = Date.now();
2554
+ recentWorkspaceSyncProof.invalidate();
2555
+ const observationGeneration = recentWorkspaceSyncProof.beginObservation();
2556
+ const workerWasBusyDuringObservation = workerBusy;
2557
+ let synchronized;
2558
+ try {
2559
+ synchronized = await synchronizeWorkspace(input);
2560
+ } finally {
2561
+ synchronizeFinishedAt = Date.now();
2562
+ }
2563
+ if ((synchronized.outcome === "no_change" || synchronized.outcome === "updated") && pendingTargets.length === 0) {
2564
+ recentWorkspaceSyncProof.recordStable({
2565
+ observationGeneration,
2566
+ canonicalHead: synchronized.publishedHead ?? null,
2567
+ scopeKey,
2568
+ observedAtMs: synchronizeFinishedAt,
2569
+ workerBusy: workerWasBusyDuringObservation || activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress || activeWorkspaceIncidentId !== null
2570
+ });
2571
+ }
2572
+ return synchronized;
2499
2573
  });
2500
- if (result.outcome === "no_change" || result.outcome === "published" || result.outcome === "updated" || result.outcome === "conflict_reset") {
2574
+ const completedAt = Date.now();
2575
+ const resultWithTelemetry = {
2576
+ ...result,
2577
+ telemetry: {
2578
+ totalMs: completedAt - requestedAt,
2579
+ queueMs: queueEnteredAt - requestedAt,
2580
+ prepareMs: synchronizeStartedAt - prepareStartedAt,
2581
+ synchronizeMs: synchronizeFinishedAt - synchronizeStartedAt,
2582
+ fastPath: workspaceSyncFastPathTelemetry(fastPathDecision)
2583
+ }
2584
+ };
2585
+ if (resultWithTelemetry.outcome === "no_change" || resultWithTelemetry.outcome === "published" || resultWithTelemetry.outcome === "updated" || resultWithTelemetry.outcome === "conflict_reset") {
2501
2586
  for (const target of pendingTargets) {
2502
2587
  const key = manifestCheckoutKey(target.projectId, target.branchName);
2503
2588
  if (pendingManifestCheckouts.get(key) === target) pendingManifestCheckouts.delete(key);
2504
2589
  }
2505
2590
  }
2506
2591
  previousPeriodicFingerprint = null;
2507
- sendWorkspaceSyncResult(authority, result);
2508
- return result;
2592
+ sendWorkspaceSyncResult(authority, resultWithTelemetry);
2593
+ return resultWithTelemetry;
2509
2594
  } catch (error) {
2595
+ const completedAt = Date.now();
2510
2596
  const result = {
2511
2597
  type: "workspace_sync",
2512
2598
  attemptId: overrides.attemptId ?? crypto.randomUUID(),
@@ -2521,6 +2607,13 @@ async function startWorker(options) {
2521
2607
  affectedPaths: [],
2522
2608
  discardedPaths: [],
2523
2609
  localChangesDiscarded: false,
2610
+ telemetry: {
2611
+ totalMs: completedAt - requestedAt,
2612
+ queueMs: queueEnteredAt - requestedAt,
2613
+ prepareMs: Math.max(0, synchronizeStartedAt - prepareStartedAt),
2614
+ synchronizeMs: Math.max(0, synchronizeFinishedAt - synchronizeStartedAt),
2615
+ fastPath: workspaceSyncFastPathTelemetry(fastPathDecision)
2616
+ },
2524
2617
  error: error instanceof Error ? error.message : String(error)
2525
2618
  };
2526
2619
  previousPeriodicFingerprint = null;
@@ -2596,6 +2689,7 @@ async function startWorker(options) {
2596
2689
  return;
2597
2690
  }
2598
2691
  if (message.type === "workspace_manifest") {
2692
+ recentWorkspaceSyncProof.invalidate();
2599
2693
  await workspaceSyncSingleFlight.runMutation(() => {
2600
2694
  configureGitHubAuth(message.githubCredential);
2601
2695
  visibleGitIdentity = message.gitIdentity;
@@ -2640,6 +2734,7 @@ async function startWorker(options) {
2640
2734
  (target) => genericCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
2641
2735
  );
2642
2736
  if (hasPendingGenericCheckout) {
2737
+ recentWorkspaceSyncProof.invalidate();
2643
2738
  previousPeriodicFingerprint = null;
2644
2739
  requestWorkspaceSync({
2645
2740
  type: "periodic",
@@ -2647,17 +2742,36 @@ async function startWorker(options) {
2647
2742
  });
2648
2743
  return;
2649
2744
  }
2650
- const fingerprint = await workspaceSyncSingleFlight.fingerprintPrepared(() => workspaceSyncInput({ type: "periodic" }));
2651
- if (fingerprint === emptyFingerprint) {
2745
+ const observation = await workspaceSyncSingleFlight.runExclusive(() => {
2746
+ const observationGeneration = recentWorkspaceSyncProof.beginObservation();
2747
+ const input = workspaceSyncInput({ type: "periodic" });
2748
+ const workerBusy = activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress;
2749
+ return {
2750
+ observationGeneration,
2751
+ scopeKey: workspaceSyncScopeKey(input.remoteUrl, input.projects),
2752
+ fingerprint: calculateWorkspaceDiffFingerprint(input),
2753
+ canonicalHead: readShadowWorkspaceCanonicalHead(workspaceShadowRoot),
2754
+ workerBusy
2755
+ };
2756
+ });
2757
+ if (observation.fingerprint === emptyFingerprint) {
2758
+ recentWorkspaceSyncProof.recordStable({
2759
+ observationGeneration: observation.observationGeneration,
2760
+ canonicalHead: observation.canonicalHead,
2761
+ scopeKey: observation.scopeKey,
2762
+ observedAtMs: Date.now(),
2763
+ workerBusy: observation.workerBusy || activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress
2764
+ });
2652
2765
  previousPeriodicFingerprint = null;
2653
2766
  return;
2654
2767
  }
2655
- if (previousPeriodicFingerprint !== fingerprint) {
2656
- previousPeriodicFingerprint = fingerprint;
2768
+ recentWorkspaceSyncProof.invalidate();
2769
+ if (previousPeriodicFingerprint !== observation.fingerprint) {
2770
+ previousPeriodicFingerprint = observation.fingerprint;
2657
2771
  return;
2658
2772
  }
2659
2773
  previousPeriodicFingerprint = null;
2660
- requestWorkspaceSync({ type: "periodic" }, fingerprint);
2774
+ requestWorkspaceSync({ type: "periodic" }, observation.fingerprint);
2661
2775
  })().catch((error) => {
2662
2776
  process.stderr.write(
2663
2777
  `[r5d-worker] periodic workspace synchronization failed: ${error instanceof Error ? error.message : String(error)}
@@ -2688,7 +2802,9 @@ async function startWorker(options) {
2688
2802
  confirmationReason: message.confirmationReason,
2689
2803
  resetToCanonical: message.resetToCanonical,
2690
2804
  skipVisibleMirror: message.skipVisibleMirror
2691
- }
2805
+ },
2806
+ message.canonicalHead,
2807
+ message.allowRecentNoChangeFastPath
2692
2808
  );
2693
2809
  return;
2694
2810
  }
@@ -2719,6 +2835,7 @@ async function startWorker(options) {
2719
2835
  return;
2720
2836
  }
2721
2837
  if (message.type === "workspace_incident_updated") {
2838
+ recentWorkspaceSyncProof.invalidate();
2722
2839
  const previousIncidentId = activeWorkspaceIncidentId;
2723
2840
  activeWorkspaceIncidentId = applyWorkspaceIncidentUpdate(activeWorkspaceIncidentId, message);
2724
2841
  previousPeriodicFingerprint = null;
@@ -2765,6 +2882,7 @@ async function startWorker(options) {
2765
2882
  return;
2766
2883
  }
2767
2884
  cliUpdateInProgress = true;
2885
+ recentWorkspaceSyncProof.invalidate();
2768
2886
  try {
2769
2887
  const result = await installCliUpdate(message);
2770
2888
  sendWorkerMessage(ws, {
@@ -2827,6 +2945,7 @@ async function startWorker(options) {
2827
2945
  });
2828
2946
  return;
2829
2947
  }
2948
+ recentWorkspaceSyncProof.invalidate();
2830
2949
  const releaseWorkspaceMutation = await acquireWorkspaceCommandMutation(message.target, workspaceSyncSingleFlight);
2831
2950
  let leaseTransferred = false;
2832
2951
  try {
@@ -2854,6 +2973,7 @@ async function startWorker(options) {
2854
2973
  return;
2855
2974
  }
2856
2975
  if (message.type === "pty_input") {
2976
+ recentWorkspaceSyncProof.invalidate();
2857
2977
  writePty(ws, message);
2858
2978
  return;
2859
2979
  }
@@ -2862,6 +2982,7 @@ async function startWorker(options) {
2862
2982
  return;
2863
2983
  }
2864
2984
  if (message.type === "pty_close") {
2985
+ recentWorkspaceSyncProof.invalidate();
2865
2986
  closePty(message);
2866
2987
  return;
2867
2988
  }
@@ -2876,6 +2997,7 @@ async function startWorker(options) {
2876
2997
  });
2877
2998
  return;
2878
2999
  }
3000
+ recentWorkspaceSyncProof.invalidate();
2879
3001
  let result;
2880
3002
  try {
2881
3003
  const runCommand = async () => {
@@ -2908,6 +3030,7 @@ async function startWorker(options) {
2908
3030
  });
2909
3031
  return;
2910
3032
  }
3033
+ recentWorkspaceSyncProof.invalidate();
2911
3034
  sendWorkerMessage(ws, {
2912
3035
  type: "exec_accepted",
2913
3036
  requestId: message.requestId,
@@ -2939,6 +3062,7 @@ async function startWorker(options) {
2939
3062
  return;
2940
3063
  }
2941
3064
  if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes") {
3065
+ if (message.type === "write" || message.type === "edit") recentWorkspaceSyncProof.invalidate();
2942
3066
  try {
2943
3067
  const result = await workspaceSyncSingleFlight.runMutation(async () => {
2944
3068
  const resolvedTarget = resolveMessageTarget(message.target);
@@ -2987,6 +3111,7 @@ async function startWorker(options) {
2987
3111
  });
2988
3112
  });
2989
3113
  ws.addEventListener("close", (event) => {
3114
+ recentWorkspaceSyncProof.invalidate();
2990
3115
  stopHeartbeatWatchdog();
2991
3116
  if (periodicWorkspaceScan) {
2992
3117
  clearInterval(periodicWorkspaceScan);
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.57",
3
+ "version": "0.0.58",
4
4
  "type": "module"
5
5
  }