@ricsam/r5d-worker 0.0.58 → 0.0.59

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.58",
3
+ "version": "0.0.59",
4
4
  "type": "commonjs"
5
5
  }
@@ -0,0 +1,320 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var workspace_convergence_exports = {};
30
+ __export(workspace_convergence_exports, {
31
+ ExplicitWorkspaceCheckpointQueue: () => ExplicitWorkspaceCheckpointQueue,
32
+ WORKSPACE_CHECKPOINT_QUIET_MS: () => WORKSPACE_CHECKPOINT_QUIET_MS,
33
+ WORKSPACE_IDLE_SAFETY_SCAN_MS: () => WORKSPACE_IDLE_SAFETY_SCAN_MS,
34
+ WorkspaceConvergenceState: () => WorkspaceConvergenceState,
35
+ WorkspaceFilesystemWriterGate: () => WorkspaceFilesystemWriterGate,
36
+ WorkspaceManifestRevisionFence: () => WorkspaceManifestRevisionFence,
37
+ processWorkspaceConvergenceState: () => processWorkspaceConvergenceState,
38
+ processWorkspaceFilesystemWriterGate: () => processWorkspaceFilesystemWriterGate,
39
+ readWorkspaceConvergenceState: () => readWorkspaceConvergenceState,
40
+ waitForWorkspaceCheckpointOnShutdown: () => waitForWorkspaceCheckpointOnShutdown,
41
+ workspaceCheckpointIncidentAction: () => workspaceCheckpointIncidentAction,
42
+ workspaceCheckpointTelemetry: () => workspaceCheckpointTelemetry
43
+ });
44
+ module.exports = __toCommonJS(workspace_convergence_exports);
45
+ var import_node_fs = __toESM(require("node:fs"), 1);
46
+ var import_node_path = __toESM(require("node:path"), 1);
47
+ const WORKSPACE_CHECKPOINT_QUIET_MS = 5e3;
48
+ const WORKSPACE_IDLE_SAFETY_SCAN_MS = 6e4;
49
+ const WORKSPACE_MANIFEST_REVISION_PATTERN = /^[0-9a-f]{64}$/;
50
+ class WorkspaceFilesystemWriterGate {
51
+ activeWriters = 0;
52
+ checkpointActive = false;
53
+ waitingWriters = [];
54
+ waitingCheckpoints = [];
55
+ acquireWriter() {
56
+ if (!this.checkpointActive && this.waitingCheckpoints.length === 0) {
57
+ this.activeWriters += 1;
58
+ return Promise.resolve(this.writerRelease());
59
+ }
60
+ return new Promise((resolve) => this.waitingWriters.push(resolve));
61
+ }
62
+ tryAcquireCheckpoint() {
63
+ if (this.checkpointActive || this.activeWriters > 0 || this.waitingCheckpoints.length > 0) return null;
64
+ this.checkpointActive = true;
65
+ return this.checkpointRelease();
66
+ }
67
+ acquireCheckpoint() {
68
+ const immediate = this.tryAcquireCheckpoint();
69
+ if (immediate) return Promise.resolve(immediate);
70
+ return new Promise((resolve) => this.waitingCheckpoints.push(resolve));
71
+ }
72
+ checkpointRelease() {
73
+ let released = false;
74
+ return () => {
75
+ if (released) return;
76
+ released = true;
77
+ this.checkpointActive = false;
78
+ this.drain();
79
+ };
80
+ }
81
+ writerRelease() {
82
+ let released = false;
83
+ return () => {
84
+ if (released) return;
85
+ released = true;
86
+ this.activeWriters -= 1;
87
+ this.drain();
88
+ };
89
+ }
90
+ drain() {
91
+ if (this.checkpointActive || this.activeWriters > 0) return;
92
+ const checkpoint = this.waitingCheckpoints.shift();
93
+ if (checkpoint) {
94
+ this.checkpointActive = true;
95
+ checkpoint(this.checkpointRelease());
96
+ return;
97
+ }
98
+ while (this.waitingWriters.length > 0) {
99
+ const resolve = this.waitingWriters.shift();
100
+ if (!resolve) continue;
101
+ this.activeWriters += 1;
102
+ resolve(this.writerRelease());
103
+ }
104
+ }
105
+ }
106
+ const PROCESS_WORKSPACE_FILESYSTEM_WRITERS = new WorkspaceFilesystemWriterGate();
107
+ function processWorkspaceFilesystemWriterGate() {
108
+ return PROCESS_WORKSPACE_FILESYSTEM_WRITERS;
109
+ }
110
+ class WorkspaceManifestRevisionFence {
111
+ sequence = 0;
112
+ requestedRevision = null;
113
+ completedRevision = null;
114
+ begin(revision) {
115
+ if (!WORKSPACE_MANIFEST_REVISION_PATTERN.test(revision)) {
116
+ throw new Error("Workspace manifest revision must be a lowercase SHA-256 hash");
117
+ }
118
+ this.sequence += 1;
119
+ this.requestedRevision = revision;
120
+ this.completedRevision = null;
121
+ return { sequence: this.sequence, revision };
122
+ }
123
+ isCurrent(token) {
124
+ return token.sequence === this.sequence && token.revision === this.requestedRevision;
125
+ }
126
+ complete(token) {
127
+ if (!this.isCurrent(token)) return false;
128
+ this.completedRevision = token.revision;
129
+ return true;
130
+ }
131
+ readyRevision() {
132
+ return this.completedRevision === this.requestedRevision ? this.completedRevision : null;
133
+ }
134
+ }
135
+ function workspaceCheckpointTelemetry(input) {
136
+ return {
137
+ totalMs: Math.max(0, input.finishedAt - input.requestedAt),
138
+ queueMs: Math.max(0, input.prepareStartedAt - input.requestedAt),
139
+ prepareMs: Math.max(0, input.synchronizeStartedAt - input.prepareStartedAt),
140
+ synchronizeMs: Math.max(0, input.finishedAt - input.synchronizeStartedAt)
141
+ };
142
+ }
143
+ function workspaceCheckpointIncidentAction(requestId) {
144
+ return requestId ? "request_terminal_authority" : "defer_automatic";
145
+ }
146
+ class ExplicitWorkspaceCheckpointQueue {
147
+ scheduled;
148
+ queued = [];
149
+ schedule(checkpoint, pendingRequestId) {
150
+ if (pendingRequestId === checkpoint.requestId || this.scheduled?.requestId === checkpoint.requestId || this.queued.some((queued) => queued.requestId === checkpoint.requestId)) {
151
+ return false;
152
+ }
153
+ if (pendingRequestId || this.scheduled) {
154
+ this.queued.push(checkpoint);
155
+ return false;
156
+ }
157
+ this.scheduled = checkpoint;
158
+ return true;
159
+ }
160
+ hasScheduled() {
161
+ return this.scheduled !== void 0;
162
+ }
163
+ consumeScheduled(requestId) {
164
+ if (this.scheduled?.requestId === requestId) this.scheduled = void 0;
165
+ }
166
+ enqueue(checkpoint, pendingRequestId) {
167
+ if (pendingRequestId === checkpoint.requestId || this.scheduled?.requestId === checkpoint.requestId || this.queued.some((queued) => queued.requestId === checkpoint.requestId)) {
168
+ return;
169
+ }
170
+ this.queued.push(checkpoint);
171
+ }
172
+ next() {
173
+ return this.queued.shift();
174
+ }
175
+ }
176
+ const EMPTY_STATE = {
177
+ localHead: null,
178
+ desiredCanonicalHead: null,
179
+ dirtyGeneration: 0,
180
+ publishedGeneration: 0
181
+ };
182
+ function validHead(value) {
183
+ return value === null || typeof value === "string" && /^[0-9a-f]{40,64}$/.test(value);
184
+ }
185
+ function validGeneration(value) {
186
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
187
+ }
188
+ function readWorkspaceConvergenceState(statePath) {
189
+ try {
190
+ const parsed = JSON.parse(import_node_fs.default.readFileSync(statePath, "utf8"));
191
+ if (!validHead(parsed.localHead) || !validHead(parsed.desiredCanonicalHead) || !validGeneration(parsed.dirtyGeneration) || !validGeneration(parsed.publishedGeneration) || parsed.publishedGeneration > parsed.dirtyGeneration) {
192
+ return { ...EMPTY_STATE };
193
+ }
194
+ return {
195
+ localHead: parsed.localHead,
196
+ desiredCanonicalHead: parsed.desiredCanonicalHead,
197
+ dirtyGeneration: parsed.dirtyGeneration,
198
+ publishedGeneration: parsed.publishedGeneration
199
+ };
200
+ } catch {
201
+ return { ...EMPTY_STATE };
202
+ }
203
+ }
204
+ class WorkspaceConvergenceState {
205
+ constructor(statePath) {
206
+ this.statePath = statePath;
207
+ this.state = readWorkspaceConvergenceState(statePath);
208
+ this.checkpointState = this.state.dirtyGeneration > this.state.publishedGeneration ? "scheduled" : "idle";
209
+ }
210
+ statePath;
211
+ state;
212
+ checkpointState;
213
+ snapshot() {
214
+ return { ...this.state, checkpointState: this.checkpointState };
215
+ }
216
+ setLocalHead(localHead) {
217
+ if (!validHead(localHead)) throw new Error("Invalid local workspace head");
218
+ this.state.localHead = localHead;
219
+ this.persist();
220
+ }
221
+ observeCanonicalHead(canonicalHead) {
222
+ if (!validHead(canonicalHead)) throw new Error("Invalid canonical workspace head");
223
+ if (this.state.desiredCanonicalHead === canonicalHead) return false;
224
+ this.state.desiredCanonicalHead = canonicalHead;
225
+ this.persist();
226
+ return true;
227
+ }
228
+ markDirty() {
229
+ this.state.dirtyGeneration += 1;
230
+ if (this.checkpointState === "idle") this.checkpointState = "scheduled";
231
+ this.persist();
232
+ return this.state.dirtyGeneration;
233
+ }
234
+ schedule() {
235
+ if (this.checkpointState === "idle") this.checkpointState = "scheduled";
236
+ }
237
+ beginPreparing() {
238
+ if (this.checkpointState === "preparing" || this.checkpointState === "submitting") {
239
+ throw new Error("A workspace checkpoint is already in flight");
240
+ }
241
+ this.checkpointState = "preparing";
242
+ }
243
+ beginSubmitting() {
244
+ if (this.checkpointState !== "preparing") throw new Error("Workspace checkpoint authority was not prepared");
245
+ this.checkpointState = "submitting";
246
+ }
247
+ defer() {
248
+ this.checkpointState = "scheduled";
249
+ }
250
+ block() {
251
+ this.checkpointState = "blocked";
252
+ }
253
+ releaseBlock() {
254
+ this.checkpointState = this.state.dirtyGeneration > this.state.publishedGeneration ? "scheduled" : "idle";
255
+ }
256
+ observeIncidentState(blocked) {
257
+ if (this.checkpointState === "preparing" || this.checkpointState === "submitting") return;
258
+ if (blocked) this.block();
259
+ else this.releaseBlock();
260
+ }
261
+ resetTransientCheckpointState() {
262
+ this.checkpointState = this.state.dirtyGeneration > this.state.publishedGeneration ? "scheduled" : "idle";
263
+ }
264
+ complete(input) {
265
+ if (!validGeneration(input.generation) || input.generation > this.state.dirtyGeneration) {
266
+ throw new Error("Invalid completed workspace generation");
267
+ }
268
+ if (!validHead(input.canonicalHead)) throw new Error("Invalid completed canonical workspace head");
269
+ if (input.published) {
270
+ this.state.publishedGeneration = Math.max(this.state.publishedGeneration, input.generation);
271
+ this.state.localHead = input.canonicalHead;
272
+ this.state.desiredCanonicalHead = input.canonicalHead;
273
+ }
274
+ this.checkpointState = this.state.dirtyGeneration > this.state.publishedGeneration ? "scheduled" : "idle";
275
+ this.persist();
276
+ }
277
+ persist() {
278
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(this.statePath), { recursive: true });
279
+ const temporaryPath = `${this.statePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
280
+ import_node_fs.default.writeFileSync(temporaryPath, `${JSON.stringify(this.state)}
281
+ `, { mode: 384 });
282
+ import_node_fs.default.renameSync(temporaryPath, this.statePath);
283
+ }
284
+ }
285
+ const PROCESS_WORKSPACE_CONVERGENCE_STATES = /* @__PURE__ */ new Map();
286
+ function processWorkspaceConvergenceState(statePath) {
287
+ const key = import_node_path.default.resolve(statePath);
288
+ const existing = PROCESS_WORKSPACE_CONVERGENCE_STATES.get(key);
289
+ if (existing) return existing;
290
+ const created = new WorkspaceConvergenceState(key);
291
+ PROCESS_WORKSPACE_CONVERGENCE_STATES.set(key, created);
292
+ return created;
293
+ }
294
+ async function waitForWorkspaceCheckpointOnShutdown(input) {
295
+ input.forceCheckpoint();
296
+ const deadline = Date.now() + (input.timeoutMs ?? 1e4);
297
+ const pollMs = input.pollMs ?? 50;
298
+ while (Date.now() < deadline) {
299
+ const snapshot = input.snapshot();
300
+ if (snapshot.checkpointState === "blocked") return "blocked";
301
+ if (snapshot.checkpointState === "idle" && snapshot.publishedGeneration >= snapshot.dirtyGeneration) return "settled";
302
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
303
+ }
304
+ return "timed_out";
305
+ }
306
+ // Annotate the CommonJS export names for ESM import in node:
307
+ 0 && (module.exports = {
308
+ ExplicitWorkspaceCheckpointQueue,
309
+ WORKSPACE_CHECKPOINT_QUIET_MS,
310
+ WORKSPACE_IDLE_SAFETY_SCAN_MS,
311
+ WorkspaceConvergenceState,
312
+ WorkspaceFilesystemWriterGate,
313
+ WorkspaceManifestRevisionFence,
314
+ processWorkspaceConvergenceState,
315
+ processWorkspaceFilesystemWriterGate,
316
+ readWorkspaceConvergenceState,
317
+ waitForWorkspaceCheckpointOnShutdown,
318
+ workspaceCheckpointIncidentAction,
319
+ workspaceCheckpointTelemetry
320
+ });
@@ -18,22 +18,51 @@ 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
+ WorkspaceIncidentOrderingFence: () => WorkspaceIncidentOrderingFence,
21
22
  applyWorkspaceIncidentUpdate: () => applyWorkspaceIncidentUpdate,
22
23
  releasePendingWorkspaceHead: () => releasePendingWorkspaceHead
23
24
  });
24
25
  module.exports = __toCommonJS(workspace_incident_state_exports);
26
+ const DEFAULT_TERMINAL_INCIDENT_HISTORY_LIMIT = 32;
27
+ function isTerminalWorkspaceIncidentUpdate(update) {
28
+ return update.incidentId !== null && (update.status === "resolved" || update.status === "confirmed" || update.status === "reset");
29
+ }
30
+ class WorkspaceIncidentOrderingFence {
31
+ constructor(historyLimit = DEFAULT_TERMINAL_INCIDENT_HISTORY_LIMIT) {
32
+ this.historyLimit = historyLimit;
33
+ if (!Number.isSafeInteger(historyLimit) || historyLimit < 1) {
34
+ throw new Error("Workspace incident history limit must be a positive integer");
35
+ }
36
+ }
37
+ historyLimit;
38
+ terminalIncidentIds = /* @__PURE__ */ new Set();
39
+ terminalIncidentOrder = [];
40
+ observe(update) {
41
+ if (!isTerminalWorkspaceIncidentUpdate(update) || this.terminalIncidentIds.has(update.incidentId)) return;
42
+ this.terminalIncidentIds.add(update.incidentId);
43
+ this.terminalIncidentOrder.push(update.incidentId);
44
+ while (this.terminalIncidentOrder.length > this.historyLimit) {
45
+ const expired = this.terminalIncidentOrder.shift();
46
+ if (expired) this.terminalIncidentIds.delete(expired);
47
+ }
48
+ }
49
+ permitsBlockedResponse(incidentId) {
50
+ if (!incidentId) return false;
51
+ return !this.terminalIncidentIds.has(incidentId);
52
+ }
53
+ }
25
54
  function applyWorkspaceIncidentUpdate(currentIncidentId, update) {
26
55
  if (update.status === "remediating" || update.status === "waiting_for_worker") return update.incidentId;
27
56
  if (currentIncidentId && update.incidentId && update.incidentId !== currentIncidentId) return currentIncidentId;
28
57
  return null;
29
58
  }
30
59
  function releasePendingWorkspaceHead(previousIncidentId, currentIncidentId, terminalIncidentId) {
31
- if (!previousIncidentId || currentIncidentId) return { syncHead: null };
32
- if (terminalIncidentId === previousIncidentId) return { syncHead: "authoritative" };
33
- return { syncHead: null };
60
+ if (!previousIncidentId || currentIncidentId) return { fetchAuthoritativeHead: false };
61
+ return { fetchAuthoritativeHead: terminalIncidentId === previousIncidentId };
34
62
  }
35
63
  // Annotate the CommonJS export names for ESM import in node:
36
64
  0 && (module.exports = {
65
+ WorkspaceIncidentOrderingFence,
37
66
  applyWorkspaceIncidentUpdate,
38
67
  releasePendingWorkspaceHead
39
68
  });
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var workspace_manifest_admission_exports = {};
30
+ __export(workspace_manifest_admission_exports, {
31
+ acquireWorkspaceManifestVisibleLease: () => acquireWorkspaceManifestVisibleLease,
32
+ inspectWorkspaceManifestAdmission: () => inspectWorkspaceManifestAdmission
33
+ });
34
+ module.exports = __toCommonJS(workspace_manifest_admission_exports);
35
+ var import_node_fs = __toESM(require("node:fs"), 1);
36
+ var import_node_path = __toESM(require("node:path"), 1);
37
+ var import_managed_paths = require("./managed-paths.cjs");
38
+ function hasNormalGitDirectory(checkoutPath) {
39
+ const gitPath = import_node_path.default.join(checkoutPath, ".git");
40
+ return import_node_fs.default.existsSync(gitPath) && import_node_fs.default.statSync(gitPath).isDirectory();
41
+ }
42
+ function inspectWorkspaceManifestAdmission(input) {
43
+ const firstBootstrap = !hasNormalGitDirectory(input.workspaceShadowRoot);
44
+ const missingCheckouts = [];
45
+ const migratedProjectIds = [];
46
+ for (const project of input.projects) {
47
+ const projectRoot = (0, import_managed_paths.managedProjectRoot)(input.projectsRoot, project.checkoutPathSegments);
48
+ const legacyRoot = import_node_path.default.join(input.projectsRoot, (0, import_managed_paths.legacyProjectSlug)(project.projectPath, project.projectId));
49
+ if (import_node_fs.default.existsSync(legacyRoot)) migratedProjectIds.push(project.projectId);
50
+ for (const branchName of project.branches) {
51
+ if (!hasNormalGitDirectory(import_node_path.default.join(projectRoot, branchName))) {
52
+ missingCheckouts.push({ projectId: project.projectId, branchName });
53
+ }
54
+ }
55
+ }
56
+ return {
57
+ firstBootstrap,
58
+ missingCheckouts,
59
+ migratedProjectIds,
60
+ requiresVisibleLease: firstBootstrap || migratedProjectIds.length > 0
61
+ };
62
+ }
63
+ async function acquireWorkspaceManifestVisibleLease(gate, admission) {
64
+ return admission.requiresVisibleLease ? await gate.acquireCheckpoint() : void 0;
65
+ }
66
+ // Annotate the CommonJS export names for ESM import in node:
67
+ 0 && (module.exports = {
68
+ acquireWorkspaceManifestVisibleLease,
69
+ inspectWorkspaceManifestAdmission
70
+ });
@@ -32,11 +32,13 @@ __export(workspace_sync_exports, {
32
32
  WORKSPACE_BRANCH: () => WORKSPACE_BRANCH,
33
33
  WORKSPACE_INTENT_REF_PREFIX: () => WORKSPACE_INTENT_REF_PREFIX,
34
34
  WORKSPACE_NATIVE_PLANS_RELATIVE_PATH: () => WORKSPACE_NATIVE_PLANS_RELATIVE_PATH,
35
- WORKSPACE_PERIODIC_SCAN_INTERVAL_MS: () => WORKSPACE_PERIODIC_SCAN_INTERVAL_MS,
35
+ WORKSPACE_PUBLICATION_REF_PREFIX: () => WORKSPACE_PUBLICATION_REF_PREFIX,
36
36
  WorkspaceSyncSingleFlight: () => WorkspaceSyncSingleFlight,
37
37
  assertCanonicalCheckoutIndexMatchesWorktree: () => assertCanonicalCheckoutIndexMatchesWorktree,
38
38
  assertWorkspaceQuarantineRef: () => assertWorkspaceQuarantineRef,
39
39
  calculateWorkspaceDiffFingerprint: () => calculateWorkspaceDiffFingerprint,
40
+ calculateWorkspaceLocalDiffFingerprint: () => calculateWorkspaceLocalDiffFingerprint,
41
+ convergeWorkspaceHead: () => convergeWorkspaceHead,
40
42
  encodeWorkspaceBranch: () => encodeWorkspaceBranch,
41
43
  mirrorShadowWorkspaceToVisible: () => mirrorShadowWorkspaceToVisible,
42
44
  mirrorVisibleWorkspaceToShadow: () => mirrorVisibleWorkspaceToShadow,
@@ -54,8 +56,8 @@ var import_managed_paths = require("./managed-paths.cjs");
54
56
  var import_workspace_mutation_gate = require("./workspace-mutation-gate.cjs");
55
57
  const WORKSPACE_BRANCH = "main";
56
58
  const WORKSPACE_INTENT_REF_PREFIX = "refs/r5d/workspace-intents/";
59
+ const WORKSPACE_PUBLICATION_REF_PREFIX = "refs/r5d/workspace-publications/";
57
60
  const MAX_WORKSPACE_SYNC_DIFF_BYTES = 5 * 1024 * 1024;
58
- const WORKSPACE_PERIODIC_SCAN_INTERVAL_MS = 5e3;
59
61
  const MAX_REPORTED_WORKSPACE_PATHS = 2e3;
60
62
  const MAX_REPORTED_WORKSPACE_STATUS_BYTES = 256 * 1024;
61
63
  const MAX_MIRROR_COMPARISON_CACHE_ENTRIES = 2e5;
@@ -1754,9 +1756,12 @@ function commitMessage(input) {
1754
1756
  });
1755
1757
  }
1756
1758
  function assertWorkspaceQuarantineRef(quarantineRef) {
1757
- const suffix = quarantineRef.slice(WORKSPACE_INTENT_REF_PREFIX.length);
1758
- if (!quarantineRef.startsWith(WORKSPACE_INTENT_REF_PREFIX) || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(suffix)) {
1759
- throw new Error(`Workspace candidate ref must be below ${WORKSPACE_INTENT_REF_PREFIX}`);
1759
+ const prefix = quarantineRef.startsWith(WORKSPACE_PUBLICATION_REF_PREFIX) ? WORKSPACE_PUBLICATION_REF_PREFIX : WORKSPACE_INTENT_REF_PREFIX;
1760
+ const suffix = quarantineRef.slice(prefix.length);
1761
+ if (!quarantineRef.startsWith(prefix) || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(suffix)) {
1762
+ throw new Error(
1763
+ `Workspace candidate ref must be below ${WORKSPACE_PUBLICATION_REF_PREFIX} or ${WORKSPACE_INTENT_REF_PREFIX}`
1764
+ );
1760
1765
  }
1761
1766
  const result = Bun.spawnSync(["git", "check-ref-format", quarantineRef], {
1762
1767
  stdout: "pipe",
@@ -1876,6 +1881,36 @@ function preserveWorkspaceConflictCandidate(input, observed, options) {
1876
1881
  };
1877
1882
  }
1878
1883
  }
1884
+ function preserveLargeDiffCandidate(input, observed, error) {
1885
+ try {
1886
+ if (stagedPaths(input).length > 0) {
1887
+ runGit(input, input.shadowRoot, ["commit", "-m", commitMessage(input)], "commit blocked workspace candidate");
1888
+ }
1889
+ const candidateHead = revParse(input, "HEAD");
1890
+ if (!candidateHead) throw new Error("the blocked workspace candidate does not have a commit");
1891
+ assertUnscopedCanonicalCheckoutSubtreesMatchRemote(input, candidateHead);
1892
+ const upload = uploadWorkspaceCandidate(input, candidateHead);
1893
+ if (upload.exitCode !== 0) {
1894
+ throw new Error(upload.stderr || upload.stdout || "blocked workspace candidate upload failed");
1895
+ }
1896
+ return {
1897
+ ...observed,
1898
+ outcome: "large_diff_blocked",
1899
+ expectedHead: revParse(input, `origin/${WORKSPACE_BRANCH}`),
1900
+ candidateHead,
1901
+ quarantineRef: input.quarantineRef,
1902
+ error,
1903
+ gitStatus: gitStatus(input)
1904
+ };
1905
+ } catch (candidateError) {
1906
+ return {
1907
+ ...observed,
1908
+ outcome: "failed",
1909
+ error: `Workspace safety guard blocked publication but could not preserve a durable candidate: ${candidateError instanceof Error ? candidateError.message : String(candidateError)}`,
1910
+ gitStatus: gitStatus(input)
1911
+ };
1912
+ }
1913
+ }
1879
1914
  async function synchronizeWorkspace(rawInput) {
1880
1915
  let input = { ...rawInput, attemptId: rawInput.attemptId ?? crypto.randomUUID() };
1881
1916
  try {
@@ -1887,6 +1922,15 @@ async function synchronizeWorkspace(rawInput) {
1887
1922
  let hydrationPreimages = input.skipVisibleMirror ? hydrationPreimagesFromShadowRevision(input, shadowWasCreated ? null : revParse(input, "HEAD")) : /* @__PURE__ */ new Map();
1888
1923
  const remoteHeadAtStart = revParse(input, `origin/${WORKSPACE_BRANCH}`);
1889
1924
  const result = baseResult(input, remoteHeadAtStart);
1925
+ if (input.expectedCanonicalHead !== void 0 && remoteHeadAtStart !== input.expectedCanonicalHead) {
1926
+ return {
1927
+ ...result,
1928
+ outcome: "failed",
1929
+ expectedHead: input.expectedCanonicalHead,
1930
+ publishedHead: remoteHeadAtStart ?? void 0,
1931
+ error: `canonical_head_advanced: expected ${input.expectedCanonicalHead ?? "unborn"}, fetched ${remoteHeadAtStart ?? "unborn"}`
1932
+ };
1933
+ }
1890
1934
  const inboundConflictsAtStart = input.skipVisibleMirror && !isCanonicalRemediationSync && remoteHeadAtStart ? visibleHydrationConflictPaths(input, hydrationPreimages, `origin/${WORKSPACE_BRANCH}`) : [];
1891
1935
  if (input.resetToCanonical) {
1892
1936
  if (input.trigger.canonicalCheckoutOnly) {
@@ -1935,18 +1979,18 @@ async function synchronizeWorkspace(rawInput) {
1935
1979
  const destructiveReductions = destructiveCheckoutReductions(input, baseRevision);
1936
1980
  if (destructiveReductions.length > 0 && !input.confirmedLargeDiff) {
1937
1981
  const summary = destructiveReductions.map(({ root, trackedBefore, trackedAfter }) => `${root} (${trackedBefore} -> ${trackedAfter} tracked files)`).join(", ");
1938
- return {
1939
- ...observed,
1940
- outcome: "large_diff_blocked",
1941
- error: `Workspace safety guard blocked a destructive checkout reduction: ${summary}`
1942
- };
1982
+ return preserveLargeDiffCandidate(
1983
+ input,
1984
+ observed,
1985
+ `Workspace safety guard blocked a destructive checkout reduction: ${summary}`
1986
+ );
1943
1987
  }
1944
1988
  if (paths.length > 0 && diffSizeBytes > MAX_WORKSPACE_SYNC_DIFF_BYTES && !input.confirmedLargeDiff && !input.trigger.canonicalCheckoutOnly) {
1945
- return {
1946
- ...observed,
1947
- outcome: "large_diff_blocked",
1948
- error: `Workspace safety guard blocked a ${diffSizeBytes}-byte diff above the ${MAX_WORKSPACE_SYNC_DIFF_BYTES}-byte automatic publication limit.`
1949
- };
1989
+ return preserveLargeDiffCandidate(
1990
+ input,
1991
+ observed,
1992
+ `Workspace safety guard blocked a ${diffSizeBytes}-byte diff above the ${MAX_WORKSPACE_SYNC_DIFF_BYTES}-byte automatic publication limit.`
1993
+ );
1950
1994
  }
1951
1995
  if (stagedWorkingPaths.length > 0) {
1952
1996
  runGit(input, input.shadowRoot, ["commit", "-m", commitMessage(input)], "commit workspace changes");
@@ -2111,6 +2155,79 @@ async function synchronizeWorkspace(rawInput) {
2111
2155
  };
2112
2156
  }
2113
2157
  }
2158
+ async function convergeWorkspaceHead(rawInput, options) {
2159
+ const input = {
2160
+ ...rawInput,
2161
+ projects: workspaceProjectsForSync(rawInput.projects, rawInput.trigger)
2162
+ };
2163
+ if (input.trigger.canonicalCheckoutOnly) {
2164
+ throw new Error("Ordinary inbound convergence cannot target a canonical resolver checkout");
2165
+ }
2166
+ ensureShadowWorkspace(input);
2167
+ const fetchedLocalHead = revParse(input, "HEAD");
2168
+ const fetchedCanonicalHead = revParse(input, `origin/${WORKSPACE_BRANCH}`);
2169
+ if (!options.hydrateVisible) {
2170
+ return {
2171
+ localHead: fetchedLocalHead,
2172
+ canonicalHead: fetchedCanonicalHead,
2173
+ localChanges: false,
2174
+ hydrated: false,
2175
+ hydrationConflicts: []
2176
+ };
2177
+ }
2178
+ resetUncommittedShadowSnapshot(input);
2179
+ restoreUnscopedCanonicalCheckoutSubtrees(input);
2180
+ const localHead = fetchedLocalHead;
2181
+ const canonicalHead = fetchedCanonicalHead;
2182
+ const projection = mirrorVisibleWorkspaceToShadow(input);
2183
+ runGit(
2184
+ input,
2185
+ input.shadowRoot,
2186
+ ["add", "-A", "--", ".", ...projection.opaqueRoots.map((root) => `:(exclude,literal)${root}`)],
2187
+ "stage inbound workspace observation"
2188
+ );
2189
+ forceStageCanonicalCheckoutFiles(input, projection.entries, projection.opaqueRoots);
2190
+ const stagedTree = runGit(input, input.shadowRoot, ["write-tree"], "write inbound workspace observation tree");
2191
+ const headTree = localHead ? revParse(input, `${localHead}^{tree}`) : null;
2192
+ const localChanges = stagedTree !== headTree;
2193
+ if (!options.hydrateVisible || localChanges || !canonicalHead || localHead === canonicalHead) {
2194
+ resetUncommittedShadowSnapshot(input);
2195
+ return { localHead, canonicalHead, localChanges, hydrated: false, hydrationConflicts: [] };
2196
+ }
2197
+ const preimages = projection.visiblePreimages ?? hydrationPreimagesFromShadowRevision(input, localHead);
2198
+ const hydrationConflicts = localHead ? hydrationConflictPaths(input, preimages, localHead, canonicalHead) : [];
2199
+ if (hydrationConflicts.length > 0) {
2200
+ resetUncommittedShadowSnapshot(input);
2201
+ return { localHead, canonicalHead, localChanges: true, hydrated: false, hydrationConflicts };
2202
+ }
2203
+ runGit(input, input.shadowRoot, ["reset", "--hard", canonicalHead], "fast-forward workspace to available canonical head");
2204
+ runGit(input, input.shadowRoot, ["clean", "-fd"], "clean fast-forwarded workspace");
2205
+ mirrorShadowWorkspaceToVisible(input, projection.opaqueRoots, preimages);
2206
+ return { localHead: canonicalHead, canonicalHead, localChanges: false, hydrated: true, hydrationConflicts: [] };
2207
+ }
2208
+ function calculateWorkspaceLocalDiffFingerprint(rawInput) {
2209
+ const input = {
2210
+ ...rawInput,
2211
+ projects: workspaceProjectsForSync(rawInput.projects, rawInput.trigger)
2212
+ };
2213
+ if (!import_node_fs.default.existsSync(import_node_path.default.join(input.shadowRoot, ".git"))) {
2214
+ throw new Error("Cannot inspect local workspace changes before first bootstrap");
2215
+ }
2216
+ resetUncommittedShadowSnapshot(input);
2217
+ restoreUnscopedCanonicalCheckoutSubtrees(input);
2218
+ const projection = mirrorVisibleWorkspaceToShadow(input);
2219
+ runGit(
2220
+ input,
2221
+ input.shadowRoot,
2222
+ ["add", "-A", "--", ".", ...projection.opaqueRoots.map((root) => `:(exclude,literal)${root}`)],
2223
+ "stage local workspace fingerprint"
2224
+ );
2225
+ forceStageCanonicalCheckoutFiles(input, projection.entries, projection.opaqueRoots);
2226
+ const stagedTree = runGit(input, input.shadowRoot, ["write-tree"], "write local workspace fingerprint tree");
2227
+ const headTree = revParse(input, "HEAD^{tree}");
2228
+ resetUncommittedShadowSnapshot(input);
2229
+ return stagedTree === headTree ? (0, import_node_crypto.createHash)("sha256").update("").digest("hex") : (0, import_node_crypto.createHash)("sha256").update(stagedTree).digest("hex");
2230
+ }
2114
2231
  function calculateWorkspaceDiffFingerprint(rawInput) {
2115
2232
  const input = {
2116
2233
  ...rawInput,
@@ -2183,11 +2300,13 @@ class WorkspaceSyncSingleFlight {
2183
2300
  WORKSPACE_BRANCH,
2184
2301
  WORKSPACE_INTENT_REF_PREFIX,
2185
2302
  WORKSPACE_NATIVE_PLANS_RELATIVE_PATH,
2186
- WORKSPACE_PERIODIC_SCAN_INTERVAL_MS,
2303
+ WORKSPACE_PUBLICATION_REF_PREFIX,
2187
2304
  WorkspaceSyncSingleFlight,
2188
2305
  assertCanonicalCheckoutIndexMatchesWorktree,
2189
2306
  assertWorkspaceQuarantineRef,
2190
2307
  calculateWorkspaceDiffFingerprint,
2308
+ calculateWorkspaceLocalDiffFingerprint,
2309
+ convergeWorkspaceHead,
2191
2310
  encodeWorkspaceBranch,
2192
2311
  mirrorShadowWorkspaceToVisible,
2193
2312
  mirrorVisibleWorkspaceToShadow,