@bevel-software/platform-core-backend 0.4.0 → 0.4.1

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.
@@ -1403,12 +1403,23 @@ export class GitService implements IGitService {
1403
1403
  // A failed rebase leaves the repo in a "REBASE_HEAD" / detached-apply state,
1404
1404
  // which makes every subsequent git command behave unexpectedly. Return the
1405
1405
  // working tree to a clean state before surfacing the original error.
1406
- await this.git(cwd, ['rebase', '--abort']).catch(() => undefined);
1407
- if (conflictedPaths.length > 0) {
1406
+ let abortSucceeded = true;
1407
+ await this.git(cwd, ['rebase', '--abort']).catch(() => {
1408
+ abortSucceeded = false;
1409
+ });
1410
+ if (abortSucceeded && conflictedPaths.length > 0) {
1408
1411
  // Typed so the workflow layer can queue background recovery — this
1409
1412
  // divergence never resolves on its own (every retry pull hits the
1410
1413
  // same conflict) and the unpushed local commits are someone's saved
1411
1414
  // content stuck violating save=share.
1415
+ //
1416
+ // Gated on the abort succeeding: a failed abort means either no
1417
+ // rebase was ever active (the unmerged paths pre-date this pull —
1418
+ // some earlier operation left the index broken) or the clone is in
1419
+ // a state git itself can't unwind. Queueing recovery there would
1420
+ // point the worker's commitFile at a repo mid-conflict, risking a
1421
+ // commit full of conflict markers — surface the raw error instead
1422
+ // and leave diagnosis to a human.
1412
1423
  throw new PullRebaseConflictError(
1413
1424
  branch,
1414
1425
  conflictedPaths,
@@ -327,10 +327,13 @@ export class WorkflowService implements IWorkflowService {
327
327
  * `needs_attention` row: once the ladder has escalated to a human, more
328
328
  * agent runs on the same divergence are just a loop.
329
329
  *
330
- * One row, first conflicted path as the representative: the recovery
330
+ * One row, smallest conflicted path as the representative: the recovery
331
331
  * agent's job is "bring the BRANCH into a pushable state", so one ladder
332
332
  * per divergence — not one per conflicted file, which would multiply
333
- * agent runs for a single underlying conflict.
333
+ * agent runs for a single underlying conflict. Sorted so the choice is
334
+ * stable across retries regardless of how the caller ordered the paths —
335
+ * a shifting representative would slip past `enqueueIfAbsent`'s
336
+ * per-(workspace, branch, path) dedup and start a second ladder.
334
337
  *
335
338
  * Best-effort by design — the caller is already surfacing the conflict
336
339
  * error; a queue hiccup must not mask it.
@@ -344,7 +347,7 @@ export class WorkflowService implements IWorkflowService {
344
347
  const queued = await this.pendingCommits.enqueueIfAbsent({
345
348
  workspaceId,
346
349
  branch: err.branch,
347
- path: err.conflictedPaths[0],
350
+ path: [...err.conflictedPaths].sort()[0],
348
351
  authorEmail: user?.email ?? RECOVERY_BOT_EMAIL,
349
352
  authorName: user?.name ?? RECOVERY_BOT_NAME,
350
353
  });
@@ -1418,16 +1421,19 @@ export class WorkflowService implements IWorkflowService {
1418
1421
  // this a read right after a merge misses the just-merged change. Best-effort:
1419
1422
  // the merge already succeeded on origin, so a pull hiccup must not fail the
1420
1423
  // response (a later fetch/pull reconciles).
1424
+ let targetWorkspaceId: string | undefined;
1421
1425
  try {
1422
1426
  const targetWorkspace = await this.workspaceService.getOrCreateForBranch(baseBranch);
1427
+ targetWorkspaceId = targetWorkspace.id;
1423
1428
  await this.git.pull(targetWorkspace.id);
1424
1429
  } catch (err) {
1425
1430
  // Still best-effort for the merge response (the merge already landed
1426
1431
  // on origin) — but a rebase CONFLICT here means the target workspace
1427
1432
  // is stranded (local commits vs origin, never self-heals), so queue
1428
- // the background recovery ladder before shrugging.
1429
- if (err instanceof PullRebaseConflictError) {
1430
- const targetWorkspaceId = encodeURIComponent(baseBranch);
1433
+ // the background recovery ladder before shrugging. A conflict can
1434
+ // only have come from the pull, so the workspace id is always set on
1435
+ // this arm — the guard just satisfies the narrowing.
1436
+ if (err instanceof PullRebaseConflictError && targetWorkspaceId) {
1431
1437
  await this.queuePullConflictRecovery(targetWorkspaceId, err, user);
1432
1438
  }
1433
1439
  console.warn(