@straiffi/archon 1.7.8 → 1.7.9

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.
Files changed (100) hide show
  1. package/dist/client/assets/{ActivityView-DwfDSF4q.js → ActivityView-CDLQOXZm.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-3dMCYCSZ.js → GitHubActionsDialog-DYxLX2HS.js} +1 -1
  3. package/dist/client/assets/{VerificationDialog-CoTfXMyb.js → VerificationDialog-DEKvZG75.js} +1 -1
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-Bdnc97XK.js → architectureDiagram-3BPJPVTR-ByNXcCIO.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-boxM3She.js → blockDiagram-GPEHLZMM-ByykLAaG.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-CJDdF7xd.js → c4Diagram-AAUBKEIU-CmWA4Pli.js} +1 -1
  7. package/dist/client/assets/channel-BvcRv90i.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-ClpcO-Om.js → chunk-2J33WTMH-CTgbXRh4.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-CcuL_65J.js → chunk-3OPIFGDE-D3FajVfq.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-CUGYZXkH.js → chunk-5ZQYHXKU-CUMg_Vfo.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-CApeLDyK.js → chunk-727SXJPM-Oc9MgxZD.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-8FdSvF2D.js → chunk-AQP2D5EJ-5JZboHeP.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-CZoIOF3a.js → chunk-CSCIHK7Q-BpkSMHjB.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-BigFQI5y.js → chunk-KSCS5N6A-BPBMAswO.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-DIi3SHaT.js → chunk-L5ZTLDWV-BA2sHmu7.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-B-Z0BcQy.js → chunk-LZXEDZCA-Blc9q-fQ.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-EBeba0mg.js → chunk-ND2GUHAM-GlVS74JP.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-DJBI2y53.js → chunk-NZK2D7GU-fctBFnAm.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-BqxdXhMd.js → chunk-O5CBEL6O-eGs4DDLJ.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-Coe1_Cbb.js → chunk-WU5MYG2G-vxa50E7g.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-IuovJyxl.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BBJ3avX6.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-tuTmeA0Y.js → dagre-BM42HDAG-DXGXNVUp.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-ILGCpG2F.js → diagram-2AECGRRQ-BRYr1GvB.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-C8qBZsgK.js → diagram-5GNKFQAL-BcwgMag4.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-BXlcm_Zo.js → diagram-KO2AKTUF-Delk7lbr.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-B8V5EVbD.js → diagram-LMA3HP47-BVtc_fjo.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-CWsdOqcP.js → diagram-OG6HWLK6-weIPvUz5.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-DW8pgly2.js → erDiagram-TEJ5UH35-DKde70dA.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-Djn7fOVR.js → flowDiagram-I6XJVG4X-B7Jrk-6c.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-DZ421MEy.js → ganttDiagram-6RSMTGT7-CDzWDgnA.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CCNzLPuP.js → gitGraphDiagram-PVQCEYII-DYZCryuN.js} +1 -1
  33. package/dist/client/assets/index-BlKOx9Vf.css +2 -0
  34. package/dist/client/assets/index-CmaBDvIB.js +273 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-Bd1gYgMp.js → infoDiagram-5YYISTIA-DmQOgVKK.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-txyMoatJ.js → ishikawaDiagram-YF4QCWOH-DCpu-q-Z.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-DhT3bmX6.js → journeyDiagram-JHISSGLW-DhBoglhI.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-BBHt2hfC.js → kanban-definition-UN3LZRKU-BSjvGFTY.js} +1 -1
  39. package/dist/client/assets/{line-KGRcT7Qz.js → line-ClKBZwDr.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-CDzt4_zR.js → mermaid-parser.core-BRA4mJ40.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-DD_-k1aB.js → mermaid.core-BZ7kSfEt.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DoctVTDl.js → mindmap-definition-RKZ34NQL-DoUdLHgj.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-DFlLEi70.js → pieDiagram-4H26LBE5-NVnzqltb.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-3YHOQgl7.js → quadrantDiagram-W4KKPZXB-DOITgkan.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-X4fZVL9M.js → requirementDiagram-4Y6WPE33-D0VPxrFE.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BUPnMXeG.js → sankeyDiagram-5OEKKPKP-Bx0uU7xV.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-D6QU3M64.js → sequenceDiagram-3UESZ5HK-B4lFL6cO.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-c35bu4qZ.js → stateDiagram-AJRCARHV-CsLVsMZ4.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CvrvaIdF.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-CX2y6e_b.js → timeline-definition-PNZ67QCA-BkcYpPFI.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-BDaWltjF.js → vennDiagram-CIIHVFJN-Xc5f2sRQ.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BpBRL2sb.js → wardleyDiagram-YWT4CUSO-n3Dpk02Q.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-Bqw7X0UT.js → xychartDiagram-2RQKCTM6-BTMzF8zg.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +8 -6
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +224 -11
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/automationCheckGate.js +82 -7
  60. package/dist/server/lib/automationCheckGate.js.map +1 -1
  61. package/dist/server/lib/automationReconciliation.js +19 -0
  62. package/dist/server/lib/automationReconciliation.js.map +1 -1
  63. package/dist/server/lib/automationResumeDecision.js +57 -0
  64. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  65. package/dist/server/lib/automationStageDetails.js +98 -5
  66. package/dist/server/lib/automationStageDetails.js.map +1 -1
  67. package/dist/server/lib/buildCompletionContinuation.js +109 -0
  68. package/dist/server/lib/buildCompletionContinuation.js.map +1 -0
  69. package/dist/server/lib/buildSequences.js +81 -0
  70. package/dist/server/lib/buildSequences.js.map +1 -1
  71. package/dist/server/lib/buildTicketCommit.js +219 -0
  72. package/dist/server/lib/buildTicketCommit.js.map +1 -0
  73. package/dist/server/lib/bundleAutomationReadiness.js +7 -0
  74. package/dist/server/lib/bundleAutomationReadiness.js.map +1 -0
  75. package/dist/server/lib/bundleReviewOperations.js +17 -5
  76. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  77. package/dist/server/lib/chatOperations.js +2 -2
  78. package/dist/server/lib/chatOperations.js.map +1 -1
  79. package/dist/server/lib/checkGateEvaluations.js +1 -0
  80. package/dist/server/lib/checkGateEvaluations.js.map +1 -1
  81. package/dist/server/lib/projects.js +13 -1
  82. package/dist/server/lib/projects.js.map +1 -1
  83. package/dist/server/lib/requiredCheckPolicy.js +35 -10
  84. package/dist/server/lib/requiredCheckPolicy.js.map +1 -1
  85. package/dist/server/lib/ticketAutomationCommit.js +250 -160
  86. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  87. package/dist/server/lib/types.js.map +1 -1
  88. package/dist/server/workers/build.js +50 -81
  89. package/dist/server/workers/build.js.map +1 -1
  90. package/dist/server/workers/chat.js +13 -6
  91. package/dist/server/workers/chat.js.map +1 -1
  92. package/dist/server/workers/followUp.js +53 -30
  93. package/dist/server/workers/followUp.js.map +1 -1
  94. package/package.json +1 -1
  95. package/dist/client/assets/channel-BGqdAcIT.js +0 -1
  96. package/dist/client/assets/classDiagram-4FO5ZUOK-D5VuKRcj.js +0 -1
  97. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B9GNqpRr.js +0 -1
  98. package/dist/client/assets/index-BRc02lbW.js +0 -273
  99. package/dist/client/assets/index-CZO9HL1Z.css +0 -2
  100. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Ymy1_EHr.js +0 -1
@@ -12,7 +12,7 @@ import { getGitHubConnectionConfig } from './integrations/index.js';
12
12
  import { upsertBundlePullRequestFromGitHub, } from './bundlePullRequests.js';
13
13
  import { depthAtLeast, getBundleAutomationState, resolveAutomationConfig, } from './ticketAutomation.js';
14
14
  import { completeAutomationProgressionInTransaction, completeAutomationProgression, getAutomationProgression, waitAutomationProgressionInTransaction, waitAutomationProgression, } from './automationProgression.js';
15
- import { assertAutomationAuthorizationCurrent, authorizeAutomationBoundary, AutomationCheckGateError, } from './automationCheckGate.js';
15
+ import { assertAutomationBoundaryNotRequired, assertAutomationAuthorizationCurrent, authorizeAutomationBoundary, AutomationCheckGateError, } from './automationCheckGate.js';
16
16
  import { AUTOMATION_STAGE_DETAILS_SCHEMA_VERSION, createAutomationStageDetails, ensureAutomationStageResult, getAutomationStageDetails, } from './automationStageDetails.js';
17
17
  import { getProjectById } from './projects.js';
18
18
  import { getExecutionRun, withExecutionRunTransaction } from './executionRuns.js';
@@ -160,7 +160,37 @@ const loadLiveState = (projectId, bundleId) => {
160
160
  }
161
161
  return { project, bundle, automationConfig: resolveAutomationConfig(bundle, project) };
162
162
  };
