@straiffi/archon 1.8.20 → 1.8.22
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.
- package/dist/client/assets/{ActivityView-DDE3U1zB.js → ActivityView-CdEPjmqD.js} +1 -1
- package/dist/client/assets/GitHubActionsDialog-CA189pr2.js +1 -0
- package/dist/client/assets/VerificationDialog-BPVkhKRj.js +4 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-oPUmRpZr.js → architectureDiagram-3BPJPVTR-BrZ08mqq.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-0sq1jkkp.js → blockDiagram-GPEHLZMM-b93XwJ6Z.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-BeDxMy7J.js → c4Diagram-AAUBKEIU-BKOdbZ_v.js} +1 -1
- package/dist/client/assets/channel-D0zqzBT_.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-D8VGsje2.js → chunk-2J33WTMH-D7Vxn_1m.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-DuTUTeBe.js → chunk-3OPIFGDE-B1zo2UZq.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-BzX5dPNW.js → chunk-5ZQYHXKU-BajvkDdQ.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-DS9putxN.js → chunk-727SXJPM-CSH7cVpV.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-CWzFZRBv.js → chunk-AQP2D5EJ-DE-wgUNP.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-DXZ-ADYW.js → chunk-CSCIHK7Q-Ba7JgCEf.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-BD7mL3KK.js → chunk-KSCS5N6A-0o3YWkHM.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-DmYQi9AP.js → chunk-L5ZTLDWV-DihWczP8.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-CZyFYHvS.js → chunk-LZXEDZCA-Bxnu-hme.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-BxdjvT9I.js → chunk-ND2GUHAM-CoaR5kmP.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-BwuZcZ_9.js → chunk-NZK2D7GU-By92Rd3d.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-CC-TZXr3.js → chunk-O5CBEL6O-D83Pkgfu.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-CJZJkca2.js → chunk-WU5MYG2G-v8OVf5Lz.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-ChhyvHH2.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Cl02kurK.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-D2L5RvPF.js → dagre-BM42HDAG-Dq1ALF7Z.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-DBJzxSqZ.js → diagram-2AECGRRQ-1t1_TKtu.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-J_1M-Wou.js → diagram-5GNKFQAL-DtvYRL6f.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-CmT1Vbmc.js → diagram-KO2AKTUF-PRNrSX5F.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DfPoJyQI.js → diagram-LMA3HP47-DaTX0DhT.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-D4uEb6vo.js → diagram-OG6HWLK6-C2p9p-92.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-l7yIFj0e.js → erDiagram-TEJ5UH35-DKzLtnp-.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-B3caTMes.js → flowDiagram-I6XJVG4X-GdL8lm6e.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-9Am36v9m.js → ganttDiagram-6RSMTGT7-Ok-4XYwT.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-B7OjYsv_.js → gitGraphDiagram-PVQCEYII-DSMrOCK7.js} +1 -1
- package/dist/client/assets/index-Bs8KsdV9.js +276 -0
- package/dist/client/assets/index-DdwM8Wgt.css +2 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-ZAcNejkW.js → infoDiagram-5YYISTIA-Dl4hRXis.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BaaJwuN0.js → ishikawaDiagram-YF4QCWOH-C1Dc1fTT.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-DjzTU3kM.js → journeyDiagram-JHISSGLW-CUscmJze.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-DYRchzub.js → kanban-definition-UN3LZRKU-C0nwYjQV.js} +1 -1
- package/dist/client/assets/{line-X0j9gj61.js → line-ByiuEIg_.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-DCKG87Vl.js → mermaid-parser.core-B-XdAyDR.js} +1 -1
- package/dist/client/assets/{mermaid.core-BxI5T2gs.js → mermaid.core-D_bIfuZD.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-BE7MZzQO.js → mindmap-definition-RKZ34NQL-BGNiPJDK.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-D3fTYovg.js → pieDiagram-4H26LBE5-CP6FpAoO.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-B35nXRdQ.js → quadrantDiagram-W4KKPZXB-dsAWBAQn.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-CLBcHvP0.js → requirementDiagram-4Y6WPE33-DJH-zHqF.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-DdBCxGHF.js → sankeyDiagram-5OEKKPKP-BJMnZrqH.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-KcLt0X1K.js → sequenceDiagram-3UESZ5HK-CzNjUSlM.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-Bq23ObLK.js → stateDiagram-AJRCARHV-jppMhOwh.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-CLR1WgdV.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-SK3oVUD9.js → timeline-definition-PNZ67QCA-BX2CI5H_.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-COVtAlEC.js → vennDiagram-CIIHVFJN-Bd1KZaXh.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BvfOOwga.js → wardleyDiagram-YWT4CUSO-D-0BNz49.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-DFnAD9k4.js → xychartDiagram-2RQKCTM6-DbYa0ZWK.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +780 -16
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +198 -20
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/agent.js +8 -4
- package/dist/server/lib/agent.js.map +1 -1
- package/dist/server/lib/agentProvider.js +8 -1
- package/dist/server/lib/agentProvider.js.map +1 -1
- package/dist/server/lib/approvalReconciliation.js +101 -0
- package/dist/server/lib/approvalReconciliation.js.map +1 -0
- package/dist/server/lib/approvalRequests.js +587 -0
- package/dist/server/lib/approvalRequests.js.map +1 -0
- package/dist/server/lib/approvalRoutes.js +143 -0
- package/dist/server/lib/approvalRoutes.js.map +1 -0
- package/dist/server/lib/automationProgression.js +44 -14
- package/dist/server/lib/automationProgression.js.map +1 -1
- package/dist/server/lib/buildCompletionContinuation.js +14 -3
- package/dist/server/lib/buildCompletionContinuation.js.map +1 -1
- package/dist/server/lib/buildExecutionEnvironment.js +350 -66
- package/dist/server/lib/buildExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/buildOpenQuestionOperations.js +47 -3
- package/dist/server/lib/buildOpenQuestionOperations.js.map +1 -1
- package/dist/server/lib/buildSequences.js +267 -18
- package/dist/server/lib/buildSequences.js.map +1 -1
- package/dist/server/lib/buildSetupAuthority.js +138 -0
- package/dist/server/lib/buildSetupAuthority.js.map +1 -0
- package/dist/server/lib/bundleReviewOperations.js +15 -4
- package/dist/server/lib/bundleReviewOperations.js.map +1 -1
- package/dist/server/lib/checkDefinitions.js +29 -1
- package/dist/server/lib/checkDefinitions.js.map +1 -1
- package/dist/server/lib/checkRunner.js +306 -27
- package/dist/server/lib/checkRunner.js.map +1 -1
- package/dist/server/lib/checkRuns.js +4 -2
- package/dist/server/lib/checkRuns.js.map +1 -1
- package/dist/server/lib/consequentialActionAuthority.js +142 -0
- package/dist/server/lib/consequentialActionAuthority.js.map +1 -0
- package/dist/server/lib/consequentialActions.js +231 -0
- package/dist/server/lib/consequentialActions.js.map +1 -0
- package/dist/server/lib/dockerSandboxCredentials.js +64 -10
- package/dist/server/lib/dockerSandboxCredentials.js.map +1 -1
- package/dist/server/lib/dockerSandboxEnvironmentOperations.js +119 -0
- package/dist/server/lib/dockerSandboxEnvironmentOperations.js.map +1 -1
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js +174 -11
- package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/dockerSandboxExecutionProvider.js +12 -3
- package/dist/server/lib/dockerSandboxExecutionProvider.js.map +1 -1
- package/dist/server/lib/dockerSandboxNetworkPolicy.js +222 -0
- package/dist/server/lib/dockerSandboxNetworkPolicy.js.map +1 -0
- package/dist/server/lib/executionCredentialCapabilities.js +106 -0
- package/dist/server/lib/executionCredentialCapabilities.js.map +1 -0
- package/dist/server/lib/executionEnvironment.js.map +1 -1
- package/dist/server/lib/executionEnvironmentContexts.js +59 -3
- package/dist/server/lib/executionEnvironmentContexts.js.map +1 -1
- package/dist/server/lib/executionEnvironmentLifecycle.js +34 -0
- package/dist/server/lib/executionEnvironmentLifecycle.js.map +1 -1
- package/dist/server/lib/executionEnvironmentRoutes.js +127 -9
- package/dist/server/lib/executionEnvironmentRoutes.js.map +1 -1
- package/dist/server/lib/executionEnvironmentValidation.js +169 -45
- package/dist/server/lib/executionEnvironmentValidation.js.map +1 -1
- package/dist/server/lib/executionEnvironmentValidationOperations.js +203 -19
- package/dist/server/lib/executionEnvironmentValidationOperations.js.map +1 -1
- package/dist/server/lib/executionEnvironments.js +103 -14
- package/dist/server/lib/executionEnvironments.js.map +1 -1
- package/dist/server/lib/executionProfileRoutes.js +67 -0
- package/dist/server/lib/executionProfileRoutes.js.map +1 -0
- package/dist/server/lib/executionProfileStore.js +117 -0
- package/dist/server/lib/executionProfileStore.js.map +1 -0
- package/dist/server/lib/executionProfiles.js +324 -0
- package/dist/server/lib/executionProfiles.js.map +1 -0
- package/dist/server/lib/executionReconciliation.js +345 -197
- package/dist/server/lib/executionReconciliation.js.map +1 -1
- package/dist/server/lib/executionRetry.js +24 -5
- package/dist/server/lib/executionRetry.js.map +1 -1
- package/dist/server/lib/executionRunAuthority.js +467 -0
- package/dist/server/lib/executionRunAuthority.js.map +1 -0
- package/dist/server/lib/executionRuns.js +30 -4
- package/dist/server/lib/executionRuns.js.map +1 -1
- package/dist/server/lib/executionTimeline.js +2 -1
- package/dist/server/lib/executionTimeline.js.map +1 -1
- package/dist/server/lib/git.js +77 -10
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/gitPushApprovals.js +367 -0
- package/dist/server/lib/gitPushApprovals.js.map +1 -0
- package/dist/server/lib/hostExecutionEnvironment.js +7 -1
- package/dist/server/lib/hostExecutionEnvironment.js.map +1 -1
- package/dist/server/lib/mobileAccess.js +115 -1
- package/dist/server/lib/mobileAccess.js.map +1 -1
- package/dist/server/lib/productionCheckApprovals.js +106 -0
- package/dist/server/lib/productionCheckApprovals.js.map +1 -0
- package/dist/server/lib/projects.js +1 -0
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/run.js +58 -2
- package/dist/server/lib/run.js.map +1 -1
- package/dist/server/lib/ticketFollowUpOperations.js +32 -6
- package/dist/server/lib/ticketFollowUpOperations.js.map +1 -1
- package/dist/server/lib/transcriptRuns.js +3 -0
- package/dist/server/lib/transcriptRuns.js.map +1 -1
- package/dist/server/lib/types.js +40 -0
- package/dist/server/lib/types.js.map +1 -1
- package/dist/server/lib/workspaceProcessEnvironment.js +72 -1
- package/dist/server/lib/workspaceProcessEnvironment.js.map +1 -1
- package/dist/server/workers/build.js +214 -46
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/followUp.js +74 -1
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/review.js +30 -13
- package/dist/server/workers/review.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/GitHubActionsDialog-CM1z-amZ.js +0 -1
- package/dist/client/assets/VerificationDialog-oHjNllh6.js +0 -4
- package/dist/client/assets/channel-CF8hkOdu.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-D6FiG9_B.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Bim05ijr.js +0 -1
- package/dist/client/assets/index-CsZfTdMO.css +0 -2
- package/dist/client/assets/index-DDefs3iM.js +0 -275
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BLzZdVo0.js +0 -1
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
2
|
import { spawn } from 'child_process';
|
|
3
|
+
import db from '../db.js';
|
|
3
4
|
import { existsSync, lstatSync, realpathSync } from 'fs';
|
|
4
|
-
import { relative, resolve } from 'path';
|
|
5
|
+
import { basename, relative, resolve } from 'path';
|
|
5
6
|
import { StringDecoder } from 'string_decoder';
|
|
6
|
-
import { findActiveCheckBatchForTarget, findActiveCheckAutoFixForTarget, canAutoFixStartCheckBatch, getCheckBatchDetails, inspectCheckBatchAggregation, listCheckAttemptDetails, rerunCheckBatch, setCheckBatchStopRequested, } from './checkRuns.js';
|
|
7
|
+
import { findActiveCheckBatchForTarget, findActiveCheckAutoFixForTarget, canAutoFixStartCheckBatch, getCheckBatchDetails, getCheckAttemptDetails, inspectCheckBatchAggregation, listCheckAttemptDetails, rerunCheckBatch, setCheckBatchStopRequested, } from './checkRuns.js';
|
|
7
8
|
import { getCheckDefinitionFingerprint } from './checkDefinitions.js';
|
|
8
9
|
import { captureCheckWorkspaceIdentity } from './checkWorkspaceIdentity.js';
|
|
9
10
|
import { getProjectById, getProjectCheckDefinitions } from './projects.js';
|
|
10
11
|
import { aggregateCheckBatch, completeCheckAttempt, startCheckAttempt, withExecutionRunTransaction, } from './executionRuns.js';
|
|
11
12
|
import { resolveCommandWorkspacePath } from './testCommandRunner.js';
|
|
12
13
|
import { createWorkspaceProcessEnvironment } from './workspaceProcessEnvironment.js';
|
|
14
|
+
import { createGovernedHostRuntimeEnvironment } from './workspaceProcessEnvironment.js';
|
|
15
|
+
import { cancelApprovalRequest, consumeApprovalRequest, createApprovalRequestInTransaction, emitApprovalRequestCreated, getApprovalRequest, invalidateApprovalRequest, registerApprovalRequestTerminalHandler, } from './approvalRequests.js';
|
|
16
|
+
import { fingerprintConsequentialAction } from './consequentialActions.js';
|
|
17
|
+
import { buildProductionCheckAction, PRODUCTION_CHECK_APPROVAL_TTL_MS } from './productionCheckApprovals.js';
|
|
13
18
|
import { appendCheckAttemptEvidence, appendCheckDiagnostics, completeCheckDiagnosticParser, initializeCheckDiagnosticParser, } from './checkDiagnostics.js';
|
|
14
19
|
import { createCheckDiagnosticParserSession } from './checkDiagnosticParsers.js';
|
|
15
20
|
const MAX_CONCURRENT_CHECK_ATTEMPTS = 2;
|
|
@@ -35,7 +40,7 @@ const processes = new Map();
|
|
|
35
40
|
const admittedEntries = new Map();
|
|
36
41
|
const invalidatedBatches = new Set();
|
|
37
42
|
const stoppedBatches = new Set();
|
|
38
|
-
const
|
|
43
|
+
const forcedAttemptOutcomes = new Map();
|
|
39
44
|
const batchWaiters = new Map();
|
|
40
45
|
let admittedAttempts = 0;
|
|
41
46
|
let acceptingAdmissions = true;
|
|
@@ -72,8 +77,26 @@ export const openCheckRunnerAdmissions = () => {
|
|
|
72
77
|
acceptingAdmissions = true;
|
|
73
78
|
};
|
|
74
79
|
const resolveShell = () => process.env.SHELL?.trim() || '/bin/zsh';
|
|
75
|
-
const
|
|
76
|
-
|
|
80
|
+
export const getCheckShellArguments = (shell, command, governedProductionEnvironment = false) => {
|
|
81
|
+
if (!governedProductionEnvironment) {
|
|
82
|
+
return ['-i', '-c', command];
|
|
83
|
+
}
|
|
84
|
+
const shellName = basename(shell);
|
|
85
|
+
if (shellName === 'zsh') {
|
|
86
|
+
return ['-f', '-c', command];
|
|
87
|
+
}
|
|
88
|
+
if (shellName === 'bash') {
|
|
89
|
+
return ['--noprofile', '--norc', '-c', command];
|
|
90
|
+
}
|
|
91
|
+
if (shellName === 'fish') {
|
|
92
|
+
return ['--no-config', '-c', command];
|
|
93
|
+
}
|
|
94
|
+
return ['-c', command];
|
|
95
|
+
};
|
|
96
|
+
const createShellEnvironment = (cwd, definition, governedProductionEnvironment = false) => {
|
|
97
|
+
const env = definition.consequence === 'production' && governedProductionEnvironment
|
|
98
|
+
? createGovernedHostRuntimeEnvironment(cwd).variables
|
|
99
|
+
: createWorkspaceProcessEnvironment(cwd);
|
|
77
100
|
delete env.PORT;
|
|
78
101
|
return env;
|
|
79
102
|
};
|
|
@@ -84,7 +107,7 @@ const isPathInside = (root, candidate) => {
|
|
|
84
107
|
const pathFromRoot = relative(root, candidate).replaceAll('\\', '/');
|
|
85
108
|
return pathFromRoot === '' || (!pathFromRoot.startsWith('..') && !pathFromRoot.includes('/../'));
|
|
86
109
|
};
|
|
87
|
-
const resolveDefinitionCwd = (workspaceCwd, definition) => {
|
|
110
|
+
export const resolveDefinitionCwd = (workspaceCwd, definition) => {
|
|
88
111
|
const workspacePath = resolveCommandWorkspacePath(definition);
|
|
89
112
|
if (!workspacePath) {
|
|
90
113
|
return workspaceCwd;
|
|
@@ -300,6 +323,26 @@ const terminateProcess = (record) => {
|
|
|
300
323
|
record.killTimer = setTimeout(() => signalProcessGroup(record.child, 'SIGKILL'), STOP_GRACE_MS);
|
|
301
324
|
}
|
|
302
325
|
};
|
|
326
|
+
const waitForProcessFinalization = (record) => {
|
|
327
|
+
if (record.finalizationPromise) {
|
|
328
|
+
return record.finalizationPromise;
|
|
329
|
+
}
|
|
330
|
+
return new Promise((resolve, reject) => {
|
|
331
|
+
const waitForFinalization = () => {
|
|
332
|
+
const finalizationPromise = record.finalizationPromise;
|
|
333
|
+
if (!finalizationPromise) {
|
|
334
|
+
reject(new Error(`Check attempt ${record.attemptRunId} closed without starting finalization`));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
void finalizationPromise.then(resolve, reject);
|
|
338
|
+
};
|
|
339
|
+
record.child.once('close', waitForFinalization);
|
|
340
|
+
if (record.finalizationPromise) {
|
|
341
|
+
record.child.off('close', waitForFinalization);
|
|
342
|
+
void record.finalizationPromise.then(resolve, reject);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
};
|
|
303
346
|
const notifyBatchWaiters = (projectId, batchRunId) => {
|
|
304
347
|
const details = getCheckBatchDetails(projectId, batchRunId);
|
|
305
348
|
if (!details || !['completed', 'failed', 'cancelled', 'interrupted'].includes(details.execution_status)) {
|
|
@@ -418,7 +461,7 @@ const failQueuedBatch = async (batchRunId, outcome, identity) => {
|
|
|
418
461
|
};
|
|
419
462
|
const runQueuedAttempt = async (entry) => {
|
|
420
463
|
const identity = await captureCheckWorkspaceIdentity(entry.cwd);
|
|
421
|
-
const forcedOutcome =
|
|
464
|
+
const forcedOutcome = forcedAttemptOutcomes.get(entry.attemptRunId);
|
|
422
465
|
if (forcedOutcome) {
|
|
423
466
|
await completeAttempt(entry, forcedOutcome, identity, {
|
|
424
467
|
exitError: forcedOutcome === 'shutdown_loss'
|
|
@@ -440,6 +483,30 @@ const runQueuedAttempt = async (entry) => {
|
|
|
440
483
|
return;
|
|
441
484
|
}
|
|
442
485
|
const spawnCwd = resolveDefinitionCwd(entry.cwd, entry.definition);
|
|
486
|
+
if (entry.approvalRequestId) {
|
|
487
|
+
const request = getApprovalRequest(entry.projectId, entry.approvalRequestId, { emitter: entry.emitter });
|
|
488
|
+
const batch = getCheckBatchDetails(entry.projectId, entry.batchRunId);
|
|
489
|
+
if (!request || request.status !== 'consumed' || !batch) {
|
|
490
|
+
throw new Error('Production check approval is no longer consumable');
|
|
491
|
+
}
|
|
492
|
+
const currentDefinitions = getProjectCheckDefinitions(getProjectById(entry.projectId));
|
|
493
|
+
const currentDefinition = currentDefinitions.find(definition => definition.id === entry.definition.id);
|
|
494
|
+
if (!currentDefinition || getCheckDefinitionFingerprint(currentDefinition) !== getCheckDefinitionFingerprint(entry.definition)) {
|
|
495
|
+
throw new Error('Production check definition changed before spawn');
|
|
496
|
+
}
|
|
497
|
+
const current = buildProductionCheckAction({
|
|
498
|
+
projectId: entry.projectId,
|
|
499
|
+
workspaceCwd: entry.cwd,
|
|
500
|
+
effectiveCwd: spawnCwd,
|
|
501
|
+
definition: currentDefinition,
|
|
502
|
+
batch,
|
|
503
|
+
workspaceIdentity: identity,
|
|
504
|
+
expiresAt: request.expires_at,
|
|
505
|
+
});
|
|
506
|
+
if (fingerprintConsequentialAction(current.action) !== request.action_fingerprint) {
|
|
507
|
+
throw new Error('Production check action changed before spawn');
|
|
508
|
+
}
|
|
509
|
+
}
|
|
443
510
|
initializeCheckDiagnosticParser(entry.projectId, entry.attemptRunId);
|
|
444
511
|
startCheckAttempt({
|
|
445
512
|
projectId: entry.projectId,
|
|
@@ -448,10 +515,11 @@ const runQueuedAttempt = async (entry) => {
|
|
|
448
515
|
await new Promise((resolveAttempt, rejectAttempt) => {
|
|
449
516
|
let child;
|
|
450
517
|
try {
|
|
451
|
-
|
|
518
|
+
const shell = resolveShell();
|
|
519
|
+
child = spawn(shell, getCheckShellArguments(shell, entry.definition.command, entry.governedProductionEnvironment), {
|
|
452
520
|
cwd: spawnCwd,
|
|
453
521
|
detached: process.platform !== 'win32',
|
|
454
|
-
env: createShellEnvironment(entry.cwd),
|
|
522
|
+
env: createShellEnvironment(entry.cwd, entry.definition, entry.governedProductionEnvironment),
|
|
455
523
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
456
524
|
});
|
|
457
525
|
}
|
|
@@ -559,6 +627,8 @@ const enqueueCheckAttempts = (input) => {
|
|
|
559
627
|
definition: attempt.definition,
|
|
560
628
|
initialIdentity: input.initialIdentity,
|
|
561
629
|
emitter: input.emitter,
|
|
630
|
+
approvalRequestId: attempt.approvalRequestId,
|
|
631
|
+
governedProductionEnvironment: attempt.governedProductionEnvironment,
|
|
562
632
|
})));
|
|
563
633
|
pumpScheduler();
|
|
564
634
|
};
|
|
@@ -608,6 +678,7 @@ export const startCheckBatch = async (input, emitter) => withCheckAdmission(asyn
|
|
|
608
678
|
assertAcceptingAdmissions();
|
|
609
679
|
const batchRunId = randomUUID();
|
|
610
680
|
const attempts = input.selectedDefinitions.map(definition => ({ runId: randomUUID(), definition }));
|
|
681
|
+
const createdApprovals = [];
|
|
611
682
|
const checkTarget = input.targetKind === 'bundle'
|
|
612
683
|
? { targetKind: 'bundle', targetBundleId: targetBundleId }
|
|
613
684
|
: { targetKind: 'repo_root', targetBundleId: null };
|
|
@@ -665,6 +736,26 @@ export const startCheckBatch = async (input, emitter) => withCheckAdmission(asyn
|
|
|
665
736
|
},
|
|
666
737
|
});
|
|
667
738
|
}
|
|
739
|
+
const batch = getCheckBatchDetails(input.projectId, batchRunId);
|
|
740
|
+
for (const attempt of attempts.filter(candidate => candidate.definition.consequence === 'production')) {
|
|
741
|
+
const expiresAt = new Date(Date.now() + PRODUCTION_CHECK_APPROVAL_TTL_MS).toISOString();
|
|
742
|
+
const approval = buildProductionCheckAction({
|
|
743
|
+
projectId: input.projectId,
|
|
744
|
+
workspaceCwd: input.cwd,
|
|
745
|
+
effectiveCwd: resolveDefinitionCwd(input.cwd, attempt.definition),
|
|
746
|
+
definition: attempt.definition,
|
|
747
|
+
batch,
|
|
748
|
+
workspaceIdentity: initialIdentity,
|
|
749
|
+
expiresAt,
|
|
750
|
+
});
|
|
751
|
+
createdApprovals.push(createApprovalRequestInTransaction({
|
|
752
|
+
projectId: input.projectId,
|
|
753
|
+
executionRunId: attempt.runId,
|
|
754
|
+
action: approval.action,
|
|
755
|
+
authority: approval.authority,
|
|
756
|
+
expiresAt,
|
|
757
|
+
}, transaction));
|
|
758
|
+
}
|
|
668
759
|
input.onBatchCreated?.(batchRunId);
|
|
669
760
|
transaction.transitionExecutionRun({
|
|
670
761
|
projectId: input.projectId,
|
|
@@ -674,11 +765,15 @@ export const startCheckBatch = async (input, emitter) => withCheckAdmission(asyn
|
|
|
674
765
|
eventType: 'check_batch_started',
|
|
675
766
|
});
|
|
676
767
|
}, emitter);
|
|
768
|
+
for (const approval of createdApprovals) {
|
|
769
|
+
emitApprovalRequestCreated(emitter, approval);
|
|
770
|
+
}
|
|
771
|
+
const ordinaryAttempts = attempts.filter(attempt => attempt.definition.consequence !== 'production');
|
|
677
772
|
enqueueCheckAttempts({
|
|
678
773
|
projectId: input.projectId,
|
|
679
774
|
batchRunId,
|
|
680
775
|
cwd: input.cwd,
|
|
681
|
-
attempts,
|
|
776
|
+
attempts: ordinaryAttempts,
|
|
682
777
|
initialIdentity,
|
|
683
778
|
emitter,
|
|
684
779
|
});
|
|
@@ -711,19 +806,47 @@ export const rerunCheckBatchAndSchedule = async (input, emitter) => withCheckAdm
|
|
|
711
806
|
}
|
|
712
807
|
}
|
|
713
808
|
assertAcceptingAdmissions();
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
809
|
+
const createdApprovals = [];
|
|
810
|
+
const created = withExecutionRunTransaction(transaction => {
|
|
811
|
+
const result = rerunCheckBatch({
|
|
812
|
+
transaction,
|
|
813
|
+
projectId: input.projectId,
|
|
814
|
+
sourceBatchRunId: input.sourceBatchRunId,
|
|
815
|
+
selection: input.selection,
|
|
816
|
+
availableDefinitions: currentDefinitions,
|
|
817
|
+
initialWorkspaceIdentity: initialIdentity,
|
|
818
|
+
});
|
|
819
|
+
const batch = getCheckBatchDetails(input.projectId, result.batchRunId);
|
|
820
|
+
for (const attempt of result.attempts.filter(candidate => candidate.definition.consequence === 'production')) {
|
|
821
|
+
const expiresAt = new Date(Date.now() + PRODUCTION_CHECK_APPROVAL_TTL_MS).toISOString();
|
|
822
|
+
const approval = buildProductionCheckAction({
|
|
823
|
+
projectId: input.projectId,
|
|
824
|
+
workspaceCwd: input.cwd,
|
|
825
|
+
effectiveCwd: resolveDefinitionCwd(input.cwd, attempt.definition),
|
|
826
|
+
definition: attempt.definition,
|
|
827
|
+
batch,
|
|
828
|
+
workspaceIdentity: initialIdentity,
|
|
829
|
+
expiresAt,
|
|
830
|
+
});
|
|
831
|
+
createdApprovals.push(createApprovalRequestInTransaction({
|
|
832
|
+
projectId: input.projectId,
|
|
833
|
+
executionRunId: attempt.runId,
|
|
834
|
+
action: approval.action,
|
|
835
|
+
authority: approval.authority,
|
|
836
|
+
expiresAt,
|
|
837
|
+
}, transaction));
|
|
838
|
+
}
|
|
839
|
+
return result;
|
|
840
|
+
}, emitter);
|
|
841
|
+
for (const approval of createdApprovals) {
|
|
842
|
+
emitApprovalRequestCreated(emitter, approval);
|
|
843
|
+
}
|
|
844
|
+
const ordinaryAttempts = created.attempts.filter(attempt => attempt.definition.consequence !== 'production');
|
|
722
845
|
enqueueCheckAttempts({
|
|
723
846
|
projectId: input.projectId,
|
|
724
847
|
batchRunId: created.batchRunId,
|
|
725
848
|
cwd: input.cwd,
|
|
726
|
-
attempts:
|
|
849
|
+
attempts: ordinaryAttempts,
|
|
727
850
|
initialIdentity,
|
|
728
851
|
emitter,
|
|
729
852
|
});
|
|
@@ -742,6 +865,21 @@ export const stopCheckBatch = async (projectId, batchRunId, emitter) => {
|
|
|
742
865
|
}
|
|
743
866
|
const batch = setCheckBatchStopRequested(projectId, batchRunId);
|
|
744
867
|
stoppedBatches.add(batchRunId);
|
|
868
|
+
const approvalRows = db.prepare(`
|
|
869
|
+
SELECT request.id
|
|
870
|
+
FROM approval_requests request
|
|
871
|
+
JOIN check_attempt_details attempt ON attempt.execution_run_id = request.execution_run_id
|
|
872
|
+
WHERE request.project_id = ? AND attempt.batch_run_id = ?
|
|
873
|
+
AND request.status IN ('pending', 'approved')
|
|
874
|
+
`).all(projectId, batchRunId);
|
|
875
|
+
for (const row of approvalRows) {
|
|
876
|
+
cancelApprovalRequest({
|
|
877
|
+
projectId,
|
|
878
|
+
requestId: row.id,
|
|
879
|
+
reason: 'check_batch_stopped',
|
|
880
|
+
emitter,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
745
883
|
const identity = await captureCheckWorkspaceIdentity([...processes.values()].find(record => record.batchRunId === batchRunId)?.cwd
|
|
746
884
|
?? queue.find(entry => entry.batchRunId === batchRunId)?.cwd
|
|
747
885
|
?? '');
|
|
@@ -775,30 +913,169 @@ export const getCheckRunnerState = () => ({
|
|
|
775
913
|
queued_attempt_count: queue.length,
|
|
776
914
|
process_attempt_ids: [...processes.keys()],
|
|
777
915
|
});
|
|
916
|
+
export const resumeProductionCheckApproval = async (projectId, requestId, emitter) => withCheckAdmission(async () => {
|
|
917
|
+
const request = getApprovalRequest(projectId, requestId, { emitter });
|
|
918
|
+
if (!request || request.status !== 'approved' || request.action.kind !== 'custom_check') {
|
|
919
|
+
return false;
|
|
920
|
+
}
|
|
921
|
+
const attempt = getCheckAttemptDetails(projectId, request.execution_run_id);
|
|
922
|
+
if (!attempt || attempt.outcome) {
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
const batch = getCheckBatchDetails(projectId, attempt.batch_run_id);
|
|
926
|
+
if (!batch || batch.stop_requested || ['completed', 'failed', 'cancelled', 'interrupted'].includes(batch.execution_status)) {
|
|
927
|
+
invalidateApprovalRequest({ projectId, requestId, reason: 'check_batch_no_longer_active', emitter });
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
const workspaceCwd = request.action.workspace_git.workspace_path;
|
|
931
|
+
let currentIdentity;
|
|
932
|
+
let currentDefinition;
|
|
933
|
+
try {
|
|
934
|
+
currentIdentity = await captureCheckWorkspaceIdentity(workspaceCwd);
|
|
935
|
+
currentDefinition = getProjectCheckDefinitions(getProjectById(projectId))
|
|
936
|
+
.find(definition => definition.id === attempt.definition_id);
|
|
937
|
+
}
|
|
938
|
+
catch {
|
|
939
|
+
invalidateApprovalRequest({ projectId, requestId, reason: 'production_check_revalidation_failed', emitter });
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
if (!currentDefinition
|
|
943
|
+
|| currentDefinition.category !== 'custom'
|
|
944
|
+
|| currentDefinition.consequence !== 'production'
|
|
945
|
+
|| getCheckDefinitionFingerprint(currentDefinition) !== attempt.definition_fingerprint) {
|
|
946
|
+
consumeApprovalRequest({
|
|
947
|
+
projectId,
|
|
948
|
+
requestId,
|
|
949
|
+
currentAction: {
|
|
950
|
+
...request.action,
|
|
951
|
+
target: { ...request.action.target, check_definition_fingerprint: '0'.repeat(64) },
|
|
952
|
+
},
|
|
953
|
+
emitter,
|
|
954
|
+
});
|
|
955
|
+
return false;
|
|
956
|
+
}
|
|
957
|
+
let current;
|
|
958
|
+
try {
|
|
959
|
+
current = buildProductionCheckAction({
|
|
960
|
+
projectId,
|
|
961
|
+
workspaceCwd,
|
|
962
|
+
effectiveCwd: resolveDefinitionCwd(workspaceCwd, currentDefinition),
|
|
963
|
+
definition: currentDefinition,
|
|
964
|
+
batch,
|
|
965
|
+
workspaceIdentity: currentIdentity,
|
|
966
|
+
expiresAt: request.expires_at,
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
catch {
|
|
970
|
+
invalidateApprovalRequest({ projectId, requestId, reason: 'production_check_revalidation_failed', emitter });
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
const consumed = consumeApprovalRequest({ projectId, requestId, currentAction: current.action, emitter });
|
|
974
|
+
if (!consumed.ok) {
|
|
975
|
+
if (consumed.request && ['expired', 'invalidated'].includes(consumed.request.status)) {
|
|
976
|
+
await finalizeProductionCheckApproval(projectId, requestId, emitter);
|
|
977
|
+
}
|
|
978
|
+
return false;
|
|
979
|
+
}
|
|
980
|
+
enqueueCheckAttempts({
|
|
981
|
+
projectId,
|
|
982
|
+
batchRunId: batch.execution_run_id,
|
|
983
|
+
cwd: workspaceCwd,
|
|
984
|
+
attempts: [{
|
|
985
|
+
runId: attempt.execution_run_id,
|
|
986
|
+
definition: currentDefinition,
|
|
987
|
+
approvalRequestId: requestId,
|
|
988
|
+
governedProductionEnvironment: request.authority.selected_profile.compatibility === 'governed',
|
|
989
|
+
}],
|
|
990
|
+
initialIdentity: attempt.initial_workspace_identity,
|
|
991
|
+
emitter,
|
|
992
|
+
});
|
|
993
|
+
return true;
|
|
994
|
+
});
|
|
995
|
+
export const finalizeProductionCheckApproval = (projectId, requestId, emitter) => {
|
|
996
|
+
const request = getApprovalRequest(projectId, requestId, { emitter });
|
|
997
|
+
if (!request || request.action.kind !== 'custom_check' || !['denied', 'cancelled', 'expired', 'invalidated'].includes(request.status)) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const attempt = getCheckAttemptDetails(projectId, request.execution_run_id);
|
|
1001
|
+
if (!attempt) {
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
aggregateCheckBatch(projectId, attempt.batch_run_id, emitter);
|
|
1005
|
+
notifyBatchWaiters(projectId, attempt.batch_run_id);
|
|
1006
|
+
};
|
|
1007
|
+
registerApprovalRequestTerminalHandler((request, emitter) => {
|
|
1008
|
+
if (request.action_kind === 'custom_check') {
|
|
1009
|
+
finalizeProductionCheckApproval(request.project_id, request.id, emitter);
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
778
1012
|
export const shutdownCheckRunner = async () => {
|
|
779
1013
|
closeCheckRunnerAdmissions();
|
|
780
1014
|
await waitForCheckAdmissions();
|
|
1015
|
+
const hasApprovalRequests = Boolean(db.prepare(`
|
|
1016
|
+
SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'approval_requests'
|
|
1017
|
+
`).get());
|
|
1018
|
+
const durableAttempts = db.prepare(`
|
|
1019
|
+
SELECT run.project_id, run.id AS attempt_run_id, run.parent_run_id AS batch_run_id,
|
|
1020
|
+
${hasApprovalRequests ? 'approval.id' : 'NULL'} AS approval_request_id,
|
|
1021
|
+
${hasApprovalRequests ? 'approval.status' : 'NULL'} AS approval_status
|
|
1022
|
+
FROM execution_runs run
|
|
1023
|
+
${hasApprovalRequests ? `LEFT JOIN approval_requests approval
|
|
1024
|
+
ON approval.execution_run_id = run.id AND approval.project_id = run.project_id` : ''}
|
|
1025
|
+
WHERE run.kind = 'check_attempt' AND run.status IN ('queued', 'waiting', 'running')
|
|
1026
|
+
`).all();
|
|
781
1027
|
const batchIds = new Set([
|
|
782
1028
|
...queue.map(entry => entry.batchRunId),
|
|
783
1029
|
...[...admittedEntries.values()].map(entry => entry.batchRunId),
|
|
784
1030
|
...[...processes.values()].map(record => record.batchRunId),
|
|
1031
|
+
...durableAttempts.map(attempt => attempt.batch_run_id),
|
|
785
1032
|
]);
|
|
1033
|
+
const approvalHeldBatchIds = new Set(durableAttempts
|
|
1034
|
+
.filter(attempt => attempt.approval_status === 'pending' || attempt.approval_status === 'approved')
|
|
1035
|
+
.map(attempt => attempt.batch_run_id));
|
|
1036
|
+
const activeRecords = [...processes.values()];
|
|
786
1037
|
try {
|
|
787
|
-
for (const
|
|
788
|
-
|
|
1038
|
+
for (const entry of admittedEntries.values()) {
|
|
1039
|
+
forcedAttemptOutcomes.set(entry.attemptRunId, entry.approvalRequestId ? 'ambiguous_process_loss' : 'shutdown_loss');
|
|
789
1040
|
}
|
|
790
1041
|
const queuedEntries = queue.splice(0);
|
|
791
1042
|
for (const entry of queuedEntries) {
|
|
792
1043
|
const finalIdentity = await captureCheckWorkspaceIdentity(entry.cwd);
|
|
793
|
-
|
|
794
|
-
|
|
1044
|
+
const outcome = entry.approvalRequestId ? 'ambiguous_process_loss' : 'shutdown_loss';
|
|
1045
|
+
await completeAttempt(entry, outcome, finalIdentity, {
|
|
1046
|
+
exitError: entry.approvalRequestId
|
|
1047
|
+
? 'The consumed production command process identity was lost during shutdown'
|
|
1048
|
+
: 'The server shut down before this check attempt started',
|
|
795
1049
|
});
|
|
796
1050
|
}
|
|
797
|
-
for (const
|
|
798
|
-
|
|
1051
|
+
for (const attempt of durableAttempts) {
|
|
1052
|
+
if (attempt.approval_request_id && (attempt.approval_status === 'pending' || attempt.approval_status === 'approved')) {
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
if (admittedEntries.has(attempt.attempt_run_id)) {
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
const details = getCheckAttemptDetails(attempt.project_id, attempt.attempt_run_id);
|
|
1059
|
+
if (details && !details.outcome) {
|
|
1060
|
+
completeCheckAttempt({
|
|
1061
|
+
projectId: attempt.project_id,
|
|
1062
|
+
runId: attempt.attempt_run_id,
|
|
1063
|
+
outcome: attempt.approval_status === 'consumed' ? 'ambiguous_process_loss' : 'shutdown_loss',
|
|
1064
|
+
finalWorkspaceIdentity: details.initial_workspace_identity,
|
|
1065
|
+
exitError: attempt.approval_status === 'consumed'
|
|
1066
|
+
? 'The consumed production command process identity was lost during shutdown'
|
|
1067
|
+
: 'The server shut down before this check attempt completed',
|
|
1068
|
+
});
|
|
1069
|
+
aggregateCheckBatch(attempt.project_id, attempt.batch_run_id);
|
|
1070
|
+
notifyBatchWaiters(attempt.project_id, attempt.batch_run_id);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
for (const record of activeRecords) {
|
|
1074
|
+
record.terminalCause ??= record.approvalRequestId ? 'ambiguous_process_loss' : 'shutdown_loss';
|
|
799
1075
|
terminateProcess(record);
|
|
800
1076
|
}
|
|
801
|
-
await Promise.all(
|
|
1077
|
+
await Promise.all(activeRecords.map(waitForProcessFinalization));
|
|
1078
|
+
await Promise.all([...batchIds].filter(batchRunId => !approvalHeldBatchIds.has(batchRunId)).map(batchRunId => {
|
|
802
1079
|
const projectId = [...admittedEntries.values()].find(entry => entry.batchRunId === batchRunId)?.projectId
|
|
803
1080
|
?? [...processes.values()].find(record => record.batchRunId === batchRunId)?.projectId
|
|
804
1081
|
?? queuedEntries.find(entry => entry.batchRunId === batchRunId)?.projectId;
|
|
@@ -807,7 +1084,9 @@ export const shutdownCheckRunner = async () => {
|
|
|
807
1084
|
}
|
|
808
1085
|
finally {
|
|
809
1086
|
for (const batchRunId of batchIds) {
|
|
810
|
-
|
|
1087
|
+
for (const attempt of durableAttempts.filter(candidate => candidate.batch_run_id === batchRunId)) {
|
|
1088
|
+
forcedAttemptOutcomes.delete(attempt.attempt_run_id);
|
|
1089
|
+
}
|
|
811
1090
|
}
|
|
812
1091
|
}
|
|
813
1092
|
};
|