@ricsam/r5d-worker 0.0.56 → 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.
- package/dist/cjs/main.cjs +133 -12
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/workspace-sync-fast-path.cjs +108 -0
- package/dist/cjs/workspace-sync.cjs +124 -53
- package/dist/mjs/main.mjs +137 -12
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/workspace-sync-fast-path.mjs +81 -0
- package/dist/mjs/workspace-sync.mjs +124 -53
- package/dist/types/workspace-sync-fast-path.d.ts +69 -0
- package/dist/types/workspace-sync.d.ts +13 -1
- package/package.json +1 -1
package/dist/cjs/main.cjs
CHANGED
|
@@ -65,10 +65,14 @@ var import_process_tree = require("./process-tree.cjs");
|
|
|
65
65
|
var import_port_forward_client = require("./port-forward-client.cjs");
|
|
66
66
|
var import_workspace_incident_state = require("./workspace-incident-state.cjs");
|
|
67
67
|
var import_workspace_command_sync_policy = require("./workspace-command-sync-policy.cjs");
|
|
68
|
+
var import_workspace_sync_fast_path = require("./workspace-sync-fast-path.cjs");
|
|
68
69
|
var import_supervisor = require("./supervisor.cjs");
|
|
69
70
|
var import_managed_paths = require("./managed-paths.cjs");
|
|
70
71
|
var import_workspace_sync = require("./workspace-sync.cjs");
|
|
71
72
|
const import_meta = {};
|
|
73
|
+
function workspaceSyncFastPathTelemetry(decision) {
|
|
74
|
+
return decision.hit ? { hit: true, reason: decision.reason, proofAgeMs: decision.proofAgeMs } : { hit: false, reason: decision.reason };
|
|
75
|
+
}
|
|
72
76
|
class WorkerServerUnavailableError extends Error {
|
|
73
77
|
name = "WorkerServerUnavailableError";
|
|
74
78
|
}
|
|
@@ -2407,6 +2411,7 @@ async function startWorker(options) {
|
|
|
2407
2411
|
let sessionArtifactSyncRequestsInFlight = 0;
|
|
2408
2412
|
let cliUpdateInProgress = false;
|
|
2409
2413
|
let reloadAfterClose = false;
|
|
2414
|
+
const recentWorkspaceSyncProof = new import_workspace_sync_fast_path.RecentWorkspaceSyncProofCache();
|
|
2410
2415
|
const workspaceSyncInput = (trigger, overrides = {}) => {
|
|
2411
2416
|
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2412
2417
|
const projects = (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], trigger);
|
|
@@ -2498,19 +2503,64 @@ async function startWorker(options) {
|
|
|
2498
2503
|
}
|
|
2499
2504
|
return created;
|
|
2500
2505
|
};
|
|
2501
|
-
const runRequestedWorkspaceSync = async (authority, trigger, overrides = {}) => {
|
|
2506
|
+
const runRequestedWorkspaceSync = async (authority, trigger, overrides = {}, authoritativeCanonicalHead, allowRecentNoChangeFastPath) => {
|
|
2507
|
+
const requestedAt = Date.now();
|
|
2508
|
+
let queueEnteredAt = requestedAt;
|
|
2509
|
+
let prepareStartedAt = requestedAt;
|
|
2510
|
+
let synchronizeStartedAt = requestedAt;
|
|
2511
|
+
let synchronizeFinishedAt = requestedAt;
|
|
2512
|
+
let fastPathDecision = { hit: false, reason: "no_recent_proof" };
|
|
2502
2513
|
workspaceSyncRequestsInFlight += 1;
|
|
2503
2514
|
try {
|
|
2504
2515
|
let pendingTargets = [];
|
|
2505
|
-
const result = await workspaceSyncSingleFlight.
|
|
2516
|
+
const result = await workspaceSyncSingleFlight.runExclusive(async () => {
|
|
2517
|
+
queueEnteredAt = Date.now();
|
|
2506
2518
|
const shouldEnsureCheckouts = !overrides.skipVisibleMirror && !overrides.resetToCanonical;
|
|
2507
2519
|
const syncProjects = (0, import_workspace_sync.workspaceProjectsForSync)([...manifestByProjectId.values()], trigger);
|
|
2520
|
+
if (!workspaceRemoteUrl) throw new Error("Worker workspace manifest has not been received");
|
|
2521
|
+
const scopeKey = (0, import_workspace_sync_fast_path.workspaceSyncScopeKey)(workspaceRemoteUrl, syncProjects);
|
|
2508
2522
|
const allowedCheckoutKeys = new Set(
|
|
2509
2523
|
syncProjects.flatMap((project) => project.branches.map((branchName) => manifestCheckoutKey(project.projectId, branchName)))
|
|
2510
2524
|
);
|
|
2511
2525
|
const scopedPendingTargets = [...pendingManifestCheckouts.values()].filter(
|
|
2512
2526
|
(target) => allowedCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
|
|
2513
2527
|
);
|
|
2528
|
+
const workerBusy = activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress;
|
|
2529
|
+
fastPathDecision = recentWorkspaceSyncProof.evaluate({
|
|
2530
|
+
trigger,
|
|
2531
|
+
allowRecentNoChangeFastPath,
|
|
2532
|
+
canonicalHead: authoritativeCanonicalHead,
|
|
2533
|
+
scopeKey,
|
|
2534
|
+
nowMs: Date.now(),
|
|
2535
|
+
skipVisibleMirror: overrides.skipVisibleMirror,
|
|
2536
|
+
resetToCanonical: overrides.resetToCanonical,
|
|
2537
|
+
incidentActive: activeWorkspaceIncidentId !== null,
|
|
2538
|
+
workerBusy,
|
|
2539
|
+
pendingCheckouts: scopedPendingTargets.length > 0 || Boolean(overrides.newVisibleCheckouts?.length)
|
|
2540
|
+
});
|
|
2541
|
+
if (fastPathDecision.hit) {
|
|
2542
|
+
prepareStartedAt = queueEnteredAt;
|
|
2543
|
+
synchronizeStartedAt = queueEnteredAt;
|
|
2544
|
+
synchronizeFinishedAt = queueEnteredAt;
|
|
2545
|
+
return {
|
|
2546
|
+
type: "workspace_sync",
|
|
2547
|
+
attemptId: overrides.attemptId ?? crypto.randomUUID(),
|
|
2548
|
+
workerLabel: label,
|
|
2549
|
+
trigger,
|
|
2550
|
+
outcome: "no_change",
|
|
2551
|
+
startingHead: fastPathDecision.canonicalHead,
|
|
2552
|
+
expectedHead: fastPathDecision.canonicalHead,
|
|
2553
|
+
publishedHead: fastPathDecision.canonicalHead,
|
|
2554
|
+
rebaseCount: 0,
|
|
2555
|
+
diffSizeBytes: 0,
|
|
2556
|
+
gitStatus: "",
|
|
2557
|
+
affectedProjects: [],
|
|
2558
|
+
affectedPaths: [],
|
|
2559
|
+
discardedPaths: [],
|
|
2560
|
+
localChangesDiscarded: false
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
prepareStartedAt = Date.now();
|
|
2514
2564
|
let checkoutTargets = [];
|
|
2515
2565
|
if (shouldEnsureCheckouts) {
|
|
2516
2566
|
checkoutTargets = workspaceSyncCheckoutTargets({
|
|
@@ -2530,21 +2580,53 @@ async function startWorker(options) {
|
|
|
2530
2580
|
if (allowedCheckoutKeys.has(key)) newVisibleCheckoutByKey.set(key, target);
|
|
2531
2581
|
}
|
|
2532
2582
|
const newVisibleCheckouts = [...newVisibleCheckoutByKey.values()];
|
|
2533
|
-
|
|
2583
|
+
const input = workspaceSyncInput(trigger, {
|
|
2534
2584
|
...overrides,
|
|
2535
2585
|
...newVisibleCheckouts.length > 0 ? { newVisibleCheckouts } : {}
|
|
2536
2586
|
});
|
|
2587
|
+
synchronizeStartedAt = Date.now();
|
|
2588
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2589
|
+
const observationGeneration = recentWorkspaceSyncProof.beginObservation();
|
|
2590
|
+
const workerWasBusyDuringObservation = workerBusy;
|
|
2591
|
+
let synchronized;
|
|
2592
|
+
try {
|
|
2593
|
+
synchronized = await (0, import_workspace_sync.synchronizeWorkspace)(input);
|
|
2594
|
+
} finally {
|
|
2595
|
+
synchronizeFinishedAt = Date.now();
|
|
2596
|
+
}
|
|
2597
|
+
if ((synchronized.outcome === "no_change" || synchronized.outcome === "updated") && pendingTargets.length === 0) {
|
|
2598
|
+
recentWorkspaceSyncProof.recordStable({
|
|
2599
|
+
observationGeneration,
|
|
2600
|
+
canonicalHead: synchronized.publishedHead ?? null,
|
|
2601
|
+
scopeKey,
|
|
2602
|
+
observedAtMs: synchronizeFinishedAt,
|
|
2603
|
+
workerBusy: workerWasBusyDuringObservation || activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress || activeWorkspaceIncidentId !== null
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
return synchronized;
|
|
2537
2607
|
});
|
|
2538
|
-
|
|
2608
|
+
const completedAt = Date.now();
|
|
2609
|
+
const resultWithTelemetry = {
|
|
2610
|
+
...result,
|
|
2611
|
+
telemetry: {
|
|
2612
|
+
totalMs: completedAt - requestedAt,
|
|
2613
|
+
queueMs: queueEnteredAt - requestedAt,
|
|
2614
|
+
prepareMs: synchronizeStartedAt - prepareStartedAt,
|
|
2615
|
+
synchronizeMs: synchronizeFinishedAt - synchronizeStartedAt,
|
|
2616
|
+
fastPath: workspaceSyncFastPathTelemetry(fastPathDecision)
|
|
2617
|
+
}
|
|
2618
|
+
};
|
|
2619
|
+
if (resultWithTelemetry.outcome === "no_change" || resultWithTelemetry.outcome === "published" || resultWithTelemetry.outcome === "updated" || resultWithTelemetry.outcome === "conflict_reset") {
|
|
2539
2620
|
for (const target of pendingTargets) {
|
|
2540
2621
|
const key = manifestCheckoutKey(target.projectId, target.branchName);
|
|
2541
2622
|
if (pendingManifestCheckouts.get(key) === target) pendingManifestCheckouts.delete(key);
|
|
2542
2623
|
}
|
|
2543
2624
|
}
|
|
2544
2625
|
previousPeriodicFingerprint = null;
|
|
2545
|
-
sendWorkspaceSyncResult(authority,
|
|
2546
|
-
return
|
|
2626
|
+
sendWorkspaceSyncResult(authority, resultWithTelemetry);
|
|
2627
|
+
return resultWithTelemetry;
|
|
2547
2628
|
} catch (error) {
|
|
2629
|
+
const completedAt = Date.now();
|
|
2548
2630
|
const result = {
|
|
2549
2631
|
type: "workspace_sync",
|
|
2550
2632
|
attemptId: overrides.attemptId ?? crypto.randomUUID(),
|
|
@@ -2559,6 +2641,13 @@ async function startWorker(options) {
|
|
|
2559
2641
|
affectedPaths: [],
|
|
2560
2642
|
discardedPaths: [],
|
|
2561
2643
|
localChangesDiscarded: false,
|
|
2644
|
+
telemetry: {
|
|
2645
|
+
totalMs: completedAt - requestedAt,
|
|
2646
|
+
queueMs: queueEnteredAt - requestedAt,
|
|
2647
|
+
prepareMs: Math.max(0, synchronizeStartedAt - prepareStartedAt),
|
|
2648
|
+
synchronizeMs: Math.max(0, synchronizeFinishedAt - synchronizeStartedAt),
|
|
2649
|
+
fastPath: workspaceSyncFastPathTelemetry(fastPathDecision)
|
|
2650
|
+
},
|
|
2562
2651
|
error: error instanceof Error ? error.message : String(error)
|
|
2563
2652
|
};
|
|
2564
2653
|
previousPeriodicFingerprint = null;
|
|
@@ -2634,6 +2723,7 @@ async function startWorker(options) {
|
|
|
2634
2723
|
return;
|
|
2635
2724
|
}
|
|
2636
2725
|
if (message.type === "workspace_manifest") {
|
|
2726
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2637
2727
|
await workspaceSyncSingleFlight.runMutation(() => {
|
|
2638
2728
|
configureGitHubAuth(message.githubCredential);
|
|
2639
2729
|
visibleGitIdentity = message.gitIdentity;
|
|
@@ -2678,6 +2768,7 @@ async function startWorker(options) {
|
|
|
2678
2768
|
(target) => genericCheckoutKeys.has(manifestCheckoutKey(target.projectId, target.branchName))
|
|
2679
2769
|
);
|
|
2680
2770
|
if (hasPendingGenericCheckout) {
|
|
2771
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2681
2772
|
previousPeriodicFingerprint = null;
|
|
2682
2773
|
requestWorkspaceSync({
|
|
2683
2774
|
type: "periodic",
|
|
@@ -2685,17 +2776,36 @@ async function startWorker(options) {
|
|
|
2685
2776
|
});
|
|
2686
2777
|
return;
|
|
2687
2778
|
}
|
|
2688
|
-
const
|
|
2689
|
-
|
|
2779
|
+
const observation = await workspaceSyncSingleFlight.runExclusive(() => {
|
|
2780
|
+
const observationGeneration = recentWorkspaceSyncProof.beginObservation();
|
|
2781
|
+
const input = workspaceSyncInput({ type: "periodic" });
|
|
2782
|
+
const workerBusy = activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress;
|
|
2783
|
+
return {
|
|
2784
|
+
observationGeneration,
|
|
2785
|
+
scopeKey: (0, import_workspace_sync_fast_path.workspaceSyncScopeKey)(input.remoteUrl, input.projects),
|
|
2786
|
+
fingerprint: (0, import_workspace_sync.calculateWorkspaceDiffFingerprint)(input),
|
|
2787
|
+
canonicalHead: (0, import_workspace_sync_fast_path.readShadowWorkspaceCanonicalHead)(workspaceShadowRoot),
|
|
2788
|
+
workerBusy
|
|
2789
|
+
};
|
|
2790
|
+
});
|
|
2791
|
+
if (observation.fingerprint === emptyFingerprint) {
|
|
2792
|
+
recentWorkspaceSyncProof.recordStable({
|
|
2793
|
+
observationGeneration: observation.observationGeneration,
|
|
2794
|
+
canonicalHead: observation.canonicalHead,
|
|
2795
|
+
scopeKey: observation.scopeKey,
|
|
2796
|
+
observedAtMs: Date.now(),
|
|
2797
|
+
workerBusy: observation.workerBusy || activeProcesses.size > 0 || activePtys.size > 0 || cliUpdateInProgress
|
|
2798
|
+
});
|
|
2690
2799
|
previousPeriodicFingerprint = null;
|
|
2691
2800
|
return;
|
|
2692
2801
|
}
|
|
2693
|
-
|
|
2694
|
-
|
|
2802
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2803
|
+
if (previousPeriodicFingerprint !== observation.fingerprint) {
|
|
2804
|
+
previousPeriodicFingerprint = observation.fingerprint;
|
|
2695
2805
|
return;
|
|
2696
2806
|
}
|
|
2697
2807
|
previousPeriodicFingerprint = null;
|
|
2698
|
-
requestWorkspaceSync({ type: "periodic" }, fingerprint);
|
|
2808
|
+
requestWorkspaceSync({ type: "periodic" }, observation.fingerprint);
|
|
2699
2809
|
})().catch((error) => {
|
|
2700
2810
|
process.stderr.write(
|
|
2701
2811
|
`[r5d-worker] periodic workspace synchronization failed: ${error instanceof Error ? error.message : String(error)}
|
|
@@ -2726,7 +2836,9 @@ async function startWorker(options) {
|
|
|
2726
2836
|
confirmationReason: message.confirmationReason,
|
|
2727
2837
|
resetToCanonical: message.resetToCanonical,
|
|
2728
2838
|
skipVisibleMirror: message.skipVisibleMirror
|
|
2729
|
-
}
|
|
2839
|
+
},
|
|
2840
|
+
message.canonicalHead,
|
|
2841
|
+
message.allowRecentNoChangeFastPath
|
|
2730
2842
|
);
|
|
2731
2843
|
return;
|
|
2732
2844
|
}
|
|
@@ -2757,6 +2869,7 @@ async function startWorker(options) {
|
|
|
2757
2869
|
return;
|
|
2758
2870
|
}
|
|
2759
2871
|
if (message.type === "workspace_incident_updated") {
|
|
2872
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2760
2873
|
const previousIncidentId = activeWorkspaceIncidentId;
|
|
2761
2874
|
activeWorkspaceIncidentId = (0, import_workspace_incident_state.applyWorkspaceIncidentUpdate)(activeWorkspaceIncidentId, message);
|
|
2762
2875
|
previousPeriodicFingerprint = null;
|
|
@@ -2803,6 +2916,7 @@ async function startWorker(options) {
|
|
|
2803
2916
|
return;
|
|
2804
2917
|
}
|
|
2805
2918
|
cliUpdateInProgress = true;
|
|
2919
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2806
2920
|
try {
|
|
2807
2921
|
const result = await (0, import_cli_update.installCliUpdate)(message);
|
|
2808
2922
|
sendWorkerMessage(ws, {
|
|
@@ -2865,6 +2979,7 @@ async function startWorker(options) {
|
|
|
2865
2979
|
});
|
|
2866
2980
|
return;
|
|
2867
2981
|
}
|
|
2982
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2868
2983
|
const releaseWorkspaceMutation = await (0, import_workspace_command_sync_policy.acquireWorkspaceCommandMutation)(message.target, workspaceSyncSingleFlight);
|
|
2869
2984
|
let leaseTransferred = false;
|
|
2870
2985
|
try {
|
|
@@ -2892,6 +3007,7 @@ async function startWorker(options) {
|
|
|
2892
3007
|
return;
|
|
2893
3008
|
}
|
|
2894
3009
|
if (message.type === "pty_input") {
|
|
3010
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2895
3011
|
writePty(ws, message);
|
|
2896
3012
|
return;
|
|
2897
3013
|
}
|
|
@@ -2900,6 +3016,7 @@ async function startWorker(options) {
|
|
|
2900
3016
|
return;
|
|
2901
3017
|
}
|
|
2902
3018
|
if (message.type === "pty_close") {
|
|
3019
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2903
3020
|
closePty(message);
|
|
2904
3021
|
return;
|
|
2905
3022
|
}
|
|
@@ -2914,6 +3031,7 @@ async function startWorker(options) {
|
|
|
2914
3031
|
});
|
|
2915
3032
|
return;
|
|
2916
3033
|
}
|
|
3034
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2917
3035
|
let result;
|
|
2918
3036
|
try {
|
|
2919
3037
|
const runCommand = async () => {
|
|
@@ -2946,6 +3064,7 @@ async function startWorker(options) {
|
|
|
2946
3064
|
});
|
|
2947
3065
|
return;
|
|
2948
3066
|
}
|
|
3067
|
+
recentWorkspaceSyncProof.invalidate();
|
|
2949
3068
|
sendWorkerMessage(ws, {
|
|
2950
3069
|
type: "exec_accepted",
|
|
2951
3070
|
requestId: message.requestId,
|
|
@@ -2977,6 +3096,7 @@ async function startWorker(options) {
|
|
|
2977
3096
|
return;
|
|
2978
3097
|
}
|
|
2979
3098
|
if (message.type === "read" || message.type === "write" || message.type === "edit" || message.type === "grep" || message.type === "find" || message.type === "ls" || message.type === "view_file_bytes") {
|
|
3099
|
+
if (message.type === "write" || message.type === "edit") recentWorkspaceSyncProof.invalidate();
|
|
2980
3100
|
try {
|
|
2981
3101
|
const result = await workspaceSyncSingleFlight.runMutation(async () => {
|
|
2982
3102
|
const resolvedTarget = resolveMessageTarget(message.target);
|
|
@@ -3025,6 +3145,7 @@ async function startWorker(options) {
|
|
|
3025
3145
|
});
|
|
3026
3146
|
});
|
|
3027
3147
|
ws.addEventListener("close", (event) => {
|
|
3148
|
+
recentWorkspaceSyncProof.invalidate();
|
|
3028
3149
|
stopHeartbeatWatchdog();
|
|
3029
3150
|
if (periodicWorkspaceScan) {
|
|
3030
3151
|
clearInterval(periodicWorkspaceScan);
|
package/dist/cjs/package.json
CHANGED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var workspace_sync_fast_path_exports = {};
|
|
20
|
+
__export(workspace_sync_fast_path_exports, {
|
|
21
|
+
RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS: () => RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS,
|
|
22
|
+
RecentWorkspaceSyncProofCache: () => RecentWorkspaceSyncProofCache,
|
|
23
|
+
readShadowWorkspaceCanonicalHead: () => readShadowWorkspaceCanonicalHead,
|
|
24
|
+
workspaceSyncScopeKey: () => workspaceSyncScopeKey
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(workspace_sync_fast_path_exports);
|
|
27
|
+
var import_node_crypto = require("node:crypto");
|
|
28
|
+
const RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS = 15e3;
|
|
29
|
+
function stableProjectShape(project) {
|
|
30
|
+
return {
|
|
31
|
+
projectId: project.projectId,
|
|
32
|
+
checkoutPathSegments: [...project.checkoutPathSegments],
|
|
33
|
+
projectPath: project.projectPath,
|
|
34
|
+
repoHttpUrl: project.repoHttpUrl,
|
|
35
|
+
defaultBranch: project.defaultBranch,
|
|
36
|
+
branches: [...project.branches].sort(),
|
|
37
|
+
canonicalCheckouts: [...project.canonicalCheckouts].sort(
|
|
38
|
+
(left, right) => left.branchName.localeCompare(right.branchName) || left.scaffoldCommitHash.localeCompare(right.scaffoldCommitHash)
|
|
39
|
+
)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function workspaceSyncScopeKey(remoteUrl, projects) {
|
|
43
|
+
const stableProjects = [...projects].map(stableProjectShape).sort((left, right) => left.projectId.localeCompare(right.projectId) || left.projectPath.localeCompare(right.projectPath));
|
|
44
|
+
return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify({ remoteUrl, projects: stableProjects })).digest("hex");
|
|
45
|
+
}
|
|
46
|
+
function readShadowWorkspaceCanonicalHead(shadowRoot) {
|
|
47
|
+
const result = Bun.spawnSync(["git", "rev-parse", "--verify", "HEAD"], {
|
|
48
|
+
cwd: shadowRoot,
|
|
49
|
+
stdout: "pipe",
|
|
50
|
+
stderr: "ignore"
|
|
51
|
+
});
|
|
52
|
+
if (result.exitCode !== 0) return null;
|
|
53
|
+
const head = result.stdout.toString().trim();
|
|
54
|
+
return /^[0-9a-f]{40,64}$/.test(head) ? head : null;
|
|
55
|
+
}
|
|
56
|
+
class RecentWorkspaceSyncProofCache {
|
|
57
|
+
constructor(maxAgeMs = RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS) {
|
|
58
|
+
this.maxAgeMs = maxAgeMs;
|
|
59
|
+
}
|
|
60
|
+
maxAgeMs;
|
|
61
|
+
generation = 0;
|
|
62
|
+
proof = null;
|
|
63
|
+
beginObservation() {
|
|
64
|
+
return this.generation;
|
|
65
|
+
}
|
|
66
|
+
invalidate() {
|
|
67
|
+
this.generation += 1;
|
|
68
|
+
}
|
|
69
|
+
recordStable(input) {
|
|
70
|
+
if (input.observationGeneration !== this.generation || input.workerBusy || !input.canonicalHead || !/^[0-9a-f]{40,64}$/.test(input.canonicalHead)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
this.proof = {
|
|
74
|
+
canonicalHead: input.canonicalHead,
|
|
75
|
+
scopeKey: input.scopeKey,
|
|
76
|
+
observedAtMs: input.observedAtMs,
|
|
77
|
+
generation: this.generation
|
|
78
|
+
};
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
evaluate(input) {
|
|
82
|
+
if (!input.allowRecentNoChangeFastPath || input.trigger.type !== "inbound_head") {
|
|
83
|
+
return { hit: false, reason: "unsupported_trigger" };
|
|
84
|
+
}
|
|
85
|
+
if (input.trigger.canonicalCheckoutOnly) return { hit: false, reason: "canonical_checkout" };
|
|
86
|
+
if (input.skipVisibleMirror) return { hit: false, reason: "visible_mirror_skipped" };
|
|
87
|
+
if (input.resetToCanonical) return { hit: false, reason: "reset_requested" };
|
|
88
|
+
if (input.incidentActive) return { hit: false, reason: "incident_active" };
|
|
89
|
+
if (input.workerBusy) return { hit: false, reason: "worker_busy" };
|
|
90
|
+
if (input.pendingCheckouts) return { hit: false, reason: "pending_checkouts" };
|
|
91
|
+
if (!input.canonicalHead) return { hit: false, reason: "canonical_head_missing" };
|
|
92
|
+
const proof = this.proof;
|
|
93
|
+
if (!proof) return { hit: false, reason: "no_recent_proof" };
|
|
94
|
+
if (proof.generation !== this.generation) return { hit: false, reason: "proof_invalidated" };
|
|
95
|
+
const proofAgeMs = Math.max(0, input.nowMs - proof.observedAtMs);
|
|
96
|
+
if (proofAgeMs > this.maxAgeMs) return { hit: false, reason: "proof_expired" };
|
|
97
|
+
if (proof.scopeKey !== input.scopeKey) return { hit: false, reason: "scope_changed" };
|
|
98
|
+
if (proof.canonicalHead !== input.canonicalHead) return { hit: false, reason: "canonical_head_changed" };
|
|
99
|
+
return { hit: true, reason: "recent_stable_no_change", canonicalHead: proof.canonicalHead, proofAgeMs };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
103
|
+
0 && (module.exports = {
|
|
104
|
+
RECENT_WORKSPACE_SYNC_PROOF_MAX_AGE_MS,
|
|
105
|
+
RecentWorkspaceSyncProofCache,
|
|
106
|
+
readShadowWorkspaceCanonicalHead,
|
|
107
|
+
workspaceSyncScopeKey
|
|
108
|
+
});
|