@straiffi/archon 1.8.21 → 1.8.23

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 (133) hide show
  1. package/dist/client/assets/{ActivityView-BcguXTZ5.js → ActivityView-BaIZ8eHC.js} +1 -1
  2. package/dist/client/assets/GitHubActionsDialog-BFCqikDY.js +1 -0
  3. package/dist/client/assets/VerificationDialog-B1_6HMdt.js +4 -0
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-DnC7Twj0.js → architectureDiagram-3BPJPVTR-Dl5UF67P.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-D0VbjiKd.js → blockDiagram-GPEHLZMM-DCcERfrZ.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-CzQ3l15K.js → c4Diagram-AAUBKEIU-BGhbASFm.js} +1 -1
  7. package/dist/client/assets/channel-BfskvH2B.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-B5UGiqLW.js → chunk-2J33WTMH-C1ARozTV.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-SCksTqtu.js → chunk-3OPIFGDE-BEovLizF.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-IOkwLOtG.js → chunk-5ZQYHXKU-C5MmlLa_.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-Do60Xs9p.js → chunk-727SXJPM-8y8jBk4n.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-CuQT3imB.js → chunk-AQP2D5EJ-q-mByAd3.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-BkxMIESX.js → chunk-CSCIHK7Q-BOx2mLSz.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-uUU3sWgR.js → chunk-KSCS5N6A-zEwQFq7q.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-DLYccV6J.js → chunk-L5ZTLDWV-C80z6rY_.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-CgJFfCRZ.js → chunk-LZXEDZCA-BLaEsGPd.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-D7IQSQfK.js → chunk-ND2GUHAM-x26BZ95L.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-B4O6JWAo.js → chunk-NZK2D7GU-D2WAxr1p.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-DidlQEqb.js → chunk-O5CBEL6O-DDPCrzKh.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-2-oq_Gue.js → chunk-WU5MYG2G-CE-JcvIc.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-D4HPfxL3.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DQQ_WqAU.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-TubuN0hR.js → dagre-BM42HDAG-Bf7wpUk_.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-DNcUk6rV.js → diagram-2AECGRRQ-BQoKmc3B.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-B1sJn4w_.js → diagram-5GNKFQAL-D2jarPLn.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-CnW6ZuQe.js → diagram-KO2AKTUF-Bcbbjvsl.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-YHUmDzyS.js → diagram-LMA3HP47-t862RRBQ.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-BkOQphxq.js → diagram-OG6HWLK6-CacbMcO0.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-oNMxAdxN.js → erDiagram-TEJ5UH35-DRQ79baI.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-ByuCV-Am.js → flowDiagram-I6XJVG4X-BfW4KEdc.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CRkNH8Ft.js → ganttDiagram-6RSMTGT7-BPIl0TAj.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DRXzXjd8.js → gitGraphDiagram-PVQCEYII-CRIusBMV.js} +1 -1
  33. package/dist/client/assets/index-CZ1z72fo.js +276 -0
  34. package/dist/client/assets/index-SMj5ZmMs.css +2 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-CJ4-hWch.js → infoDiagram-5YYISTIA-DTKzK8EJ.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-CW1xqvim.js → ishikawaDiagram-YF4QCWOH-DVIHZLvL.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-BVVFg8_h.js → journeyDiagram-JHISSGLW-CQBZ4tCx.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-AcLZ63tA.js → kanban-definition-UN3LZRKU-D7naegR0.js} +1 -1
  39. package/dist/client/assets/{line-CG_TG6Ci.js → line-CizZlaeV.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-UDLSm_c3.js → mermaid-parser.core-NrDqgpZa.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-IvBk22DF.js → mermaid.core-CwVO3oX0.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-BflWNtnl.js → mindmap-definition-RKZ34NQL-BWxy3Aa_.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-Dm1iGMm7.js → pieDiagram-4H26LBE5-B3gXpmBn.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-CM4zJd_R.js → quadrantDiagram-W4KKPZXB-Dpp4g8Al.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-miuP86cp.js → requirementDiagram-4Y6WPE33-lFKXnxml.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-zUcRagtr.js → sankeyDiagram-5OEKKPKP-BBdtwqrS.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BRYqVpsT.js → sequenceDiagram-3UESZ5HK-ANpVZzqm.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-DwcF0DJm.js → stateDiagram-AJRCARHV-D58ZxmKY.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Cv8cmDfz.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-BzH929s_.js → timeline-definition-PNZ67QCA-B6FK8IQC.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-BIq8rooy.js → vennDiagram-CIIHVFJN-lFWtZEEY.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-CyV_gL5J.js → wardleyDiagram-YWT4CUSO-DteAVp4l.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-Dxo-U-qe.js → xychartDiagram-2RQKCTM6-CNcDlRLN.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/db.js +359 -21
  56. package/dist/server/db.js.map +1 -1
  57. package/dist/server/index.js +152 -4
  58. package/dist/server/index.js.map +1 -1
  59. package/dist/server/lib/approvalReconciliation.js +101 -0
  60. package/dist/server/lib/approvalReconciliation.js.map +1 -0
  61. package/dist/server/lib/approvalRequests.js +587 -0
  62. package/dist/server/lib/approvalRequests.js.map +1 -0
  63. package/dist/server/lib/approvalRoutes.js +143 -0
  64. package/dist/server/lib/approvalRoutes.js.map +1 -0
  65. package/dist/server/lib/buildExecutionEnvironment.js +128 -11
  66. package/dist/server/lib/buildExecutionEnvironment.js.map +1 -1
  67. package/dist/server/lib/checkDefinitions.js +29 -1
  68. package/dist/server/lib/checkDefinitions.js.map +1 -1
  69. package/dist/server/lib/checkRunner.js +306 -27
  70. package/dist/server/lib/checkRunner.js.map +1 -1
  71. package/dist/server/lib/checkRuns.js +4 -2
  72. package/dist/server/lib/checkRuns.js.map +1 -1
  73. package/dist/server/lib/consequentialActionAuthority.js +142 -0
  74. package/dist/server/lib/consequentialActionAuthority.js.map +1 -0
  75. package/dist/server/lib/consequentialActions.js +231 -0
  76. package/dist/server/lib/consequentialActions.js.map +1 -0
  77. package/dist/server/lib/dockerSandboxEnvironmentOperations.js +119 -0
  78. package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -1
  79. package/dist/server/lib/dockerSandboxExecutionEnvironment.js +194 -14
  80. package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -1
  81. package/dist/server/lib/dockerSandboxExecutionProvider.js +1 -1
  82. package/dist/server/lib/dockerSandboxExecutionProvider.js.map +1 -1
  83. package/dist/server/lib/dockerSandboxNetworkPolicy.js +222 -0
  84. package/dist/server/lib/dockerSandboxNetworkPolicy.js.map +1 -0
  85. package/dist/server/lib/executionEnvironmentContexts.js +4 -2
  86. package/dist/server/lib/executionEnvironmentContexts.js.map +1 -1
  87. package/dist/server/lib/executionEnvironmentLifecycle.js +34 -0
  88. package/dist/server/lib/executionEnvironmentLifecycle.js.map +1 -1
  89. package/dist/server/lib/executionEnvironmentRoutes.js +68 -6
  90. package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -1
  91. package/dist/server/lib/executionEnvironmentValidation.js +71 -5
  92. package/dist/server/lib/executionEnvironmentValidation.js.map +1 -1
  93. package/dist/server/lib/executionEnvironmentValidationOperations.js +155 -11
  94. package/dist/server/lib/executionEnvironmentValidationOperations.js.map +1 -1
  95. package/dist/server/lib/executionEnvironments.js +11 -5
  96. package/dist/server/lib/executionEnvironments.js.map +1 -1
  97. package/dist/server/lib/executionProfileStore.js +12 -3
  98. package/dist/server/lib/executionProfileStore.js.map +1 -1
  99. package/dist/server/lib/executionProfiles.js +131 -8
  100. package/dist/server/lib/executionProfiles.js.map +1 -1
  101. package/dist/server/lib/executionReconciliation.js +206 -143
  102. package/dist/server/lib/executionReconciliation.js.map +1 -1
  103. package/dist/server/lib/executionRunAuthority.js +175 -7
  104. package/dist/server/lib/executionRunAuthority.js.map +1 -1
  105. package/dist/server/lib/executionRuns.js +26 -4
  106. package/dist/server/lib/executionRuns.js.map +1 -1
  107. package/dist/server/lib/executionTimeline.js +2 -1
  108. package/dist/server/lib/executionTimeline.js.map +1 -1
  109. package/dist/server/lib/git.js +52 -10
  110. package/dist/server/lib/git.js.map +1 -1
  111. package/dist/server/lib/gitPushApprovals.js +367 -0
  112. package/dist/server/lib/gitPushApprovals.js.map +1 -0
  113. package/dist/server/lib/mobileAccess.js +115 -1
  114. package/dist/server/lib/mobileAccess.js.map +1 -1
  115. package/dist/server/lib/productionCheckApprovals.js +106 -0
  116. package/dist/server/lib/productionCheckApprovals.js.map +1 -0
  117. package/dist/server/lib/reviewFindings.js +14 -8
  118. package/dist/server/lib/reviewFindings.js.map +1 -1
  119. package/dist/server/lib/types.js +40 -0
  120. package/dist/server/lib/types.js.map +1 -1
  121. package/dist/server/lib/workspaceProcessEnvironment.js +2 -2
  122. package/dist/server/lib/workspaceProcessEnvironment.js.map +1 -1
  123. package/dist/server/workers/review.js +19 -11
  124. package/dist/server/workers/review.js.map +1 -1
  125. package/package.json +1 -1
  126. package/dist/client/assets/GitHubActionsDialog-BGecTzoq.js +0 -1
  127. package/dist/client/assets/VerificationDialog-D4QkMLZr.js +0 -4
  128. package/dist/client/assets/channel-BwRO4uiw.js +0 -1
  129. package/dist/client/assets/classDiagram-4FO5ZUOK-BIszcze-.js +0 -1
  130. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-D5dvxldS.js +0 -1
  131. package/dist/client/assets/index-CYPgEn1h.css +0 -2
  132. package/dist/client/assets/index-Ck2OKMsl.js +0 -276
  133. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-DmICWsmn.js +0 -1
@@ -0,0 +1,367 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { consumeApprovalRequest, createApprovalRequestInTransaction, emitApprovalRequestCreated, getApprovalRequest, invalidateApprovalRequest } from './approvalRequests.js';
3
+ import { consequentialActionAuthorityBinding, createConsequentialActionAuthority } from './consequentialActionAuthority.js';
4
+ import { getExecutionRun, transitionExecutionRun, withExecutionRunTransaction } from './executionRuns.js';
5
+ import { fetchRemoteBranchState, getAutomationRemoteIdentity, getCurrentGitBranch, pushCurrentBranch, resolveExistingWorktreePath, } from './git.js';
6
+ import { getProjectById } from './projects.js';
7
+ import { autoParkTicketIfStale, clearAutoParkDismissalIfNeeded, getBundle, getTicket, isProjectRootBundle, listTicketsInRunContext, } from './tickets.js';
8
+ import { captureWorkspaceIdentity } from './workspaceIdentity.js';
9
+ export const GIT_PUSH_APPROVAL_TTL_MS = 15 * 60 * 1000;
10
+ const originFingerprint = (identity) => {
11
+ const match = /^sha256:([0-9a-f]{64})$/.exec(identity);
12
+ if (!match?.[1]) {
13
+ throw new Error('Origin identity is unavailable');
14
+ }
15
+ return match[1];
16
+ };
17
+ const canonicalWorkspace = (cwd) => realpathSync(cwd);
18
+ const resolveCurrentWorkspace = (request) => {
19
+ if (request.action.kind !== 'git_push') {
20
+ throw new Error('Approval request is not a Git push');
21
+ }
22
+ const target = request.action.target;
23
+ if (!target.project_id || !target.source_kind || !target.source_id || !target.target_kind || target.bundle_id === undefined) {
24
+ throw new Error('Git push approval context is incomplete');
25
+ }
26
+ const project = getProjectById(target.project_id);
27
+ if (!project || project.id !== request.project_id) {
28
+ throw new Error('Git push project is unavailable');
29
+ }
30
+ if (target.source_kind === 'project') {
31
+ if (target.source_id !== project.id) {
32
+ throw new Error('Git push project context changed');
33
+ }
34
+ if (project.active_target_kind !== target.target_kind
35
+ || (project.active_target_bundle_id ?? null) !== target.bundle_id) {
36
+ throw new Error('Selected project target changed');
37
+ }
38
+ }
39
+ else {
40
+ const ticket = getTicket(target.source_id);
41
+ if (!ticket || ticket.project_id !== project.id || (ticket.worktree_bundle_id ?? null) !== target.bundle_id) {
42
+ throw new Error('Git push ticket or bundle context changed');
43
+ }
44
+ }
45
+ let cwd;
46
+ let branch;
47
+ if (target.target_kind === 'bundle') {
48
+ if (!target.bundle_id) {
49
+ throw new Error('Git push bundle context is incomplete');
50
+ }
51
+ const bundle = getBundle(target.bundle_id, project.id);
52
+ const path = bundle && !isProjectRootBundle(bundle)
53
+ ? resolveExistingWorktreePath(bundle.branch, project)
54
+ : null;
55
+ if (!bundle || !path) {
56
+ throw new Error('Git push bundle workspace is unavailable');
57
+ }
58
+ cwd = path;
59
+ branch = bundle.branch;
60
+ }
61
+ else if (target.target_kind === 'ticket') {
62
+ const ticket = target.source_kind === 'ticket' ? getTicket(target.source_id) : null;
63
+ const path = ticket?.branch ? resolveExistingWorktreePath(ticket.branch, project) : null;
64
+ if (!ticket || ticket.worktree_bundle_id || !ticket.branch || !path) {
65
+ throw new Error('Git push ticket workspace is unavailable');
66
+ }
67
+ cwd = path;
68
+ branch = ticket.branch;
69
+ }
70
+ else {
71
+ if (target.source_kind === 'ticket' && target.bundle_id) {
72
+ const bundle = getBundle(target.bundle_id, project.id);
73
+ if (!bundle || !isProjectRootBundle(bundle)) {
74
+ throw new Error('Git push project-root bundle context changed');
75
+ }
76
+ }
77
+ cwd = project.repo_path;
78
+ branch = request.action.workspace_git.branch ?? '';
79
+ }
80
+ if (!branch || canonicalWorkspace(cwd) !== request.action.workspace_git.workspace_path) {
81
+ throw new Error('Git push workspace was moved or recanonicalized');
82
+ }
83
+ return { project, cwd: canonicalWorkspace(cwd), branch };
84
+ };
85
+ const buildAction = async (input) => {
86
+ const cwd = canonicalWorkspace(input.cwd);
87
+ if (getCurrentGitBranch(cwd) !== input.branch) {
88
+ throw new Error('Git push branch no longer matches the reviewed branch');
89
+ }
90
+ const identity = await captureWorkspaceIdentity(cwd);
91
+ if (identity.state !== 'available' || identity.components.head === 'unborn') {
92
+ throw new Error(`Git push workspace identity is unavailable${identity.state === 'unavailable' ? `: ${identity.message}` : ''}`);
93
+ }
94
+ const originIdentity = getAutomationRemoteIdentity(cwd);
95
+ const remote = await fetchRemoteBranchState(cwd, input.branch, originIdentity);
96
+ const capabilities = ['remote_git_write'];
97
+ return {
98
+ schema_version: 1,
99
+ kind: 'git_push',
100
+ workspace_git: {
101
+ workspace_path: cwd,
102
+ branch: input.branch,
103
+ head_sha: identity.components.head,
104
+ remote_head_sha: remote.state === 'observed' ? remote.sha : null,
105
+ state_fingerprint: identity.fingerprint,
106
+ },
107
+ authority: consequentialActionAuthorityBinding(input.authority),
108
+ logical_secret_references: ['git:origin'],
109
+ target: {
110
+ remote_name: 'origin',
111
+ remote_url_fingerprint: originFingerprint(originIdentity),
112
+ ref: `refs/heads/${input.branch}`,
113
+ force: true,
114
+ project_id: input.project.id,
115
+ source_kind: input.source.kind,
116
+ source_id: input.source.id,
117
+ target_kind: input.source.targetKind,
118
+ bundle_id: input.source.bundleId,
119
+ effective_git_authority: 'remote_git_write',
120
+ capabilities,
121
+ degradations: [],
122
+ expires_at: input.expiresAt,
123
+ },
124
+ };
125
+ };
126
+ const requestContext = (request) => {
127
+ if (request.action.kind !== 'git_push'
128
+ || !request.action.target.source_kind
129
+ || !request.action.target.source_id
130
+ || !request.action.target.target_kind
131
+ || request.action.target.bundle_id === undefined) {
132
+ throw new Error('Git push approval context is incomplete');
133
+ }
134
+ return {
135
+ kind: request.action.target.source_kind,
136
+ id: request.action.target.source_id,
137
+ targetKind: request.action.target.target_kind,
138
+ bundleId: request.action.target.bundle_id,
139
+ };
140
+ };
141
+ const finish = (request, status, domainStatus, emitter, failure) => {
142
+ const run = getExecutionRun(request.project_id, request.execution_run_id);
143
+ if (!run || ['completed', 'failed', 'cancelled', 'interrupted'].includes(run.status)) {
144
+ return run;
145
+ }
146
+ return transitionExecutionRun({
147
+ projectId: request.project_id,
148
+ runId: request.execution_run_id,
149
+ status,
150
+ domainStatus,
151
+ failure: failure ?? null,
152
+ eventType: `git_push_${domainStatus}`,
153
+ }, emitter);
154
+ };
155
+ const reconcileTicketContext = (request, emitter) => {
156
+ if (request.action.kind !== 'git_push' || request.action.target.source_kind !== 'ticket') {
157
+ return;
158
+ }
159
+ const ticket = getTicket(request.action.target.source_id ?? '');
160
+ if (!ticket) {
161
+ return;
162
+ }
163
+ for (const previous of listTicketsInRunContext(ticket)) {
164
+ clearAutoParkDismissalIfNeeded(previous);
165
+ const updated = autoParkTicketIfStale(previous.id);
166
+ if (updated) {
167
+ emitter?.emit('ticket:updated', updated);
168
+ }
169
+ }
170
+ };
171
+ const reconcileTicketContextAfterOutcome = (request, emitter) => {
172
+ try {
173
+ reconcileTicketContext(request, emitter);
174
+ }
175
+ catch {
176
+ // Ticket housekeeping is secondary and must not change the persisted push outcome.
177
+ }
178
+ };
179
+ const expectedRemote = (action) => (action.workspace_git.remote_head_sha
180
+ ? { state: 'observed', sha: action.workspace_git.remote_head_sha }
181
+ : { state: 'absent' });
182
+ const remoteMatches = (left, right) => (left.state === right.state && (left.state === 'absent' || (right.state === 'observed' && left.sha === right.sha)));
183
+ const observe = async (request) => {
184
+ if (request.action.kind !== 'git_push') {
185
+ throw new Error('Approval request is not a Git push');
186
+ }
187
+ const workspacePath = canonicalWorkspace(request.action.workspace_git.workspace_path);
188
+ if (workspacePath !== request.action.workspace_git.workspace_path) {
189
+ throw new Error('Git push workspace was moved or recanonicalized');
190
+ }
191
+ const branch = request.action.workspace_git.branch;
192
+ if (!branch || request.action.target.ref !== `refs/heads/${branch}`) {
193
+ throw new Error('Git push branch evidence is unavailable');
194
+ }
195
+ const identity = `sha256:${request.action.target.remote_url_fingerprint}`;
196
+ return fetchRemoteBranchState(workspacePath, branch, identity);
197
+ };
198
+ export const admitForcePush = async (input) => {
199
+ const now = input.now ?? new Date();
200
+ const expiresAt = new Date(now.getTime() + GIT_PUSH_APPROVAL_TTL_MS).toISOString();
201
+ const authority = createConsequentialActionAuthority({
202
+ projectId: input.project.id,
203
+ effectiveCapabilities: ['remote_git_write'],
204
+ });
205
+ const action = await buildAction({ ...input, authority, expiresAt });
206
+ let executionRun;
207
+ let approvalRequest;
208
+ withExecutionRunTransaction(transaction => {
209
+ executionRun = transaction.createExecutionRun({
210
+ projectId: input.project.id,
211
+ kind: 'git_push',
212
+ initiatedBy: 'user',
213
+ sourceReference: { kind: input.source.kind, id: input.source.id },
214
+ links: input.source.bundleId
215
+ ? [{ entityType: 'bundle', entityId: input.source.bundleId, relationship: 'context' }]
216
+ : [],
217
+ domainStatus: 'admitted',
218
+ metadata: {
219
+ target_kind: input.source.targetKind,
220
+ workspace_path: action.workspace_git.workspace_path,
221
+ branch: input.branch,
222
+ },
223
+ eventType: 'git_push_admitted',
224
+ });
225
+ approvalRequest = createApprovalRequestInTransaction({
226
+ projectId: input.project.id,
227
+ executionRunId: executionRun.id,
228
+ action,
229
+ authority,
230
+ expiresAt,
231
+ }, transaction);
232
+ }, input.emitter);
233
+ emitApprovalRequestCreated(input.emitter, approvalRequest);
234
+ return {
235
+ execution_run: getExecutionRun(input.project.id, executionRun.id) ?? executionRun,
236
+ approval_request: approvalRequest,
237
+ };
238
+ };
239
+ export const finalizeGitPushApproval = async (projectId, requestId, emitter) => {
240
+ const request = getApprovalRequest(projectId, requestId, { emitter });
241
+ if (!request || request.action_kind !== 'git_push') {
242
+ return false;
243
+ }
244
+ if (request.status === 'invalidated') {
245
+ finish(request, 'failed', 'invalidated', emitter, { code: 'git_push_stale', message: 'Force-push approval no longer matches the target state.' });
246
+ }
247
+ else if (request.status === 'expired') {
248
+ finish(request, 'cancelled', 'expired', emitter);
249
+ }
250
+ else if (request.status === 'denied' || request.status === 'cancelled') {
251
+ finish(request, 'cancelled', request.status, emitter);
252
+ }
253
+ return true;
254
+ };
255
+ export const resumeGitPushApproval = async (projectId, requestId, emitter) => {
256
+ let request = getApprovalRequest(projectId, requestId, { emitter });
257
+ if (!request || request.action_kind !== 'git_push' || request.status !== 'approved') {
258
+ return false;
259
+ }
260
+ try {
261
+ const workspace = resolveCurrentWorkspace(request);
262
+ const expiresAt = request.action.kind === 'git_push' ? request.action.target.expires_at : undefined;
263
+ if (!expiresAt) {
264
+ throw new Error('Git push approval expiry evidence is unavailable');
265
+ }
266
+ const currentAction = await buildAction({
267
+ ...workspace,
268
+ source: requestContext(request),
269
+ authority: request.authority,
270
+ expiresAt,
271
+ });
272
+ const consumed = consumeApprovalRequest({ projectId, requestId, currentAction, emitter });
273
+ if (!consumed.ok) {
274
+ if (consumed.request) {
275
+ await finalizeGitPushApproval(projectId, requestId, emitter);
276
+ }
277
+ return false;
278
+ }
279
+ request = consumed.request;
280
+ const action = request.action;
281
+ const originIdentity = `sha256:${action.target.remote_url_fingerprint}`;
282
+ try {
283
+ await pushCurrentBranch(workspace.cwd, workspace.branch, {
284
+ forceWithLease: true,
285
+ expectedRemoteSha: action.workspace_git.remote_head_sha,
286
+ targetSha: action.workspace_git.head_sha,
287
+ expectedOriginIdentity: originIdentity,
288
+ });
289
+ }
290
+ catch (error) {
291
+ const observed = await observe(request).catch(() => null);
292
+ if (!observed || !remoteMatches(observed, { state: 'observed', sha: action.workspace_git.head_sha })) {
293
+ const unchanged = observed && remoteMatches(observed, expectedRemote(action));
294
+ finish(request, unchanged ? 'failed' : 'interrupted', unchanged ? 'failed' : 'ambiguous', emitter, {
295
+ code: unchanged ? 'git_push_failed' : 'git_push_outcome_ambiguous',
296
+ message: error instanceof Error ? error.message : 'Force push failed with an uncertain outcome.',
297
+ });
298
+ return false;
299
+ }
300
+ }
301
+ let observed;
302
+ try {
303
+ observed = await observe(request);
304
+ }
305
+ catch (error) {
306
+ finish(request, 'interrupted', 'ambiguous', emitter, {
307
+ code: 'git_push_outcome_ambiguous',
308
+ message: error instanceof Error ? error.message : 'The remote force-push outcome could not be observed.',
309
+ });
310
+ return false;
311
+ }
312
+ if (!remoteMatches(observed, { state: 'observed', sha: action.workspace_git.head_sha })) {
313
+ finish(request, 'interrupted', 'ambiguous', emitter, {
314
+ code: 'git_push_outcome_ambiguous',
315
+ message: 'The exact remote force-push result could not be proven.',
316
+ });
317
+ return false;
318
+ }
319
+ finish(request, 'completed', 'succeeded', emitter);
320
+ reconcileTicketContextAfterOutcome(request, emitter);
321
+ return true;
322
+ }
323
+ catch (error) {
324
+ request = getApprovalRequest(projectId, requestId, { emitter }) ?? request;
325
+ if (request.status === 'approved') {
326
+ request = invalidateApprovalRequest({ projectId, requestId, reason: 'git_push_revalidation_failed', emitter });
327
+ }
328
+ finish(request, 'failed', 'invalidated', emitter, {
329
+ code: 'git_push_stale',
330
+ message: error instanceof Error ? error.message : 'Force-push target revalidation failed.',
331
+ });
332
+ return false;
333
+ }
334
+ };
335
+ export const reconcileConsumedGitPush = async (request, emitter) => {
336
+ if (request.action.kind !== 'git_push' || request.status !== 'consumed') {
337
+ return false;
338
+ }
339
+ try {
340
+ const remote = await observe(request);
341
+ if (remoteMatches(remote, { state: 'observed', sha: request.action.workspace_git.head_sha })) {
342
+ finish(request, 'completed', 'reconciled_succeeded', emitter);
343
+ reconcileTicketContextAfterOutcome(request, emitter);
344
+ return true;
345
+ }
346
+ if (remoteMatches(remote, expectedRemote(request.action))) {
347
+ finish(request, 'interrupted', 'reconciled_absent', emitter, {
348
+ code: 'git_push_side_effect_absent',
349
+ message: 'The consumed force push was not observed on the remote and was not replayed.',
350
+ });
351
+ return false;
352
+ }
353
+ finish(request, 'interrupted', 'reconciled_ambiguous', emitter, {
354
+ code: 'git_push_outcome_ambiguous',
355
+ message: 'The remote branch differs from both approved states; the force push was not replayed.',
356
+ });
357
+ return false;
358
+ }
359
+ catch (error) {
360
+ finish(request, 'interrupted', 'reconciled_ambiguous', emitter, {
361
+ code: 'git_push_outcome_ambiguous',
362
+ message: error instanceof Error ? error.message : 'The remote force-push outcome could not be observed.',
363
+ });
364
+ return false;
365
+ }
366
+ };
367
+ //# sourceMappingURL=gitPushApprovals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitPushApprovals.js","sourceRoot":"","sources":["../../../server/lib/gitPushApprovals.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,9 @@ import { submitPlanTicketFollowUp, submitTicketFollowUp } from './ticketFollowUp
15
15
  import { resolveTicketPlanQuestionSet } from './planQuestionOperations.js';
16
16
  import { updateTicketAgentSettings } from './ticketSettingsOperations.js';
17
17
  import { listAgentRunsForParent } from './agentRuns.js';
18
+ import { ApprovalRequestConflictError, ApprovalRequestCursorError, decideApprovalRequest, getApprovalRequest, listApprovalRequestEvents, listApprovalRequests } from './approvalRequests.js';
19
+ import { finalizeProductionCheckApproval, resumeProductionCheckApproval } from './checkRunner.js';
20
+ import { finalizeGitPushApproval, resumeGitPushApproval } from './gitPushApprovals.js';
18
21
  const DEFAULT_MOBILE_PORT = 43111;
19
22
  const MOBILE_PORT_FALLBACK_COUNT = 20;
20
23
  const PAIRING_TTL_MS = 2 * 60 * 1000;
@@ -37,6 +40,10 @@ const MOBILE_REALTIME_EVENTS = new Set([
37
40
  'spec-session:updated',
38
41
  'spec-session:deleted',
39
42
  'agent-runs:updated',
43
+ 'approval-request:created',
44
+ 'approval-request:updated',
45
+ 'execution-run:updated',
46
+ 'lifecycle-event:created',
40
47
  ]);
41
48
  const hashSecret = (value) => {
42
49
  return createHash('sha256').update(value).digest('hex');
@@ -887,12 +894,119 @@ export const createMobileAccessController = (options = {}) => {
887
894
  res.status(404).json({ error: 'Not found' });
888
895
  return;
889
896
  }
890
- res.json(summary);
897
+ const pendingApprovalRequests = [];
898
+ let cursor;
899
+ do {
900
+ const page = listApprovalRequests({
901
+ projectId: req.params.projectId,
902
+ statuses: ['pending'],
903
+ limit: 200,
904
+ cursor,
905
+ emitter: operationBroadcaster,
906
+ });
907
+ pendingApprovalRequests.push(...page.approval_requests);
908
+ cursor = page.next_cursor ?? undefined;
909
+ } while (cursor);
910
+ res.json({
911
+ ...summary,
912
+ ...(pendingApprovalRequests.length > 0 ? { pending_approval_requests: pendingApprovalRequests } : {}),
913
+ });
891
914
  }
892
915
  catch {
893
916
  res.status(500).json({ error: 'Unable to load project summary.' });
894
917
  }
895
918
  });
