@straiffi/archon 1.7.8 → 1.7.10

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 (131) hide show
  1. package/dist/client/assets/{ActivityView-DwfDSF4q.js → ActivityView-IUWr92uj.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-3dMCYCSZ.js → GitHubActionsDialog-0YtWvbFk.js} +1 -1
  3. package/dist/client/assets/VerificationDialog-B4Pyz1LV.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-Bdnc97XK.js → architectureDiagram-3BPJPVTR-LLMyOR-6.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-boxM3She.js → blockDiagram-GPEHLZMM-aa1HitBJ.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-CJDdF7xd.js → c4Diagram-AAUBKEIU-BpSt5sdj.js} +1 -1
  7. package/dist/client/assets/channel-DWPRC-jg.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-ClpcO-Om.js → chunk-2J33WTMH-D8pf3RNr.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-CcuL_65J.js → chunk-3OPIFGDE-_yx1N2iT.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-CUGYZXkH.js → chunk-5ZQYHXKU-CYURGSbL.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-CApeLDyK.js → chunk-727SXJPM-Ci2Mu1zz.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-8FdSvF2D.js → chunk-AQP2D5EJ-CqVgrKze.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-CZoIOF3a.js → chunk-CSCIHK7Q-CT8aPk-o.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-BigFQI5y.js → chunk-KSCS5N6A-yM1i0J-p.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-DIi3SHaT.js → chunk-L5ZTLDWV-BKMGr31Z.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-B-Z0BcQy.js → chunk-LZXEDZCA-A7oUDETP.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-EBeba0mg.js → chunk-ND2GUHAM-Cagc-XAG.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-DJBI2y53.js → chunk-NZK2D7GU-pe8tKLWI.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-BqxdXhMd.js → chunk-O5CBEL6O-BahwghWW.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-Coe1_Cbb.js → chunk-WU5MYG2G-BfSAFufr.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-DBZaZLLc.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B7HsVFoJ.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-tuTmeA0Y.js → dagre-BM42HDAG-DCma6-hS.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-ILGCpG2F.js → diagram-2AECGRRQ-DUeggZrO.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-C8qBZsgK.js → diagram-5GNKFQAL-CdjOKGtl.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-BXlcm_Zo.js → diagram-KO2AKTUF-CYjXhgh_.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-B8V5EVbD.js → diagram-LMA3HP47-DYBCh4cm.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-CWsdOqcP.js → diagram-OG6HWLK6-D3HPQt3z.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-DW8pgly2.js → erDiagram-TEJ5UH35-mG8Yztg-.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-Djn7fOVR.js → flowDiagram-I6XJVG4X-4FvjuoSZ.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-DZ421MEy.js → ganttDiagram-6RSMTGT7-ByCQI8ft.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CCNzLPuP.js → gitGraphDiagram-PVQCEYII-CzEUA7nB.js} +1 -1
  33. package/dist/client/assets/index-D-miHTTb.js +273 -0
  34. package/dist/client/assets/index-TAQIlNdJ.css +2 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-Bd1gYgMp.js → infoDiagram-5YYISTIA-Q9CqnXO1.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-txyMoatJ.js → ishikawaDiagram-YF4QCWOH-CO8BToGf.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-DhT3bmX6.js → journeyDiagram-JHISSGLW-DtOyWvDJ.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-BBHt2hfC.js → kanban-definition-UN3LZRKU-DIn9bYpk.js} +1 -1
  39. package/dist/client/assets/{line-KGRcT7Qz.js → line-H-k0M9_s.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-CDzt4_zR.js → mermaid-parser.core-CZOUS5zK.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-DD_-k1aB.js → mermaid.core-Ck71Qr7h.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DoctVTDl.js → mindmap-definition-RKZ34NQL-BRFEcJw_.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-DFlLEi70.js → pieDiagram-4H26LBE5-CwihXXcJ.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-3YHOQgl7.js → quadrantDiagram-W4KKPZXB-moiRw4Cm.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-X4fZVL9M.js → requirementDiagram-4Y6WPE33-tshvM_vV.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BUPnMXeG.js → sankeyDiagram-5OEKKPKP-rtKIuBEp.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-D6QU3M64.js → sequenceDiagram-3UESZ5HK-lkTCo4wE.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-c35bu4qZ.js → stateDiagram-AJRCARHV-CFQhVUSd.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-rrN4DgWp.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-CX2y6e_b.js → timeline-definition-PNZ67QCA-BmlQ7Wiy.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-BDaWltjF.js → vennDiagram-CIIHVFJN-CZyZepcT.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BpBRL2sb.js → wardleyDiagram-YWT4CUSO-C2b52gni.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-Bqw7X0UT.js → xychartDiagram-2RQKCTM6-FqnROUHq.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +23 -10
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +330 -25
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/agent.js +5 -4
  60. package/dist/server/lib/agent.js.map +1 -1
  61. package/dist/server/lib/automationCheckGate.js +114 -20
  62. package/dist/server/lib/automationCheckGate.js.map +1 -1
  63. package/dist/server/lib/automationProgression.js +2 -1
  64. package/dist/server/lib/automationProgression.js.map +1 -1
  65. package/dist/server/lib/automationReconciliation.js +19 -0
  66. package/dist/server/lib/automationReconciliation.js.map +1 -1
  67. package/dist/server/lib/automationResumeDecision.js +82 -0
  68. package/dist/server/lib/automationResumeDecision.js.map +1 -1
  69. package/dist/server/lib/automationReviewGate.js +142 -0
  70. package/dist/server/lib/automationReviewGate.js.map +1 -0
  71. package/dist/server/lib/automationStageDetails.js +98 -5
  72. package/dist/server/lib/automationStageDetails.js.map +1 -1
  73. package/dist/server/lib/buildCompletionContinuation.js +109 -0
  74. package/dist/server/lib/buildCompletionContinuation.js.map +1 -0
  75. package/dist/server/lib/buildSequences.js +81 -0
  76. package/dist/server/lib/buildSequences.js.map +1 -1
  77. package/dist/server/lib/buildTicketCommit.js +219 -0
  78. package/dist/server/lib/buildTicketCommit.js.map +1 -0
  79. package/dist/server/lib/bundleAutomationReadiness.js +7 -0
  80. package/dist/server/lib/bundleAutomationReadiness.js.map +1 -0
  81. package/dist/server/lib/bundleReviewOperations.js +17 -5
  82. package/dist/server/lib/bundleReviewOperations.js.map +1 -1
  83. package/dist/server/lib/chatOperations.js +2 -2
  84. package/dist/server/lib/chatOperations.js.map +1 -1
  85. package/dist/server/lib/checkDefinitions.js +11 -0
  86. package/dist/server/lib/checkDefinitions.js.map +1 -1
  87. package/dist/server/lib/checkGateEvaluations.js +4 -3
  88. package/dist/server/lib/checkGateEvaluations.js.map +1 -1
  89. package/dist/server/lib/checkRunner.js +27 -4
  90. package/dist/server/lib/checkRunner.js.map +1 -1
  91. package/dist/server/lib/checkRuns.js +38 -6
  92. package/dist/server/lib/checkRuns.js.map +1 -1
  93. package/dist/server/lib/git.js +5 -1
  94. package/dist/server/lib/git.js.map +1 -1
  95. package/dist/server/lib/lightweightPrompt.js +2 -1
  96. package/dist/server/lib/lightweightPrompt.js.map +1 -1
  97. package/dist/server/lib/projectAutoConfig.js +2 -1
  98. package/dist/server/lib/projectAutoConfig.js.map +1 -1
  99. package/dist/server/lib/projects.js +95 -10
  100. package/dist/server/lib/projects.js.map +1 -1
  101. package/dist/server/lib/requiredCheckPolicy.js +150 -19
  102. package/dist/server/lib/requiredCheckPolicy.js.map +1 -1
  103. package/dist/server/lib/run.js +3 -2
  104. package/dist/server/lib/run.js.map +1 -1
  105. package/dist/server/lib/terminal.js +5 -7
  106. package/dist/server/lib/terminal.js.map +1 -1
  107. package/dist/server/lib/testSessions.js +2 -2
  108. package/dist/server/lib/testSessions.js.map +1 -1
  109. package/dist/server/lib/ticketAutomationCommit.js +251 -160
  110. package/dist/server/lib/ticketAutomationCommit.js.map +1 -1
  111. package/dist/server/lib/tickets.js +45 -4
  112. package/dist/server/lib/tickets.js.map +1 -1
  113. package/dist/server/lib/types.js.map +1 -1
  114. package/dist/server/lib/workspaceProcessEnvironment.js +21 -0
  115. package/dist/server/lib/workspaceProcessEnvironment.js.map +1 -0
  116. package/dist/server/workers/build.js +57 -97
  117. package/dist/server/workers/build.js.map +1 -1
  118. package/dist/server/workers/chat.js +13 -6
  119. package/dist/server/workers/chat.js.map +1 -1
  120. package/dist/server/workers/followUp.js +92 -116
  121. package/dist/server/workers/followUp.js.map +1 -1
  122. package/dist/server/workers/plan.js +4 -1
  123. package/dist/server/workers/plan.js.map +1 -1
  124. package/package.json +1 -1
  125. package/dist/client/assets/VerificationDialog-CoTfXMyb.js +0 -4
  126. package/dist/client/assets/channel-BGqdAcIT.js +0 -1
  127. package/dist/client/assets/classDiagram-4FO5ZUOK-D5VuKRcj.js +0 -1
  128. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B9GNqpRr.js +0 -1
  129. package/dist/client/assets/index-BRc02lbW.js +0 -273
  130. package/dist/client/assets/index-CZO9HL1Z.css +0 -2
  131. 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,38 @@ 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
+ boundaryKind,
192
+ });
193
+ };
194
+ const assertStageCanMutateWithPolicy = (projectId, bundleId, progressionRunId, ticket, mutationLabel, canMutateLive) => {
164
195
  const live = loadLiveState(projectId, bundleId);
165
196
  const progression = getAutomationProgression(projectId, bundleId);
166
197
  const state = getBundleAutomationState(projectId, bundleId);
@@ -169,12 +200,12 @@ const assertStageCanMutate = (projectId, bundleId, progressionRunId, requiredDep
169
200
  || progression.status !== 'running'
170
201
  || state?.automation_progression_run_id !== progressionRunId
171
202
  || 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`);
203
+ || !canMutateLive(live, { ticket, progressionRunId })) {
204
+ throw new Error(`automation no longer permits ${mutationLabel} mutation`);
175
205
  }
176
206
  return live;
177
207
  };
208
+ 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
209
  const githubConfigMatchesIntent = (githubConfig, intent) => (githubConfig.host.toLowerCase() === intent.repoHost.toLowerCase()
179
210
  && githubConfig.repo_owner.toLowerCase() === intent.repoOwner.toLowerCase()
180
211
  && githubConfig.repo_name.toLowerCase() === intent.repoName.toLowerCase()
@@ -322,6 +353,181 @@ const pauseBeforeStage = ({ projectId, bundleId, progressionRunId, pauseCode, re
322
353
  pausedStageRunId: null,
323
354
  broadcaster,
324
355
  });
356
+ export const runAutomationCommitOperation = async ({ ticket, io, progressionRunId, allowedActiveChildRunId, retryOfRunId, mutationLabel, canCommitLive, shouldCompleteProgression, failureMode = 'return', requiredProgressionPurpose, }) => {
357
+ if (!ticket.project_id || !ticket.worktree_bundle_id) {
358
+ return null;
359
+ }
360
+ const projectId = ticket.project_id;
361
+ const bundleId = ticket.worktree_bundle_id;
362
+ const progression = getAutomationProgression(projectId, bundleId);
363
+ if (!progression || progression.id !== progressionRunId) {
364
+ return null;
365
+ }
366
+ const broadcaster = asBroadcaster(io);
367
+ let live = loadLiveState(projectId, bundleId);
368
+ if (!canCommitLive(live, { ticket, progressionRunId })) {
369
+ completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
370
+ emitUpdated(io, ticket.id);
371
+ return null;
372
+ }
373
+ const branch = live.bundle.branch;
374
+ const cwd = live.bundle.kind === 'project_root'
375
+ ? live.project.repo_path
376
+ : resolveWorktreePath(branch, live.project);
377
+ let commitRunId = null;
378
+ try {
379
+ const status = getGitStatus(cwd, branch, live.project);
380
+ const precondition = evaluateCommitPrecondition(status);
381
+ if (!precondition.ok) {
382
+ throw new Error(precondition.reason);
383
+ }
384
+ if (status.files.length === 0) {
385
+ const authorization = await authorizeAutomationBoundary({
386
+ project: live.project,
387
+ bundle: live.bundle,
388
+ progressionRunId,
389
+ cwd,
390
+ boundaryKind: 'automation_commit',
391
+ requiredProgressionPurpose,
392
+ broadcaster,
393
+ prepare: () => {
394
+ const head = getGitHeadIdentity(cwd);
395
+ return { head, indexTreeSha: getGitIndexTreeSha(cwd) };
396
+ },
397
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
398
+ kind: 'workspace', fingerprint: workspaceIdentity.fingerprint,
399
+ }),
400
+ });
401
+ const { head, indexTreeSha } = authorization.value;
402
+ const intent = {
403
+ branch,
404
+ preActionHeadSha: head.headSha,
405
+ preActionIndexTreeSha: indexTreeSha,
406
+ intendedTreeSha: head.treeSha,
407
+ commitMessageDigest: EMPTY_MESSAGE_DIGEST,
408
+ selectedContentDigest: computeGitTreeDeltaDigest(cwd, head.headSha, head.treeSha),
409
+ checkAuthorization: toStageCheckAuthorization(authorization),
410
+ };
411
+ commitRunId = startStage({
412
+ projectId,
413
+ bundleId,
414
+ ticketId: ticket.id,
415
+ ticketTitle: ticket.title,
416
+ progressionRunId,
417
+ allowedActiveChildRunId,
418
+ stageKind: 'automation_commit',
419
+ intent,
420
+ broadcaster,
421
+ retryOfRunId,
422
+ });
423
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_commit' });
424
+ live = loadLiveState(projectId, bundleId);
425
+ const result = {
426
+ outcome: 'no_changes',
427
+ headSha: head.headSha,
428
+ treeSha: head.treeSha,
429
+ };
430
+ finishStage({
431
+ projectId,
432
+ bundleId,
433
+ progressionRunId,
434
+ runId: commitRunId,
435
+ stageKind: 'automation_commit',
436
+ result,
437
+ outcome: 'no_changes',
438
+ completeProgression: shouldCompleteProgression(live, result),
439
+ broadcaster,
440
+ });
441
+ return { runId: commitRunId, ...result };
442
+ }
443
+ const selectedPaths = getAutomationCommitPaths(status.files);
444
+ const message = await generateCommitMessage(cwd, live.project, selectedPaths);
445
+ live = loadLiveState(projectId, bundleId);
446
+ if (!message) {
447
+ throw new Error('commit message generation produced an empty result');
448
+ }
449
+ if (!canCommitLive(live, { ticket, progressionRunId })) {
450
+ completeAutomationProgression({ projectId, bundleId, progressionRunId, broadcaster });
451
+ emitUpdated(io, ticket.id);
452
+ return null;
453
+ }
454
+ const authorization = await authorizeAutomationBoundary({
455
+ project: live.project,
456
+ bundle: live.bundle,
457
+ progressionRunId,
458
+ cwd,
459
+ boundaryKind: 'automation_commit',
460
+ requiredProgressionPurpose,
461
+ broadcaster,
462
+ prepare: () => prepareAutomationCommit(cwd, branch, live.project, selectedPaths),
463
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
464
+ kind: 'workspace', fingerprint: workspaceIdentity.fingerprint,
465
+ }),
466
+ });
467
+ const prepared = authorization.value;
468
+ const intent = {
469
+ branch,
470
+ preActionHeadSha: prepared.preActionHeadSha,
471
+ preActionIndexTreeSha: prepared.preActionIndexTreeSha,
472
+ intendedTreeSha: prepared.intendedTreeSha,
473
+ commitMessageDigest: digestCommitMessage(message),
474
+ selectedContentDigest: prepared.selectedContentDigest,
475
+ checkAuthorization: toStageCheckAuthorization(authorization),
476
+ };
477
+ commitRunId = startStage({
478
+ projectId,
479
+ bundleId,
480
+ ticketId: ticket.id,
481
+ ticketTitle: ticket.title,
482
+ progressionRunId,
483
+ allowedActiveChildRunId,
484
+ stageKind: 'automation_commit',
485
+ intent,
486
+ broadcaster,
487
+ retryOfRunId,
488
+ });
489
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_commit' });
490
+ live = assertStageCanMutateWithPolicy(projectId, bundleId, progressionRunId, ticket, mutationLabel, canCommitLive);
491
+ const result = await commitPreparedAutomationChanges(cwd, branch, live.project, message, prepared);
492
+ live = loadLiveState(projectId, bundleId);
493
+ finishStage({
494
+ projectId,
495
+ bundleId,
496
+ progressionRunId,
497
+ runId: commitRunId,
498
+ stageKind: 'automation_commit',
499
+ result,
500
+ outcome: 'committed',
501
+ completeProgression: shouldCompleteProgression(live, result),
502
+ broadcaster,
503
+ });
504
+ return { outcome: 'committed', runId: commitRunId, ...result };
505
+ }
506
+ catch (error) {
507
+ if (error instanceof AutomationCheckGateError) {
508
+ if (commitRunId) {
509
+ failStageAuthorization({ projectId, bundleId, progressionRunId, runId: commitRunId, stageKind: 'automation_commit', error, broadcaster });
510
+ }
511
+ emitBundleUpdated(io, ticket);
512
+ if (failureMode === 'throw') {
513
+ throw error;
514
+ }
515
+ return null;
516
+ }
517
+ const reason = `commit failed: ${getErrorMessage(error)}`;
518
+ if (commitRunId) {
519
+ failStage({ projectId, bundleId, progressionRunId, runId: commitRunId, stageKind: 'automation_commit', pauseCode: 'commit', reason, ambiguous: true, broadcaster });
520
+ }
521
+ else {
522
+ pauseBeforeStage({ projectId, bundleId, progressionRunId, pauseCode: 'commit', reason, broadcaster });
523
+ }
524
+ emitUpdated(io, ticket.id);
525
+ if (failureMode === 'throw') {
526
+ throw error;
527
+ }
528
+ return null;
529
+ }
530
+ };
325
531
  const remoteStatesEqual = (left, right) => (left.state === right.state && (left.state === 'absent' || (right.state === 'observed' && left.sha === right.sha)));
326
532
  export const runAutomationCommitChain = async (ticket, _project, _bundle, _automationConfig, io, progressionRunId, resumeFrom, triggeringReviewRunId) => {
327
533
  if (!ticket.project_id || !ticket.worktree_bundle_id) {
@@ -342,143 +548,30 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
342
548
  || (resumeFrom.stageKind === 'automation_push' && !isRetry)));
343
549
  let expectedHeadSha = resumeFrom?.expectedHeadSha ?? null;
344
550
  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
551
  const branch = live.bundle.branch;
351
552
  const cwd = live.bundle.kind === 'project_root'
352
553
  ? live.project.repo_path
353
554
  : resolveWorktreePath(branch, live.project);
354
- let commitRunId = null;
355
555
  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);
556
+ const commitResult = await runAutomationCommitOperation({
557
+ ticket,
558
+ io,
559
+ progressionRunId,
560
+ allowedActiveChildRunId: triggeringReviewRunId,
561
+ retryOfRunId: resumeFrom?.stageKind === 'automation_commit' ? resumeFrom.retryOfRunId : undefined,
562
+ mutationLabel: 'commit',
563
+ canCommitLive: current => depthAtLeast(current.automationConfig.depth, 'auto_commit'),
564
+ shouldCompleteProgression: current => !depthAtLeast(current.automationConfig.depth, 'auto_push'),
565
+ });
566
+ if (!commitResult) {
480
567
  return;
481
568
  }
569
+ if (commitResult.outcome === 'committed') {
570
+ expectedHeadSha = commitResult.commitSha;
571
+ }
572
+ else {
573
+ expectedHeadSha = commitResult.headSha;
574
+ }
482
575
  }
483
576
  live = loadLiveState(projectId, bundleId);
484
577
  if (getAutomationProgression(projectId, bundleId)?.id !== progressionRunId) {
@@ -515,24 +608,23 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
515
608
  cwd,
516
609
  boundaryKind: 'automation_push',
517
610
  broadcaster,
518
- resolveIntendedIdentity: workspaceIdentity => {
611
+ prepare: () => {
519
612
  const head = getGitHeadIdentity(cwd);
520
613
  if (head.headSha !== target) {
521
614
  throw new Error('HEAD changed while required checks were evaluated for push');
522
615
  }
523
- return {
524
- intendedIdentity: { kind: 'commit', commitSha: target, workspaceFingerprint: workspaceIdentity.fingerprint },
525
- value: head,
526
- };
616
+ return head;
527
617
  },
618
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
619
+ kind: 'commit', commitSha: target, workspaceFingerprint: workspaceIdentity.fingerprint,
620
+ }),
528
621
  });
529
622
  const intent = {
530
623
  branch,
531
624
  originIdentity,
532
625
  targetLocalSha: target,
533
626
  preActionRemote,
534
- gateEvaluationId: authorization.evaluation.id,
535
- policyFingerprint: authorization.policyFingerprint,
627
+ checkAuthorization: toStageCheckAuthorization(authorization),
536
628
  intendedTreeSha: authorization.value.treeSha,
537
629
  resultingCommitSha: target,
538
630
  };
@@ -540,14 +632,14 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
540
632
  const current = await fetchRemoteBranchState(cwd, branch, originIdentity);
541
633
  live = loadLiveState(projectId, bundleId);
542
634
  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');
635
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_push' });
636
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_push');
545
637
  finishStage({ projectId, bundleId, progressionRunId, runId: pushRunId, stageKind: 'automation_push', result: { remoteSha: target }, outcome: 'already_current', completeProgression: !depthAtLeast(live.automationConfig.depth, 'auto_pr'), broadcaster });
546
638
  expectedHeadSha = target;
547
639
  }
548
640
  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');
641
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_push' });
642
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_push');
551
643
  await pushAutomationTarget(cwd, branch, target, originIdentity);
552
644
  live = loadLiveState(projectId, bundleId);
553
645
  const observed = await fetchRemoteBranchState(cwd, branch, originIdentity);
@@ -613,16 +705,16 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
613
705
  cwd,
614
706
  boundaryKind: 'automation_pull_request',
615
707
  broadcaster,
616
- resolveIntendedIdentity: workspaceIdentity => {
708
+ prepare: () => {
617
709
  const head = getGitHeadIdentity(cwd);
618
710
  if (head.headSha !== headSha) {
619
711
  throw new Error('HEAD changed while required checks were evaluated for pull request');
620
712
  }
621
- return {
622
- intendedIdentity: { kind: 'commit', commitSha: headSha, workspaceFingerprint: workspaceIdentity.fingerprint },
623
- value: head,
624
- };
713
+ return head;
625
714
  },
715
+ resolveIntendedIdentity: (_value, workspaceIdentity) => ({
716
+ kind: 'commit', commitSha: headSha, workspaceFingerprint: workspaceIdentity.fingerprint,
717
+ }),
626
718
  });
627
719
  const intent = {
628
720
  provider: 'github',
@@ -632,8 +724,7 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
632
724
  baseBranch: githubConfig.default_base_branch,
633
725
  headBranch: branch,
634
726
  headSha,
635
- gateEvaluationId: authorization.evaluation.id,
636
- policyFingerprint: authorization.policyFingerprint,
727
+ checkAuthorization: toStageCheckAuthorization(authorization),
637
728
  intendedTreeSha: authorization.value.treeSha,
638
729
  resultingCommitSha: headSha,
639
730
  };
@@ -643,8 +734,8 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
643
734
  let pullRequest;
644
735
  let outcome;
645
736
  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');
737
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_pull_request' });
738
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
648
739
  pullRequest = observation.pullRequest;
649
740
  outcome = 'already_exists';
650
741
  }
@@ -655,8 +746,8 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
655
746
  if (!currentConfig || !githubConfigMatchesIntent(currentConfig, intent)) {
656
747
  throw new Error('GitHub configuration changed while the pull-request stage was active');
657
748
  }
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' });
749
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
750
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_pull_request' });
660
751
  if (getGitHeadIdentity(cwd).headSha !== intent.headSha) {
661
752
  throw new Error('HEAD changed while the pull-request stage was active');
662
753
  }
@@ -668,12 +759,12 @@ export const runAutomationCommitChain = async (ticket, _project, _bundle, _autom
668
759
  if (!mutationConfig || !githubConfigMatchesIntent(mutationConfig, intent)) {
669
760
  throw new Error('GitHub configuration changed while the pull-request stage was active');
670
761
  }
671
- live = assertStageCanMutate(projectId, bundleId, progressionRunId, 'auto_pr');
762
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
672
763
  if (getGitHeadIdentity(cwd).headSha !== intent.headSha) {
673
764
  throw new Error('HEAD changed before pull-request creation');
674
765
  }
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');
766
+ await assertBoundaryAuthorizationCurrent({ authorization, project: live.project, bundle: live.bundle, progressionRunId, cwd, boundaryKind: 'automation_pull_request' });
767
+ live = assertStageCanMutate(projectId, bundleId, progressionRunId, ticket, 'auto_pr');
677
768
  pullRequest = await createGitHubPullRequest(mutationConfig, {
678
769
  title: content.title,
679
770
  body: content.body,