163
- const assertStageCanMutate = (projectId, bundleId, progressionRunId, requiredDepth) => {
163
+ const toStageCheckAuthorization = (authorization) => authorization.requirement === 'required'
164
+ ? {
165
+ kind: 'required',
166
+ gateEvaluationId: authorization.evaluation.id,
167
+ policyFingerprint: authorization.policyFingerprint,
168
+ workspaceFingerprint: authorization.workspaceIdentity.fingerprint,
169
+ }
170
+ : {
171
+ kind: 'not_required',
172
+ policyFingerprint: authorization.policyFingerprint,
173
+ };
174
+ const assertBoundaryAuthorizationCurrent = async ({ authorization, project, bundle, progressionRunId, cwd, boundaryKind, }) => {
175
+ if (authorization.requirement === 'required') {
176
+ await assertAutomationAuthorizationCurrent({
177
+ project,
178
+ bundle,
179
+ progressionRunId,
180
+ cwd,
181
+ evaluationId: authorization.evaluation.id,
182
+ boundaryKind,
183
+ });
184
+ return;
185
+ }
186
+ assertAutomationBoundaryNotRequired({
187
+ project,
188
+ bundle,
189
+ progressionRunId,
190
+ policyFingerprint: authorization.policyFingerprint,
191
+ });
192
+ };
193
+ const assertStageCanMutateWithPolicy = (projectId, bundleId, progressionRunId, ticket, mutationLabel, canMutateLive) => {
164
194
  const live = loadLiveState(projectId, bundleId);
165
195
  const progression = getAutomationProgression(projectId, bundleId);
166
196
  const state = getBundleAutomationState(projectId, bundleId);
@@ -169,12 +199,12 @@ const assertStageCanMutate = (projectId, bundleId, progressionRunId, requiredDep
169
199
  || progression.status !== 'running'
170
200
  || state?.automation_progression_run_id !== progressionRunId
171
201
  || state.automation_paused_reason !== null
172
- || !depthAtLeast(live.automationConfig.depth, requiredDepth)) {
173
- const mutation = requiredDepth === 'auto_commit' ? 'commit' : requiredDepth === 'auto_push' ? 'push' : 'pull-request';
174
- throw new Error(`automation no longer permits ${mutation} mutation`);
202
+ || !canMutateLive(live, { ticket, progressionRunId })) {
203
+ throw new Error(`automation no longer permits ${mutationLabel} mutation`);
175
204
  }
176
205
  return live;
177
206
  };
207
+ const assertStageCanMutate = (projectId, bundleId, progressionRunId, ticket, requiredDepth) => assertStageCanMutateWithPolicy(projectId, bundleId, progressionRunId, ticket, requiredDepth === 'auto_commit' ? 'commit' : requiredDepth === 'auto_push' ? 'push' : 'pull-request', live => depthAtLeast(live.automationConfig.depth, requiredDepth));
178
208
  const githubConfigMatchesIntent = (githubConfig, intent) => (githubConfig.host.toLowerCase() === intent.repoHost.toLowerCase()
179
209
  && githubConfig.repo_owner.toLowerCase() === intent.repoOwner.toLowerCase()
180
210
  && githubConfig.repo_name.toLowerCase() === intent.repoName.toLowerCase()
@@ -322,6 +352,181 @@ const pauseBeforeStage = ({ projectId, bundleId, progressionRunId, pauseCode, re
322
352
  pausedStageRunId: null,
323
353
  broadcaster,
324
354
  });
355
+ export const runAutomationCommitOperation = async ({ ticket, io, progressionRunId, allowedActiveChildRunId, retryOfRunId, mutationLabel, canCommitLive, shouldCompleteProgression, failureMode = 'return', requiredProgressionPurpose, }) => {
356
+ if (!ticket.project_id || !ticket.worktree_bundle_id) {
357
+ return null;
358
+ }
359
+ const projectId = ticket.project_id;
360
+ const bundleId = ticket.worktree_bundle_id;
361
+ const progression = getAutomationProgression(projectId, bundleId);
362
+ if (!progression || progression.id !== progressionRunId) {
363
+ return null;
364
+ }
365
+ const broadcaster = asBroadcaster(io);
366
+ let live = loadLiveState(projectId, bundleId);
367
+ if (!canCommitLive(live, { ticket, progressionRunId })) {
368
+ completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
369
+ emitUpdated(io, ticket.id);
370
+ return null;
371
+ }
372
+ const branch = live.bundle.branch;
373
+ const cwd = live.bundle.kind === 'project_root'
374
+ ? live.project.repo_path
375
+ : resolveWorktreePath(branch, live.project);
376
+ let commitRunId = null;
377
+ try {
378
+ const status = getGitStatus(cwd, branch, live.project);
379
+ const precondition = evaluateCommitPrecondition(status);
380
+ if (!precondition.ok) {
381
+ throw new Error(precondition.reason);
382
+ }
383
+ if (status.files.length === 0) {
384
+ const authorization = await authorizeAutomationBoundary({
385
+ project: live.project,
386
+ bundle: live.bundle,
387
+ progressionRunId,
388
+ cwd,
389
+ boundaryKind: 'automation_commit',
390
+ requiredProgressionPurpose,
391
+ broadcaster,
392
+ prepare: () => {
393
+ const head = getGitHeadIdentity(cwd);
394
+ return { head, indexTreeSha: getGitIndexTreeSha(cwd) };
395
+ },
396
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
397
+ kind: 'workspace', fingerprint: workspaceIdentity.fingerprint,
398
+ }),
399
+ });
400
+ const { head, indexTreeSha } = authorization.value;
401
+ const intent = {
402
+ branch,
403
+ preActionHeadSha: head.headSha,
404
+ preActionIndexTreeSha: indexTreeSha,
405
+ intendedTreeSha: head.treeSha,
406
+ commitMessageDigest: EMPTY_MESSAGE_DIGEST,
407
+ selectedContentDigest: computeGitTreeDeltaDigest(cwd, head.headSha, head.treeSha),
408
+ checkAuthorization: toStageCheckAuthorization(authorization),
409
+ };
410
+ commitRunId = startStage({
411
+ projectId,
412
+ bundleId,
413
+ ticketId: ticket.id,
414
+ ticketTitle: ticket.title,
415
+ progressionRunId,
416
+ allowedActiveChildRunId,
417
+ stageKind: 'automation_commit',
418
+ intent,
419
+ broadcaster,
420
+ retryOfRunId,
421
+ });
422
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_commit' });
423
+ live = loadLiveState(projectId, bundleId);
424
+ const result = {
425
+ outcome: 'no_changes',
426
+ headSha: head.headSha,
427
+ treeSha: head.treeSha,
428
+ };
429
+ finishStage({
430
+ projectId,
431
+ bundleId,
432
+ progressionRunId,
433
+ runId: commitRunId,
434
+ stageKind: 'automation_commit',
435
+ result,
436
+ outcome: 'no_changes',
437
+ completeProgression: shouldCompleteProgression(live, result),
438
+ broadcaster,
439
+ });
440
+ return { runId: commitRunId, ...result };
441
+ }
442
+ const selectedPaths = getAutomationCommitPaths(status.files);
443
+ const message = await generateCommitMessage(cwd, live.project, selectedPaths);
444
+ live = loadLiveState(projectId, bundleId);
445
+ if (!message) {
446
+ throw new Error('commit message generation produced an empty result');
447
+ }
448
+ if (!canCommitLive(live, { ticket, progressionRunId })) {
449
+ completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
450
+ emitUpdated(io, ticket.id);
451
+ return null;
452
+ }
453
+ const authorization = await authorizeAutomationBoundary({
454
+ project: live.project,
455
+ bundle: live.bundle,
456
+ progressionRunId,
457
+ cwd,
458
+ boundaryKind: 'automation_commit',
459
+ requiredProgressionPurpose,
460
+ broadcaster,
461
+ prepare: () => prepareAutomationCommit(cwd, branch, live.project, selectedPaths),
462
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
463
+ kind: 'workspace', fingerprint: workspaceIdentity.fingerprint,
464
+ }),
465
+ });
466
+ const prepared = authorization.value;
467
+ const intent = {
468
+ branch,
469
+ preActionHeadSha: prepared.preActionHeadSha,
470
+ preActionIndexTreeSha: prepared.preActionIndexTreeSha,
471
+ intendedTreeSha: prepared.intendedTreeSha,
472
+ commitMessageDigest: digestCommitMessage(message),
473
+ selectedContentDigest: prepared.selectedContentDigest,
474
+ checkAuthorization: toStageCheckAuthorization(authorization),
475
+ };
476
+ commitRunId = startStage({
477
+ projectId,
478
+ bundleId,
479
+ ticketId: ticket.id,
480
+ ticketTitle: ticket.title,
481
+ progressionRunId,
482
+ allowedActiveChildRunId,
483
+ stageKind: 'automation_commit',
484
+ intent,
485
+ broadcaster,
486
+ retryOfRunId,
487
+ });
488
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_commit' });
489
+ live = assertStageCanMutateWithPolicy(projectId, bundleId, progressionRunId, ticket, mutationLabel, canCommitLive);
490
+ const result = await commitPreparedAutomationChanges(cwd, branch, live.project, message, prepared);
491
+ live = loadLiveState(projectId, bundleId);
492
+ finishStage({
493
+ projectId,
494
+ bundleId,
495
+ progressionRunId,
496
+ runId: commitRunId,
497
+ stageKind: 'automation_commit',
498
+ result,
499
+ outcome: 'committed',
500
+ completeProgression: shouldCompleteProgression(live, result),
501
+ broadcaster,
502
+ });
503
+ return { outcome: 'committed', runId: commitRunId, ...result };
504
+ }
505
+ catch (error) {
506
+ if (error instanceof AutomationCheckGateError) {
507
+ if (commitRunId) {
508
+ failStageAuthorization({ projectId, bundleId, progressionRunId, runId: commitRunId, stageKind: 'automation_commit', error, broadcaster });
509
+ }
510
+ emitBundleUpdated(io, ticket);
511
+ if (failureMode === 'throw') {
512
+ throw error;
513
+ }
514
+ return null;
515
+ }
516
+ const reason = `commit failed: ${getErrorMessage(error)}`;
517
+ if (commitRunId) {
518
+ failStage({ projectId, bundleId, progressionRunId, runId: commitRunId, stageKind: 'automation_commit', pauseCode: 'commit', reason, ambiguous: true, broadcaster });
519
+ }
520
+ else {
521
+ pauseBeforeStage({ projectId, bundleId, progressionRunId, pauseCode: 'commit', reason, broadcaster });
522
+ }
523
+ emitUpdated(io, ticket.id);
524
+ if (failureMode === 'throw') {
525
+ throw error;
526
+ }
527
+ return null;
528
+ }
529
+ };
325
530
  const remoteStatesEqual = (left, right) => (left.state === right.state && (left.state === 'absent' || (right.state === 'observed' && left.sha === right.sha)));