919
+ app.get('/mobile/projects/:projectId/approval-requests', (req, res) => {
920
+ if (!validateMobileRequest(req)) {
921
+ res.status(401).json({ error: 'Authentication required.' });
922
+ return;
923
+ }
924
+ const cursor = typeof req.query.cursor === 'string' ? req.query.cursor : undefined;
925
+ if (req.query.cursor !== undefined && (!cursor || cursor.length > 1024)) {
926
+ res.status(400).json({ error: 'Invalid approval request cursor', reason: 'invalid_cursor' });
927
+ return;
928
+ }
929
+ try {
930
+ res.json(listApprovalRequests({
931
+ projectId: req.params.projectId,
932
+ statuses: ['pending'],
933
+ limit: 200,
934
+ cursor,
935
+ emitter: operationBroadcaster,
936
+ }));
937
+ }
938
+ catch (error) {
939
+ if (error instanceof ApprovalRequestCursorError) {
940
+ res.status(400).json({ error: error.message, reason: 'invalid_cursor' });
941
+ return;
942
+ }
943
+ throw error;
944
+ }
945
+ });
946
+ app.get('/mobile/projects/:projectId/approval-requests/:requestId', (req, res) => {
947
+ if (!validateMobileRequest(req)) {
948
+ res.status(401).json({ error: 'Authentication required.' });
949
+ return;
950
+ }
951
+ const request = getApprovalRequest(req.params.projectId, req.params.requestId, { emitter: operationBroadcaster });
952
+ if (!request) {
953
+ res.status(404).json({ error: 'Approval request not found.' });
954
+ return;
955
+ }
956
+ res.json({ approval_request: request, events: listApprovalRequestEvents(req.params.projectId, req.params.requestId) });
957
+ });
958
+ for (const decision of ['approve', 'deny', 'cancel']) {
959
+ app.post(`/mobile/projects/:projectId/approval-requests/:requestId/${decision}`, async (req, res) => {
960
+ if (!requireMobileWriteSession(req, res)) {
961
+ return;
962
+ }
963
+ const fingerprint = req.body?.expected_action_fingerprint;
964
+ const reason = req.body?.reason;
965
+ if (typeof fingerprint !== 'string'
966
+ || !/^[0-9a-f]{64}$/.test(fingerprint)
967
+ || (reason !== undefined && (typeof reason !== 'string' || reason.trim().length === 0 || Buffer.byteLength(reason.trim(), 'utf8') > 2_000))) {
968
+ res.status(400).json({ error: 'Invalid approval decision request.' });
969
+ return;
970
+ }
971
+ try {
972
+ const request = decideApprovalRequest({
973
+ projectId: req.params.projectId,
974
+ requestId: req.params.requestId,
975
+ decision: decision === 'approve' ? 'approved' : decision === 'deny' ? 'denied' : 'cancelled',
976
+ expectedActionFingerprint: fingerprint,
977
+ actorReference: 'paired_mobile_operator',
978
+ reason,
979
+ emitter: operationBroadcaster,
980
+ });
981
+ if (request.action_kind === 'custom_check') {
982
+ if (request.status === 'approved') {
983
+ await resumeProductionCheckApproval(request.project_id, request.id, operationBroadcaster);
984
+ }
985
+ else {
986
+ await finalizeProductionCheckApproval(request.project_id, request.id, operationBroadcaster);
987
+ }
988
+ }
989
+ else if (request.status === 'approved') {
990
+ await resumeGitPushApproval(request.project_id, request.id, operationBroadcaster);
991
+ }
992
+ else {
993
+ await finalizeGitPushApproval(request.project_id, request.id, operationBroadcaster);
994
+ }
995
+ res.json({ approval_request: getApprovalRequest(request.project_id, request.id, { emitter: operationBroadcaster }) ?? request });
996
+ }
997
+ catch (error) {
998
+ if (error instanceof ApprovalRequestConflictError) {
999
+ res.status(409).json({ error: error.message, reason: error.reason, approval_request: error.request });
1000
+ return;
1001
+ }
1002
+ if (error instanceof Error && error.message.includes('not found')) {
1003
+ res.status(404).json({ error: 'Approval request not found.' });
1004
+ return;
1005
+ }
1006
+ throw error;
1007
+ }
1008
+ });
1009
+ }
896
1010
  app.get('/mobile/projects/:projectId/target-options', async (req, res) => {
897
1011
  const session = validateMobileRequest(req);
898
1012
  if (!session) {