326
531
  export const runAutomationCommitChain = async (ticket, _project, _bundle, _automationConfig, io, progressionRunId, resumeFrom, triggeringReviewRunId) => {
327
532
  if (!ticket.project_id || !ticket.worktree_bundle_id) {
@@ -342,143 +547,30 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
342
547
  || (resumeFrom.stageKind === 'automation_push' && !isRetry)));
343
548
  let expectedHeadSha = resumeFrom?.expectedHeadSha ?? null;
344
549
  let live = loadLiveState(projectId, bundleId);
345
- if (!depthAtLeast(live.automationConfig.depth, 'auto_commit')) {
346
- completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
347
- emitUpdated(io, ticket.id);
348
- return;
349
- }
350
550
  const branch = live.bundle.branch;
351
551
  const cwd = live.bundle.kind === 'project_root'
352
552
  ? live.project.repo_path
353
553
  : resolveWorktreePath(branch, live.project);
354
- let commitRunId = null;
355
554
  if (!skipCommit) {
356
- try {
357
- const status = getGitStatus(cwd, branch, live.project);
358
- const precondition = evaluateCommitPrecondition(status);
359
- if (!precondition.ok) {
360
- throw new Error(precondition.reason);
361
- }
362
- if (status.files.length === 0) {
363
- const authorization = await authorizeAutomationBoundary({
364
- project: live.project,
365
- bundle: live.bundle,
366
- progressionRunId,
367
- cwd,
368
- boundaryKind: 'automation_commit',
369
- broadcaster,
370
- resolveIntendedIdentity: workspaceIdentity => {
371
- const head = getGitHeadIdentity(cwd);
372
- return {
373
- intendedIdentity: { kind: 'git_tree', treeSha: head.treeSha, workspaceFingerprint: workspaceIdentity.fingerprint },
374
- value: { head, indexTreeSha: getGitIndexTreeSha(cwd) },
375
- };
376
- },
377
- });
378
- const { head, indexTreeSha } = authorization.value;
379
- const intent = {
380
- branch,
381
- preActionHeadSha: head.headSha,
382
- preActionIndexTreeSha: indexTreeSha,
383
- intendedTreeSha: head.treeSha,
384
- commitMessageDigest: EMPTY_MESSAGE_DIGEST,
385
- selectedContentDigest: computeGitTreeDeltaDigest(cwd, head.headSha, head.treeSha),
386
- gateEvaluationId: authorization.evaluation.id,
387
- policyFingerprint: authorization.policyFingerprint,
388
- workspaceFingerprint: authorization.workspaceIdentity.fingerprint,
389
- };
390
- commitRunId = startStage({ projectId, bundleId, ticketId: ticket.id, ticketTitle: ticket.title, progressionRunId, allowedActiveChildRunId: triggeringReviewRunId, stageKind: 'automation_commit', intent, broadcaster, retryOfRunId: resumeFrom?.stageKind === 'automation_commit' ? resumeFrom.retryOfRunId : undefined });
391
- await assertAutomationAuthorizationCurrent({ project: live.project, bundle: live.bundle, progressionRunId, cwd, evaluationId: authorization.evaluation.id, boundaryKind: 'automation_commit' });
392
- live = loadLiveState(projectId, bundleId);
393
- finishStage({
394
- projectId,
395
- bundleId,
396
- progressionRunId,
397
- runId: commitRunId,
398
- stageKind: 'automation_commit',
399
- result: { outcome: 'no_changes', headSha: head.headSha, treeSha: head.treeSha },
400
- outcome: 'no_changes',
401
- completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_push'),
402
- broadcaster,
403
- });
404
- expectedHeadSha = head.headSha;
405
- }
406
- else {
407
- const selectedPaths = getAutomationCommitPaths(status.files);
408
- const message = await generateCommitMessage(cwd, live.project, selectedPaths);
409
- live = loadLiveState(projectId, bundleId);
410
- if (!message) {
411
- throw new Error('commit message generation produced an empty result');
412
- }
413
- if (!depthAtLeast(live.automationConfig.depth, 'auto_commit')) {
414
- completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
415
- emitUpdated(io, ticket.id);
416
- return;
417
- }
418
- const authorization = await authorizeAutomationBoundary({
419
- project: live.project,
420
- bundle: live.bundle,
421
- progressionRunId,
422
- cwd,
423
- boundaryKind: 'automation_commit',
424
- broadcaster,
425
- resolveIntendedIdentity: workspaceIdentity => {
426
- const prepared = prepareAutomationCommit(cwd, branch, live.project, selectedPaths);
427
- return {
428
- intendedIdentity: { kind: 'git_tree', treeSha: prepared.intendedTreeSha, workspaceFingerprint: workspaceIdentity.fingerprint },
429
- value: prepared,
430
- };
431
- },
432
- });
433
- const prepared = authorization.value;
434
- const intent = {
435
- branch,
436
- preActionHeadSha: prepared.preActionHeadSha,
437
- preActionIndexTreeSha: prepared.preActionIndexTreeSha,
438
- intendedTreeSha: prepared.intendedTreeSha,
439
- commitMessageDigest: digestCommitMessage(message),
440
- selectedContentDigest: prepared.selectedContentDigest,
441
- gateEvaluationId: authorization.evaluation.id,
442
- policyFingerprint: authorization.policyFingerprint,
443
- workspaceFingerprint: authorization.workspaceIdentity.fingerprint,
444
- };
445
- commitRunId = startStage({ projectId, bundleId, ticketId: ticket.id, ticketTitle: ticket.title, progressionRunId, allowedActiveChildRunId: triggeringReviewRunId, stageKind: 'automation_commit', intent, broadcaster, retryOfRunId: resumeFrom?.stageKind === 'automation_commit' ? resumeFrom.retryOfRunId : undefined });
446
- await assertAutomationAuthorizationCurrent({ project: live.project, bundle: live.bundle, progressionRunId, cwd, evaluationId: authorization.evaluation.id, boundaryKind: 'automation_commit' });
447
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_commit');
448
- const result = await commitPreparedAutomationChanges(cwd, branch, live.project, message, prepared);
449
- live = loadLiveState(projectId, bundleId);
450
- finishStage({
451
- projectId,
452
- bundleId,
453
- progressionRunId,
454
- runId: commitRunId,
455
- stageKind: 'automation_commit',
456
- result,
457
- outcome: 'committed',
458
- completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_push'),
459
- broadcaster,
460
- });
461
- expectedHeadSha = result.commitSha;
462
- }
463
- }
464
- catch (error) {
465
- if (error instanceof AutomationCheckGateError) {
466
- if (commitRunId) {
467
- failStageAuthorization({ projectId, bundleId, progressionRunId, runId: commitRunId, stageKind: 'automation_commit', error, broadcaster });
468
- }
469
- emitBundleUpdated(io, ticket);
470
- return;
471
- }
472
- const reason = `commit failed: ${getErrorMessage(error)}`;
473
- if (commitRunId) {
474
- failStage({ projectId, bundleId, progressionRunId, runId: commitRunId, stageKind: 'automation_commit', pauseCode: 'commit', reason, ambiguous: true, broadcaster });
475
- }
476
- else {
477
- pauseBeforeStage({ projectId, bundleId, progressionRunId, pauseCode: 'commit', reason, broadcaster });
478
- }
479
- emitUpdated(io, ticket.id);
555
+ const commitResult = await runAutomationCommitOperation({
556
+ ticket,
557
+ io,
558
+ progressionRunId,
559
+ allowedActiveChildRunId: triggeringReviewRunId,
560
+ retryOfRunId: resumeFrom?.stageKind === 'automation_commit' ? resumeFrom.retryOfRunId : undefined,
561
+ mutationLabel: 'commit',
562
+ canCommitLive: current => depthAtLeast(current.automationConfig.depth, 'auto_commit'),
563
+ shouldCompleteProgression: current => !depthAtLeast(current.automationConfig.depth, 'auto_push'),
564
+ });
565
+ if (!commitResult) {
480
566
  return;
481
567
  }
568
+ if (commitResult.outcome === 'committed') {
569
+ expectedHeadSha = commitResult.commitSha;
570
+ }
571
+ else {
572
+ expectedHeadSha = commitResult.headSha;
573
+ }
482
574
  }
483
575
  live = loadLiveState(projectId, bundleId);
484
576
  if (getAutomationProgression(projectId, bundleId)?.id !== progressionRunId) {
@@ -515,24 +607,23 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
515
607
  cwd,
516
608
  boundaryKind: 'automation_push',
517
609
  broadcaster,
518
- resolveIntendedIdentity: workspaceIdentity => {
610
+ prepare: () => {
519
611
  const head = getGitHeadIdentity(cwd);
520
612
  if (head.headSha !== target) {
521
613
  throw new Error('HEAD changed while required checks were evaluated for push');
522
614
  }
523
- return {
524
- intendedIdentity: { kind: 'commit', commitSha: target, workspaceFingerprint: workspaceIdentity.fingerprint },
525
- value: head,
526
- };
615
+ return head;
527
616
  },
617
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
618
+ kind: 'commit', commitSha: target, workspaceFingerprint: workspaceIdentity.fingerprint,
619
+ }),
528
620
  });
529
621
  const intent = {
530
622
  branch,
531
623
  originIdentity,
532
624
  targetLocalSha: target,
533
625
  preActionRemote,
534
- gateEvaluationId: authorization.evaluation.id,
535
- policyFingerprint: authorization.policyFingerprint,
626
+ checkAuthorization: toStageCheckAuthorization(authorization),
536
627
  intendedTreeSha: authorization.value.treeSha,
537
628
  resultingCommitSha: target,
538
629
  };
@@ -540,14 +631,14 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
540
631
  const current = await fetchRemoteBranchState(cwd, branch, originIdentity);
541
632
  live = loadLiveState(projectId, bundleId);
542
633
  if (current.state === 'observed' && current.sha === target) {
543
- await assertAutomationAuthorizationCurrent({ project: live.project, bundle: live.bundle, progressionRunId, cwd, evaluationId: authorization.evaluation.id, boundaryKind: 'automation_push' });
544
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_push');
634
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_push' });
635
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_push');
545
636
  finishStage({ projectId, bundleId, progressionRunId, runId: pushRunId, stageKind: 'automation_push', result: { remoteSha: target }, outcome: 'already_current', completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_pr'), broadcaster });
546
637
  expectedHeadSha = target;
547
638
  }
548
639
  else if (remoteStatesEqual(current, preActionRemote)) {
549
- await assertAutomationAuthorizationCurrent({ project: live.project, bundle: live.bundle, progressionRunId, cwd, evaluationId: authorization.evaluation.id, boundaryKind: 'automation_push' });
550
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_push');
640
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_push' });
641
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_push');
551
642
  await pushAutomationTarget(cwd, branch, target, originIdentity);
552
643
  live = loadLiveState(projectId, bundleId);
553
644
  const observed = await fetchRemoteBranchState(cwd, branch, originIdentity);
@@ -613,16 +704,16 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
613
704
  cwd,
614
705
  boundaryKind: 'automation_pull_request',
615
706
  broadcaster,
616
- resolveIntendedIdentity: workspaceIdentity => {
707
+ prepare: () => {
617
708
  const head = getGitHeadIdentity(cwd);
618
709
  if (head.headSha !== headSha) {
619
710
  throw new Error('HEAD changed while required checks were evaluated for pull request');
620
711
  }
621
- return {
622
- intendedIdentity: { kind: 'commit', commitSha: headSha, workspaceFingerprint: workspaceIdentity.fingerprint },
623
- value: head,
624
- };
712
+ return head;
625
713
  },
714
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
715
+ kind: 'commit', commitSha: headSha, workspaceFingerprint: workspaceIdentity.fingerprint,
716
+ }),
626
717
  });
627
718
  const intent = {
628
719
  provider: 'github',
@@ -632,8 +723,7 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
632
723
  baseBranch: githubConfig.default_base_branch,
633
724
  headBranch: branch,
634
725
  headSha,
635
- gateEvaluationId: authorization.evaluation.id,
636
- policyFingerprint: authorization.policyFingerprint,
726
+ checkAuthorization: toStageCheckAuthorization(authorization),
637
727
  intendedTreeSha: authorization.value.treeSha,
638
728
  resultingCommitSha: headSha,
639
729
  };
@@ -643,8 +733,8 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
643
733
  let pullRequest;
644
734
  let outcome;
645
735
  if (observation.outcome === 'exact') {
646
- await assertAutomationAuthorizationCurrent({ project: live.project, bundle: live.bundle, progressionRunId, cwd, evaluationId: authorization.evaluation.id, boundaryKind: 'automation_pull_request' });
647
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_pr');
736
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_pull_request' });
737
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
648
738
  pullRequest = observation.pullRequest;
649
739
  outcome = 'already_exists';
650
740
  }
@@ -655,8 +745,8 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
655
745
  if (!currentConfig || !githubConfigMatchesIntent(currentConfig, intent)) {
656
746
  throw new Error('GitHub configuration changed while the pull-request stage was active');
657
747
  }
658
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_pr');
659
- await assertAutomationAuthorizationCurrent({ project: live.project, bundle: live.bundle, progressionRunId, cwd, evaluationId: authorization.evaluation.id, boundaryKind: 'automation_pull_request' });
748
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
749
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_pull_request' });
660
750
  if (getGitHeadIdentity(cwd).headSha !== intent.headSha) {
661
751
  throw new Error('HEAD changed while the pull-request stage was active');
662
752
  }
@@ -668,12 +758,12 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
668
758
  if (!mutationConfig || !githubConfigMatchesIntent(mutationConfig, intent)) {
669
759
  throw new Error('GitHub configuration changed while the pull-request stage was active');
670
760
  }
671
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_pr');
761
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
672
762
  if (getGitHeadIdentity(cwd).headSha !== intent.headSha) {
673
763
  throw new Error('HEAD changed before pull-request creation');
674
764
  }
675
- await assertAutomationAuthorizationCurrent({ project: live.project, bundle: live.bundle, progressionRunId, cwd, evaluationId: authorization.evaluation.id, boundaryKind: 'automation_pull_request' });
676
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_pr');
765
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_pull_request' });
766
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
677
767
  pullRequest = await createGitHubPullRequest(mutationConfig, {
678
768
  title: content.title,
679
769
  body: content.body,