@ccdd/project 3.3.1 → 4.1.0
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/README.md +9 -12
- package/dist/monitor-ui/assets/GraphView-6yzqBOLv.js +27 -0
- package/dist/monitor-ui/assets/ValidationView-BMqI2XQ5.css +1 -0
- package/dist/monitor-ui/assets/ValidationView-BwHWjMUI.js +1 -0
- package/dist/monitor-ui/assets/{elk-api-D5d3gWoP.js → elk-api-BwZQovT5.js} +1 -1
- package/dist/monitor-ui/assets/{elk.bundled-0eaKiuBP.js → elk.bundled-CQowIhI8.js} +1 -1
- package/dist/monitor-ui/assets/index-CoJ9_gC3.js +7 -0
- package/dist/monitor-ui/assets/{index-DDwx11D7.css → index-D0yfhUlt.css} +1 -1
- package/dist/monitor-ui/index.html +2 -2
- package/dist/scripts/prepare-demo.js +25 -44
- package/dist/scripts/prepare-demo.js.map +1 -1
- package/dist/src/artifact-scope.d.ts +6 -0
- package/dist/src/artifact-scope.js +27 -0
- package/dist/src/artifact-scope.js.map +1 -0
- package/dist/src/artifacts/index.d.ts +7 -155
- package/dist/src/artifacts/index.js +0 -354
- package/dist/src/artifacts/index.js.map +1 -1
- package/dist/src/artifacts/instruction.d.ts +6 -9
- package/dist/src/artifacts/instruction.js +28 -36
- package/dist/src/artifacts/instruction.js.map +1 -1
- package/dist/src/artifacts/mcp-server.js +8 -9
- package/dist/src/artifacts/mcp-server.js.map +1 -1
- package/dist/src/artifacts/scope.d.ts +12 -0
- package/dist/src/artifacts/scope.js +34 -0
- package/dist/src/artifacts/scope.js.map +1 -0
- package/dist/src/artifacts/tool-check.d.ts +2 -6
- package/dist/src/artifacts/tool-check.js +16 -20
- package/dist/src/artifacts/tool-check.js.map +1 -1
- package/dist/src/artifacts/types.d.ts +3 -37
- package/dist/src/artifacts/types.js +5 -89
- package/dist/src/artifacts/types.js.map +1 -1
- package/dist/src/broker/config.d.ts +5 -6
- package/dist/src/broker/config.js +183 -130
- package/dist/src/broker/config.js.map +1 -1
- package/dist/src/broker/graph.d.ts +14 -21
- package/dist/src/broker/graph.js +68 -70
- package/dist/src/broker/graph.js.map +1 -1
- package/dist/src/broker/index.d.ts +6 -28
- package/dist/src/broker/index.js +84 -234
- package/dist/src/broker/index.js.map +1 -1
- package/dist/src/cli.d.ts +1 -21
- package/dist/src/cli.js +2 -329
- package/dist/src/cli.js.map +1 -1
- package/dist/src/contracts.d.ts +13 -15
- package/dist/src/definitions.d.ts +33 -23
- package/dist/src/diagnostics-cli.d.ts +9 -0
- package/dist/src/diagnostics-cli.js +94 -0
- package/dist/src/diagnostics-cli.js.map +1 -0
- package/dist/src/doctor/index.d.ts +2 -4
- package/dist/src/doctor/index.js +17 -63
- package/dist/src/doctor/index.js.map +1 -1
- package/dist/src/executors/auth.d.ts +1 -1
- package/dist/src/executors/auth.js +2 -2
- package/dist/src/executors/auth.js.map +1 -1
- package/dist/src/executors/human-preparation.js +2 -10
- package/dist/src/executors/human-preparation.js.map +1 -1
- package/dist/src/executors/index.js +30 -29
- package/dist/src/executors/index.js.map +1 -1
- package/dist/src/executors/pi.js +36 -4
- package/dist/src/executors/pi.js.map +1 -1
- package/dist/src/executors/process.js +19 -11
- package/dist/src/executors/process.js.map +1 -1
- package/dist/src/executors/telemetry.d.ts +4 -0
- package/dist/src/executors/telemetry.js +24 -0
- package/dist/src/executors/telemetry.js.map +1 -0
- package/dist/src/monitor/actions.js +3 -3
- package/dist/src/monitor/actions.js.map +1 -1
- package/dist/src/monitor/server.js +12 -95
- package/dist/src/monitor/server.js.map +1 -1
- package/dist/src/monitor/store.js +6 -42
- package/dist/src/monitor/store.js.map +1 -1
- package/dist/src/monitor/types.d.ts +3 -13
- package/dist/src/monitor/ui/critic-presentation.js +1 -1
- package/dist/src/monitor/ui/critic-presentation.js.map +1 -1
- package/dist/src/monitor/ui/graph-layout.d.ts +0 -1
- package/dist/src/monitor/ui/graph-layout.js +1 -23
- package/dist/src/monitor/ui/graph-layout.js.map +1 -1
- package/dist/src/monitor/ui/tool-input.d.ts +1 -1
- package/dist/src/monitor/ui/tool-input.js +2 -3
- package/dist/src/monitor/ui/tool-input.js.map +1 -1
- package/dist/src/project/cli.js +18 -30
- package/dist/src/project/cli.js.map +1 -1
- package/dist/src/project/graph.js +2 -2
- package/dist/src/project/graph.js.map +1 -1
- package/dist/src/project/identity.js +83 -56
- package/dist/src/project/identity.js.map +1 -1
- package/dist/src/project/index.d.ts +0 -3
- package/dist/src/project/index.js +2 -6
- package/dist/src/project/index.js.map +1 -1
- package/dist/src/project/query.d.ts +2 -1
- package/dist/src/project/query.js +49 -74
- package/dist/src/project/query.js.map +1 -1
- package/dist/src/project/store.js +3 -3
- package/dist/src/project/store.js.map +1 -1
- package/dist/src/project/types.d.ts +4 -4
- package/dist/src/requester/index.d.ts +2 -9
- package/dist/src/requester/index.js +17 -96
- package/dist/src/requester/index.js.map +1 -1
- package/dist/src/sdk.d.ts +3 -8
- package/dist/src/sdk.js +2 -3
- package/dist/src/sdk.js.map +1 -1
- package/dist/src/tools/contracts.d.ts +44 -80
- package/dist/src/tools/diagnostics.js +2 -2
- package/dist/src/tools/diagnostics.js.map +1 -1
- package/dist/src/tools/environment.d.ts +1 -0
- package/dist/src/tools/environment.js +7 -11
- package/dist/src/tools/environment.js.map +1 -1
- package/dist/src/tools/manifest.d.ts +0 -1
- package/dist/src/tools/manifest.js +1 -25
- package/dist/src/tools/manifest.js.map +1 -1
- package/dist/src/tools/runner.d.ts +19 -14
- package/dist/src/tools/runner.js +163 -118
- package/dist/src/tools/runner.js.map +1 -1
- package/dist/src/tools/schema.js +1 -1
- package/dist/src/tools/schema.js.map +1 -1
- package/dist/src/workspaces/index.d.ts +5 -12
- package/dist/src/workspaces/index.js +33 -217
- package/dist/src/workspaces/index.js.map +1 -1
- package/package.json +38 -10
- package/dist/monitor-ui/assets/GraphView-BSZlEAs3.js +0 -27
- package/dist/monitor-ui/assets/ValidationView-DaEF78J-.js +0 -1
- package/dist/monitor-ui/assets/ValidationView-p5r8lX18.css +0 -1
- package/dist/monitor-ui/assets/index-BQlWbkpH.js +0 -8
- package/dist/src/artifacts/data.d.ts +0 -5
- package/dist/src/artifacts/data.js +0 -100
- package/dist/src/artifacts/data.js.map +0 -1
- package/dist/src/artifacts/groups.d.ts +0 -13
- package/dist/src/artifacts/groups.js +0 -93
- package/dist/src/artifacts/groups.js.map +0 -1
- package/dist/src/artifacts/human.d.ts +0 -38
- package/dist/src/artifacts/human.js +0 -156
- package/dist/src/artifacts/human.js.map +0 -1
- package/dist/src/artifacts/sources.d.ts +0 -11
- package/dist/src/artifacts/sources.js +0 -106
- package/dist/src/artifacts/sources.js.map +0 -1
- package/dist/src/review/cli.d.ts +0 -8
- package/dist/src/review/cli.js +0 -136
- package/dist/src/review/cli.js.map +0 -1
- package/dist/src/review/client.d.ts +0 -30
- package/dist/src/review/client.js +0 -193
- package/dist/src/review/client.js.map +0 -1
- package/dist/src/review/server.d.ts +0 -11
- package/dist/src/review/server.js +0 -249
- package/dist/src/review/server.js.map +0 -1
- package/dist/src/review/types.d.ts +0 -10
- package/dist/src/review/types.js +0 -17
- package/dist/src/review/types.js.map +0 -1
- package/dist/src/tools/host.d.ts +0 -7
- package/dist/src/tools/host.js +0 -98
- package/dist/src/tools/host.js.map +0 -1
- package/dist/src/tools/runtime-host.d.ts +0 -1
- package/dist/src/tools/runtime-host.js +0 -212
- package/dist/src/tools/runtime-host.js.map +0 -1
- package/dist/src/workspaces/transfer.d.ts +0 -52
- package/dist/src/workspaces/transfer.js +0 -372
- package/dist/src/workspaces/transfer.js.map +0 -1
package/dist/src/broker/index.js
CHANGED
|
@@ -3,19 +3,16 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import { isDeepStrictEqual } from 'node:util';
|
|
7
6
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
8
|
-
import { prepareReviewRequests
|
|
7
|
+
import { prepareReviewRequests } from '../requester/index.js';
|
|
9
8
|
import { readWorkspaceConfig } from './config.js';
|
|
10
|
-
import { createGraphDefinition
|
|
9
|
+
import { createGraphDefinition } from './graph.js';
|
|
10
|
+
import { tokenUsage } from '../executors/telemetry.js';
|
|
11
11
|
import { createReviewTools } from '../tools/runner.js';
|
|
12
|
-
import { describeReviewTools } from '../tools/runner.js';
|
|
13
|
-
import { validateArguments } from '../tools/schema.js';
|
|
14
12
|
import { createHumanClaims, HUMAN_PREPARATION_LEASE_MS } from './human-claims.js';
|
|
15
13
|
import { prepareHumanReview } from '../executors/human-preparation.js';
|
|
16
14
|
import { prepareWorkspace, reopenWorkspace } from '../workspaces/index.js';
|
|
17
15
|
import { createProjectSnapshot } from '../project/identity.js';
|
|
18
|
-
import { assertGeneratedReviewInputs, prepareArtifactInputs } from '../artifacts/sources.js';
|
|
19
16
|
import { includedCritics, planProject } from '../project/query.js';
|
|
20
17
|
import { readEvidence } from '../project/store.js';
|
|
21
18
|
const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -34,10 +31,12 @@ const fatalExecutionError = (error) => {
|
|
|
34
31
|
const code = errorCode(error);
|
|
35
32
|
return Boolean(code?.startsWith('WORKSPACE_') || ['RUN_OWNERSHIP_LOST', 'REVIEW_CANCELED', 'WORKER_STOPPED', 'WORKER_EXITED', 'REVIEW_GRAPH_INVALID'].includes(code ?? ''));
|
|
36
33
|
};
|
|
37
|
-
function storedObservation(value) {
|
|
34
|
+
function storedObservation(value, isError = false) {
|
|
38
35
|
if (!object(value) || typeof value.artifactId !== 'string' || typeof value.operation !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/.test(value.operation))
|
|
39
36
|
return undefined;
|
|
40
37
|
const observation = { artifactId: value.artifactId.slice(0, 64), operation: value.operation };
|
|
38
|
+
if (isError)
|
|
39
|
+
return observation;
|
|
41
40
|
if (value.kind === 'content' || value.kind === 'empty')
|
|
42
41
|
observation.kind = value.kind;
|
|
43
42
|
if (typeof value.detail === 'string')
|
|
@@ -68,7 +67,9 @@ function validateResult(value) {
|
|
|
68
67
|
if (Array.isArray(value.toolCalls))
|
|
69
68
|
result.toolCalls = value.toolCalls.slice(0, 100).filter((item) => object(item) && typeof item.name === 'string').map(item => {
|
|
70
69
|
const call = { name: item.name, ...(item.arguments === undefined ? {} : { arguments: copy(item.arguments) }) };
|
|
71
|
-
const observation = storedObservation(item.observation);
|
|
70
|
+
const observation = storedObservation(item.observation, item.isError === true);
|
|
71
|
+
if (item.isError === true)
|
|
72
|
+
call.isError = true;
|
|
72
73
|
if (observation)
|
|
73
74
|
call.observation = observation;
|
|
74
75
|
return call;
|
|
@@ -142,7 +143,7 @@ function prepareStateDirectory(repoPath, stateDir) {
|
|
|
142
143
|
const canonical = canonicalFuturePath(stateDir);
|
|
143
144
|
const relative = path.relative(repoPath, canonical);
|
|
144
145
|
if (relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))) {
|
|
145
|
-
throw codedError('CCDD state, logs and
|
|
146
|
+
throw codedError('CCDD state, logs and review outputs must be outside the reviewed repository.', 'WORKSPACE_UNSAFE');
|
|
146
147
|
}
|
|
147
148
|
fs.mkdirSync(canonical, { recursive: true, mode: 0o700 });
|
|
148
149
|
return fs.realpathSync(canonical);
|
|
@@ -239,59 +240,17 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
239
240
|
event: (request, type, message) => appendEvent(request.runId, request.id, type, message),
|
|
240
241
|
assertWaiting: request => {
|
|
241
242
|
ensureOpen();
|
|
243
|
+
if (request.configManifest?.version !== 2)
|
|
244
|
+
throw new Error('Historical reviews are available for result lookup only.');
|
|
242
245
|
if (!request.notifiedAt)
|
|
243
246
|
throw new Error('Human alarm delivery is still pending.');
|
|
244
|
-
if (
|
|
245
|
-
throw new Error('
|
|
247
|
+
if (!ownerAlive(ownerData(request.runId)))
|
|
248
|
+
throw new Error('An in-place review requires its monitoring worker to remain alive.');
|
|
246
249
|
},
|
|
247
250
|
});
|
|
248
|
-
const assertRemoteClaim = (requestId, reviewerId, attemptId) => {
|
|
249
|
-
ensureOpen();
|
|
250
|
-
const request = required(requestData(requestId), 'request');
|
|
251
|
-
if (request.profile.kind !== 'human' || request.status !== 'WAITING_HUMAN' || request.claimedBy !== reviewerId || request.claimAttemptId !== attemptId)
|
|
252
|
-
throw new Error('This confirmed Claim is no longer owned by this reviewer.');
|
|
253
|
-
if (request.workspace.mode !== 'copy')
|
|
254
|
-
throw new Error('Remote Human review requires a copied workspace.');
|
|
255
|
-
return request;
|
|
256
|
-
};
|
|
257
|
-
function prerequisites(request, run, requests) {
|
|
258
|
-
if (run.project)
|
|
259
|
-
return []; // Project requests are issued only after a pull readiness check.
|
|
260
|
-
if (run.scope?.kind === 'critic')
|
|
261
|
-
return [];
|
|
262
|
-
if (run.graph) {
|
|
263
|
-
const byCritic = new Map(requests.map(item => [item.criticId, item]));
|
|
264
|
-
return prerequisiteCriticIds(request, run.graph).map(id => {
|
|
265
|
-
const dependency = byCritic.get(id);
|
|
266
|
-
if (!dependency || dependency.runId !== run.id)
|
|
267
|
-
throw codedError(`Required Critic ${id} is missing from this Run.`, 'REVIEW_GRAPH_INVALID');
|
|
268
|
-
return dependency;
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
// Historical chains retain their explicit links; no target Artifact is guessed.
|
|
272
|
-
if (request.predecessorId === null || (request.predecessorId === undefined && requests.length === 1))
|
|
273
|
-
return [];
|
|
274
|
-
const previous = requests.find(item => item.id === request.predecessorId);
|
|
275
|
-
if (!previous || previous.runId !== run.id)
|
|
276
|
-
throw codedError('This historical Run has no usable predecessor link. Submit a new review.', 'REVIEW_GRAPH_INVALID');
|
|
277
|
-
return [previous];
|
|
278
|
-
}
|
|
279
|
-
function dependencyFailure(request, run, requests, seen = new Set()) {
|
|
280
|
-
if (seen.has(request.id))
|
|
281
|
-
return null;
|
|
282
|
-
seen.add(request.id);
|
|
283
|
-
for (const dependency of prerequisites(request, run, requests)) {
|
|
284
|
-
if (dependency.status === 'RED' || dependency.status === 'ERROR')
|
|
285
|
-
return dependency;
|
|
286
|
-
const failure = dependencyFailure(dependency, run, requests, seen);
|
|
287
|
-
if (failure)
|
|
288
|
-
return failure;
|
|
289
|
-
}
|
|
290
|
-
return null;
|
|
291
|
-
}
|
|
292
251
|
function refreshReadinessWithin(runId) {
|
|
293
252
|
const run = required(runData(runId), 'Run'), requests = runRequests(runId);
|
|
294
|
-
if (run.project) {
|
|
253
|
+
if (run.project?.version === 2) {
|
|
295
254
|
if (terminal.has(run.status))
|
|
296
255
|
return;
|
|
297
256
|
const plan = planProject(run.project.snapshot, readEvidence(db), { selection: run.project.selection, recursive: run.project.recursive, force: run.project.force, runId, attempts: requests });
|
|
@@ -308,30 +267,12 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
308
267
|
}
|
|
309
268
|
return;
|
|
310
269
|
}
|
|
311
|
-
|
|
312
|
-
if (request.status !== 'BLOCKED')
|
|
313
|
-
continue;
|
|
314
|
-
const dependencies = prerequisites(request, run, requests);
|
|
315
|
-
if (dependencies.every(item => item.status === 'GREEN')) {
|
|
316
|
-
request.status = 'QUEUED';
|
|
317
|
-
request.blockedReason = null;
|
|
318
|
-
saveRequest(request);
|
|
319
|
-
appendEvent(runId, request.id, 'request.queued', 'All required Artifact reviews are GREEN.');
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
const failed = dependencyFailure(request, run, requests);
|
|
323
|
-
const reason = failed ? `${failed.criticId} returned ${failed.status}; dependent reviews cannot start.` : `Waiting for ${dependencies.filter(item => item.status !== 'GREEN').map(item => item.criticId).join(', ')} to become GREEN.`;
|
|
324
|
-
if (request.blockedReason !== reason) {
|
|
325
|
-
request.blockedReason = reason;
|
|
326
|
-
saveRequest(request);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
270
|
+
throw new Error('Historical Runs cannot be resumed; submit a new validation request.');
|
|
330
271
|
}
|
|
331
272
|
const updateRunStatus = (runId) => {
|
|
332
273
|
const states = runRequests(runId).map(request => request.status);
|
|
333
274
|
const run = required(runData(runId), 'Run');
|
|
334
|
-
if (run.project) {
|
|
275
|
+
if (run.project?.version === 2) {
|
|
335
276
|
if (terminal.has(run.status))
|
|
336
277
|
return;
|
|
337
278
|
const plan = planProject(run.project.snapshot, readEvidence(db), { selection: run.project.selection, recursive: run.project.recursive, force: run.project.force, runId, attempts: runRequests(runId) });
|
|
@@ -348,19 +289,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
348
289
|
}
|
|
349
290
|
return;
|
|
350
291
|
}
|
|
351
|
-
|
|
352
|
-
const status = states.includes('RUNNING') ? 'RUNNING' : states.includes('QUEUED') ? 'QUEUED' :
|
|
353
|
-
states.includes('WAITING_HUMAN') ? 'WAITING_HUMAN' : states.includes('ERROR') ? 'ERROR' : states.includes('RED') ? 'RED' :
|
|
354
|
-
states.length > 0 && states.every(state => state === 'GREEN') ? 'GREEN' : 'ERROR';
|
|
355
|
-
if (run.status !== status) {
|
|
356
|
-
run.status = status;
|
|
357
|
-
if (terminal.has(status))
|
|
358
|
-
run.completedAt = now();
|
|
359
|
-
else
|
|
360
|
-
delete run.completedAt;
|
|
361
|
-
saveRun(run);
|
|
362
|
-
appendEvent(runId, null, 'run.status', `Run ${status}`, { status });
|
|
363
|
-
}
|
|
292
|
+
throw new Error('Historical Runs are available for result lookup only.');
|
|
364
293
|
};
|
|
365
294
|
function finishWithin(requestId, outcome, expectedStates = ['RUNNING', 'WAITING_HUMAN']) {
|
|
366
295
|
const { result, error } = outcome, hasError = Object.hasOwn(outcome, 'error');
|
|
@@ -394,7 +323,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
394
323
|
saveRequest(request);
|
|
395
324
|
appendEvent(runId, request.id, 'request.error', request.error, { status: request.status, ...(request.errorCode ? { code: request.errorCode } : {}) });
|
|
396
325
|
}
|
|
397
|
-
if (run.project) {
|
|
326
|
+
if (run.project?.version === 2) {
|
|
398
327
|
const plan = planProject(run.project.snapshot, readEvidence(db), { selection: run.project.selection, recursive: run.project.recursive, force: run.project.force, runId, attempts: runRequests(runId) });
|
|
399
328
|
run.project.evidenceRequestIds = [...new Set(plan.critics.flatMap(c => c.result ? [c.result.requestId] : []))];
|
|
400
329
|
}
|
|
@@ -408,13 +337,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
408
337
|
const owner = ownerData(runId);
|
|
409
338
|
if (!owner || ownerAlive(owner))
|
|
410
339
|
return;
|
|
411
|
-
|
|
412
|
-
const unfinished = runRequests(runId).filter(request => !terminal.has(request.status));
|
|
413
|
-
const safeHumanWait = run?.workspace?.mode === 'copy' && unfinished.some(request => request.status === 'WAITING_HUMAN') &&
|
|
414
|
-
unfinished.every(request => request.status === 'BLOCKED' || (request.status === 'WAITING_HUMAN' && request.notifiedAt));
|
|
415
|
-
if (!safeHumanWait) {
|
|
416
|
-
failWithin(runId, codedError('The review worker exited before completing its work. Submit a new run to retry.', 'WORKER_EXITED'));
|
|
417
|
-
}
|
|
340
|
+
failWithin(runId, codedError('The review worker exited before completing its work. Submit a new run to retry.', 'WORKER_EXITED'));
|
|
418
341
|
db.prepare('DELETE FROM run_owners WHERE run_id = ? AND token = ?').run(runId, owner.token);
|
|
419
342
|
appendEvent(runId, null, 'worker.exited', 'The previous review worker is no longer running.');
|
|
420
343
|
}
|
|
@@ -455,7 +378,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
455
378
|
request.startedAt = now();
|
|
456
379
|
request.blockedReason = null;
|
|
457
380
|
saveRequest(request);
|
|
458
|
-
appendEvent(runId, request.id, 'request.started', `${request.title}: reviewing
|
|
381
|
+
appendEvent(runId, request.id, 'request.started', `${request.title}: reviewing the supplied workspace.`, { snapshotHash: workspace.descriptor.hash });
|
|
459
382
|
updateRunStatus(runId);
|
|
460
383
|
});
|
|
461
384
|
changed();
|
|
@@ -463,7 +386,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
463
386
|
signal.throwIfAborted();
|
|
464
387
|
await workspace.assertUnchanged();
|
|
465
388
|
const request = required(requestData(requestId), 'Request');
|
|
466
|
-
assertGeneratedReviewInputs(request);
|
|
467
389
|
const runDir = path.join(stateDir, 'runs', runId, request.id);
|
|
468
390
|
fs.mkdirSync(runDir, { recursive: true, mode: 0o700 });
|
|
469
391
|
if (required(runData(runId), 'Run').project) {
|
|
@@ -500,21 +422,39 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
500
422
|
const result = validateResult(await requireExecutors().execute(copy(request), {
|
|
501
423
|
worktreePath: workspace.descriptor.path, workspacePath: workspace.descriptor.path, runDir, signal,
|
|
502
424
|
onEvent(event) {
|
|
503
|
-
if (closed || closing || signal.aborted || !event || requestData(requestId)?.status !== 'RUNNING' || !['executor.started', 'artifact.tools.ready', 'artifact.tool.called', 'executor.completed'].includes(event.type))
|
|
425
|
+
if (closed || closing || signal.aborted || !event || requestData(requestId)?.status !== 'RUNNING' || !['executor.started', 'artifact.tools.ready', 'artifact.tool.called', 'artifact.tool.completed', 'executor.usage', 'executor.telemetry.failed', 'executor.completed'].includes(event.type))
|
|
504
426
|
return;
|
|
505
427
|
const safe = {};
|
|
506
428
|
for (const key of ['name', 'provider', 'model', 'kind', 'artifactId', 'path'])
|
|
507
429
|
if (typeof event[key] === 'string')
|
|
508
430
|
safe[key] = event[key].slice(0, 1000);
|
|
431
|
+
if (event.type === 'artifact.tool.completed') {
|
|
432
|
+
if (typeof event.startedAt === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(event.startedAt) && Number.isFinite(Date.parse(event.startedAt)))
|
|
433
|
+
safe.startedAt = event.startedAt;
|
|
434
|
+
if (typeof event.durationMs === 'number' && Number.isFinite(event.durationMs) && event.durationMs >= 0)
|
|
435
|
+
safe.durationMs = event.durationMs;
|
|
436
|
+
if (event.outcome === 'success' || event.outcome === 'error')
|
|
437
|
+
safe.outcome = event.outcome;
|
|
438
|
+
if (typeof event.operation === 'string')
|
|
439
|
+
safe.operation = event.operation.slice(0, 1000);
|
|
440
|
+
}
|
|
441
|
+
if (event.type === 'executor.usage') {
|
|
442
|
+
const usage = tokenUsage(event.usage);
|
|
443
|
+
if (!usage)
|
|
444
|
+
return;
|
|
445
|
+
safe.usage = usage;
|
|
446
|
+
}
|
|
447
|
+
if (event.isError === true)
|
|
448
|
+
safe.isError = true;
|
|
509
449
|
if (object(event.observation)) {
|
|
510
450
|
// Persist the same bounded metadata as final results, even if the Provider later fails.
|
|
511
|
-
const observed = storedObservation(event.observation);
|
|
451
|
+
const observed = storedObservation(event.observation, event.isError === true);
|
|
512
452
|
if (observed)
|
|
513
453
|
safe.observation = observed;
|
|
514
454
|
}
|
|
515
455
|
if (Array.isArray(event.tools))
|
|
516
456
|
safe.tools = event.tools.slice(0, 32).map(tool => typeof tool === 'string' ? tool : tool?.name).filter(value => typeof value === 'string');
|
|
517
|
-
appendEvent(runId, requestId, event.type, String(event.message ?? event.type).slice(0, 2000), safe);
|
|
457
|
+
appendEvent(runId, requestId, event.type, event.type === 'executor.telemetry.failed' ? 'Some optional execution diagnostics could not be recorded.' : String(event.message ?? event.type).slice(0, 2000), safe);
|
|
518
458
|
changed();
|
|
519
459
|
},
|
|
520
460
|
}));
|
|
@@ -543,6 +483,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
543
483
|
stored = runData(runId);
|
|
544
484
|
if (!stored)
|
|
545
485
|
throw new Error('Unknown Run.');
|
|
486
|
+
if (stored.project?.version !== 2)
|
|
487
|
+
throw new Error('Historical Runs cannot be resumed; submit a new validation request.');
|
|
546
488
|
if (terminal.has(stored.status))
|
|
547
489
|
return false;
|
|
548
490
|
if (!stored.workspace)
|
|
@@ -598,7 +540,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
598
540
|
inFlight.set(queued.id, { kind: queued.profile.kind, promise });
|
|
599
541
|
}
|
|
600
542
|
if (inFlight.size) {
|
|
601
|
-
// Human alarms and independent
|
|
543
|
+
// Human alarms and independent evaluations progress together.
|
|
602
544
|
await Promise.race([...inFlight.values()].map(item => item.promise).concat(delay(50, undefined, { signal: reviewSignal })));
|
|
603
545
|
continue;
|
|
604
546
|
}
|
|
@@ -606,24 +548,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
606
548
|
if (waiting.length) {
|
|
607
549
|
if (waiting.some(request => !request.notifiedAt))
|
|
608
550
|
throw new Error('Human notification did not complete; submit a new Run to retry.');
|
|
609
|
-
|
|
610
|
-
await workspace.assertUnchanged();
|
|
611
|
-
// A result can arrive during input validation. Release only after rechecking all branches atomically.
|
|
612
|
-
const paused = transaction(() => {
|
|
613
|
-
const pending = runRequests(runId).filter(request => !terminal.has(request.status));
|
|
614
|
-
if (!pending.some(request => request.status === 'WAITING_HUMAN') || pending.some(request => request.status !== 'BLOCKED' && (request.status !== 'WAITING_HUMAN' || !request.notifiedAt)))
|
|
615
|
-
return false;
|
|
616
|
-
if (ownerData(runId)?.token !== token)
|
|
617
|
-
throw codedError('Review ownership was lost.', 'RUN_OWNERSHIP_LOST');
|
|
618
|
-
db.prepare('DELETE FROM run_owners WHERE run_id = ? AND token = ?').run(runId, token);
|
|
619
|
-
appendEvent(runId, null, 'worker.paused', 'Copied Human reviews are persisted; no worker is needed while awaiting results.');
|
|
620
|
-
return true;
|
|
621
|
-
});
|
|
622
|
-
if (paused)
|
|
623
|
-
break;
|
|
624
|
-
continue;
|
|
625
|
-
}
|
|
626
|
-
// The lock observer stays alive with filesystem events and metadata polls.
|
|
551
|
+
// The workspace observer stays alive with filesystem events and metadata polls.
|
|
627
552
|
// Notification already crossed its final content boundary; idle waiting
|
|
628
553
|
// must not rehash the entire workspace on every scheduling iteration.
|
|
629
554
|
await delay(100, undefined, { signal: reviewSignal });
|
|
@@ -661,18 +586,17 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
661
586
|
return entry.promise;
|
|
662
587
|
}
|
|
663
588
|
return {
|
|
664
|
-
async submitProject({
|
|
589
|
+
async submitProject({ requesterId = 'cli', selection, recursive = false, force = false, ...removed }) {
|
|
665
590
|
ensureOpen();
|
|
666
591
|
requireExecutors();
|
|
592
|
+
if ('mode' in removed)
|
|
593
|
+
throw new Error('Workspace modes are no longer supported; supply an unchanged workspace.');
|
|
667
594
|
if (!requesterId.trim() || requesterId.length > 200)
|
|
668
595
|
throw new Error('requesterId is required (maximum 200 characters).');
|
|
669
|
-
if (!['lock', 'copy'].includes(mode))
|
|
670
|
-
throw new Error('mode must be lock or copy.');
|
|
671
596
|
await requireExecutors().validateWorkspace?.(repoPath);
|
|
672
|
-
const workspace = await workspaceAdapter.prepareWorkspace({ repoPath, stateDir,
|
|
597
|
+
const workspace = await workspaceAdapter.prepareWorkspace({ repoPath, stateDir, integrity: workspaceIntegrity });
|
|
673
598
|
try {
|
|
674
|
-
const
|
|
675
|
-
const config = await prepareArtifactInputs(loaded.config, workspace.descriptor.path, 'review', workspace.signal);
|
|
599
|
+
const { config } = await readWorkspaceConfig(workspace.descriptor.path, workspace.signal);
|
|
676
600
|
const snapshot = await createProjectSnapshot(config, workspace.descriptor.path, workspace.descriptor.hash, workspace.signal, workspace.descriptor.integrity);
|
|
677
601
|
const ids = includedCritics(snapshot, selection, recursive);
|
|
678
602
|
const templates = [];
|
|
@@ -680,7 +604,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
680
604
|
templates.push(...await prepareReviewRequests({ repoPath: workspace.descriptor.path, repoId, snapshotHash: workspace.descriptor.hash, criticId: id, preparedConfig: config }));
|
|
681
605
|
const id = randomUUID(), createdAt = now();
|
|
682
606
|
const record = { id, repoId, snapshotHash: workspace.descriptor.hash, workspace: workspace.descriptor, requesterId,
|
|
683
|
-
scope: { kind: 'project' }, graph: createGraphDefinition(config), project: { version:
|
|
607
|
+
scope: { kind: 'project' }, graph: createGraphDefinition(config), project: { version: 2, snapshot, selection, recursive, force, templates }, status: 'QUEUED', createdAt };
|
|
684
608
|
await workspace.assertUnchanged();
|
|
685
609
|
workspace.signal.throwIfAborted();
|
|
686
610
|
transaction(() => {
|
|
@@ -696,63 +620,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
696
620
|
await workspace.close();
|
|
697
621
|
}
|
|
698
622
|
},
|
|
699
|
-
async submit({ mode = 'copy', requesterId, reviewRequests, criticId, snapshotCommit } = {}) {
|
|
700
|
-
ensureOpen();
|
|
701
|
-
requireExecutors();
|
|
702
|
-
if (snapshotCommit !== undefined)
|
|
703
|
-
throw new Error('snapshotCommit is no longer accepted; use mode copy or lock with the current workspace.');
|
|
704
|
-
if (typeof requesterId !== 'string' || !requesterId.trim() || requesterId.length > 200)
|
|
705
|
-
throw new Error('requesterId is required (maximum 200 characters).');
|
|
706
|
-
if (!['lock', 'copy'].includes(mode))
|
|
707
|
-
throw new Error('mode must be lock or copy.');
|
|
708
|
-
await requireExecutors().validateWorkspace?.(repoPath);
|
|
709
|
-
const workspace = await workspaceAdapter.prepareWorkspace({ repoPath, stateDir, mode, integrity: workspaceIntegrity });
|
|
710
|
-
try {
|
|
711
|
-
const descriptor = copy(workspace.descriptor);
|
|
712
|
-
const { config } = await readWorkspaceConfig(descriptor.path);
|
|
713
|
-
const graph = createGraphDefinition(config);
|
|
714
|
-
const expected = await prepareReviewRequests({ repoPath: descriptor.path, repoId, snapshotHash: descriptor.hash, criticId, preparedConfig: config });
|
|
715
|
-
const supplied = reviewRequests === undefined ? expected : reviewRequests;
|
|
716
|
-
if (!isDeepStrictEqual(supplied, expected))
|
|
717
|
-
throw new Error('Submitted review request envelopes must exactly match the Artifact definitions, payload, profiles and dependencies in the requested workspace.');
|
|
718
|
-
const id = randomUUID();
|
|
719
|
-
const createdAt = now();
|
|
720
|
-
const requests = copy(supplied).map(envelope => ({
|
|
721
|
-
...envelope, id: randomUUID(), runId: id, workspace: descriptor, worktreePath: descriptor.path,
|
|
722
|
-
status: 'BLOCKED', createdAt, startedAt: null, completedAt: null,
|
|
723
|
-
result: null, error: null, claimedBy: null, claimedAt: null, notifiedAt: null,
|
|
724
|
-
}));
|
|
725
|
-
for (const request of requests) {
|
|
726
|
-
const dependencies = criticId === undefined ? prerequisiteCriticIds(request, graph) : [];
|
|
727
|
-
request.status = dependencies.length ? 'BLOCKED' : 'QUEUED';
|
|
728
|
-
request.blockedReason = dependencies.length ? `Waiting for ${dependencies.join(', ')} to become GREEN.` : null;
|
|
729
|
-
const capability = await requireExecutors().canExecute(copy(request));
|
|
730
|
-
if (!capability?.ok)
|
|
731
|
-
throw new Error(`Cannot execute ${request.criticId}: ${capability?.reason ?? 'No compatible executor.'}`);
|
|
732
|
-
if (request.profile.kind === 'human' && typeof requireExecutors().notifyHuman !== 'function')
|
|
733
|
-
throw new Error('Human execution requires an alarm method.');
|
|
734
|
-
}
|
|
735
|
-
await workspace.assertUnchanged();
|
|
736
|
-
workspace.signal.throwIfAborted();
|
|
737
|
-
ensureOpen();
|
|
738
|
-
const scope = criticId === undefined ? { kind: 'graph' } : { kind: 'critic', criticId: criticId };
|
|
739
|
-
const record = { id, repoId, snapshotHash: descriptor.hash, workspace: descriptor, requesterId, scope, graph, status: 'QUEUED', createdAt };
|
|
740
|
-
transaction(() => {
|
|
741
|
-
db.prepare('INSERT INTO runs(id,created_at,status,data) VALUES (?,?,?,?)').run(id, createdAt, record.status, JSON.stringify(record));
|
|
742
|
-
requests.forEach((request, index) => db.prepare('INSERT INTO requests(id,run_id,ordinal,status,data) VALUES (?,?,?,?,?)').run(request.id, id, index, request.status, JSON.stringify(request)));
|
|
743
|
-
appendEvent(id, null, 'run.submitted', 'Review persisted; a request-scoped worker can execute it.', { snapshotHash: descriptor.hash, mode, requesterId, scope });
|
|
744
|
-
for (const request of requests)
|
|
745
|
-
if (request.status === 'QUEUED')
|
|
746
|
-
appendEvent(id, request.id, 'request.queued', 'Required Artifact reviews are satisfied; the review is ready.');
|
|
747
|
-
appendEvent(id, null, 'workspace.ready', mode === 'copy' ? 'Content-addressed copied workspace is ready.' : 'Current workspace is fixed for this review; changes invalidate it.', { path: descriptor.path, snapshotHash: descriptor.hash });
|
|
748
|
-
});
|
|
749
|
-
changed();
|
|
750
|
-
return required(getRun(id), 'Run');
|
|
751
|
-
}
|
|
752
|
-
finally {
|
|
753
|
-
await workspace.close();
|
|
754
|
-
}
|
|
755
|
-
},
|
|
756
623
|
run,
|
|
757
624
|
reconcile,
|
|
758
625
|
listRuns() {
|
|
@@ -779,7 +646,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
779
646
|
},
|
|
780
647
|
renewHumanTryClaim: humanClaims.renew,
|
|
781
648
|
releaseHumanTryClaim: humanClaims.release,
|
|
782
|
-
confirmHumanClaim: humanClaims.confirm,
|
|
783
649
|
async claimHuman(requestId, reviewerId, { signal } = {}) {
|
|
784
650
|
ensureOpen();
|
|
785
651
|
if (typeof reviewerId !== 'string' || !reviewerId.trim())
|
|
@@ -788,6 +654,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
788
654
|
if (existing)
|
|
789
655
|
reconcile(existing.runId);
|
|
790
656
|
const request = required(requestData(requestId), 'request');
|
|
657
|
+
if (request.configManifest?.version !== 2)
|
|
658
|
+
throw new Error('Historical reviews are available for result lookup only.');
|
|
791
659
|
if (request.status === 'WAITING_HUMAN' && request.claimedBy === reviewerId)
|
|
792
660
|
return request;
|
|
793
661
|
if (request.claimedBy)
|
|
@@ -819,32 +687,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
819
687
|
clearInterval(heartbeat);
|
|
820
688
|
}
|
|
821
689
|
},
|
|
822
|
-
authorizeRemoteHumanTool(requestId, { reviewerId, attemptId, toolName, arguments: args = {} }) {
|
|
823
|
-
const request = assertRemoteClaim(requestId, reviewerId, attemptId);
|
|
824
|
-
if (!request.configManifest)
|
|
825
|
-
throw new Error('Remote tool execution requires a TypeScript tool manifest.');
|
|
826
|
-
const tool = describeReviewTools({ artifacts: request.artifacts, configManifest: request.configManifest, audience: 'human' }).find(value => value.name === toolName);
|
|
827
|
-
if (!tool)
|
|
828
|
-
throw new Error('Unknown registered Human tool.');
|
|
829
|
-
validateArguments(tool.inputSchema, args);
|
|
830
|
-
return tool;
|
|
831
|
-
},
|
|
832
|
-
recordRemoteHumanTool(requestId, { reviewerId, attemptId, toolName, arguments: args = {} }) {
|
|
833
|
-
// Client reports only after real local execution and input validation. A receipt
|
|
834
|
-
// never constitutes content observation or a semantic verdict.
|
|
835
|
-
const request = assertRemoteClaim(requestId, reviewerId, attemptId);
|
|
836
|
-
if (!request.configManifest)
|
|
837
|
-
throw new Error('Remote tool execution requires a TypeScript tool manifest.');
|
|
838
|
-
const tool = describeReviewTools({ artifacts: request.artifacts, configManifest: request.configManifest, audience: 'human' }).find(value => value.name === toolName);
|
|
839
|
-
if (!tool)
|
|
840
|
-
throw new Error('Unknown registered Human tool.');
|
|
841
|
-
validateArguments(tool.inputSchema, args);
|
|
842
|
-
transaction(() => {
|
|
843
|
-
assertRemoteClaim(requestId, reviewerId, attemptId);
|
|
844
|
-
appendEvent(request.runId, requestId, 'human.tool.executed', 'The claimed reviewer reported successful local tool execution.', { name: tool.name, artifactId: tool.artifactId, operation: tool.operation });
|
|
845
|
-
});
|
|
846
|
-
changed();
|
|
847
|
-
},
|
|
848
690
|
async executeHumanTool(requestId, { reviewerId, toolName, arguments: args = {}, signal }) {
|
|
849
691
|
ensureOpen();
|
|
850
692
|
const first = requestData(requestId);
|
|
@@ -855,10 +697,12 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
855
697
|
const current = requestData(requestId);
|
|
856
698
|
if (!current || current.profile.kind !== 'human' || current.status !== 'WAITING_HUMAN')
|
|
857
699
|
throw new Error('Request is not waiting for a human review.');
|
|
700
|
+
if (current.configManifest?.version !== 2)
|
|
701
|
+
throw new Error('Historical reviews are available for result lookup only.');
|
|
858
702
|
if (!reviewerId || current.claimedBy !== reviewerId)
|
|
859
703
|
throw new Error('Only the reviewer who claimed this request can use its tools.');
|
|
860
|
-
if (
|
|
861
|
-
throw new Error('
|
|
704
|
+
if (!ownerAlive(ownerData(current.runId)))
|
|
705
|
+
throw new Error('An in-place review requires its monitoring worker to remain alive.');
|
|
862
706
|
return current;
|
|
863
707
|
};
|
|
864
708
|
const request = assertClaim();
|
|
@@ -870,33 +714,38 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
870
714
|
// remains mandatory because configuration loading and tool execution can mutate it.
|
|
871
715
|
workspace.signal.throwIfAborted();
|
|
872
716
|
inputsValidated = true;
|
|
873
|
-
|
|
874
|
-
if (Object.values(request.artifactTypes).some(type => type.custom))
|
|
875
|
-
throw codedError('Stored TS Artifact configuration has no tool manifest.', 'WORKSPACE_ARTIFACT_MISMATCH');
|
|
876
|
-
const expected = await readStoredArtifactScope({ repoPath: workspace.descriptor.path, criticId: request.criticId });
|
|
877
|
-
if (!expected || expected.profile.kind !== 'human' || !isDeepStrictEqual(expected.artifactTypes, request.artifactTypes) || !isDeepStrictEqual(expected.artifacts, request.artifacts) || !isDeepStrictEqual(expected.artifactGroups ?? [], request.artifactGroups ?? [])) {
|
|
878
|
-
throw codedError('Stored Artifact tools do not match the reviewed workspace configuration.', 'WORKSPACE_ARTIFACT_MISMATCH');
|
|
879
|
-
}
|
|
880
|
-
}
|
|
717
|
+
let timing;
|
|
881
718
|
const registry = await createReviewTools({
|
|
882
|
-
|
|
719
|
+
onExecution: diagnostic => { timing = diagnostic; },
|
|
720
|
+
worktreePath: workspace.descriptor.path, artifacts: request.artifacts,
|
|
883
721
|
configManifest: request.configManifest, criticId: request.criticId, audience: 'human',
|
|
884
722
|
runDir: path.resolve(stateDir, 'runs', request.runId, request.id, 'human-tools'), signal: workspace.signal,
|
|
885
723
|
});
|
|
886
724
|
try {
|
|
887
725
|
registry.validateArguments(toolName, args);
|
|
888
726
|
assertClaim();
|
|
889
|
-
const
|
|
727
|
+
const attempt = await registry.call(toolName, args).then(result => ({ ok: true, result }), error => ({ ok: false, error: error }));
|
|
890
728
|
await workspace.assertUnchanged();
|
|
891
729
|
workspace.signal.throwIfAborted();
|
|
892
730
|
assertClaim();
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
731
|
+
if (timing) {
|
|
732
|
+
const record = () => transaction(() => {
|
|
733
|
+
assertClaim();
|
|
734
|
+
appendEvent(request.runId, requestId, 'human.tool.executed', 'The claimed reviewer attempted a registered Artifact tool.', { ...timing, ...(attempt.ok && attempt.result.isError ? { isError: true } : {}) });
|
|
735
|
+
});
|
|
736
|
+
if (attempt.ok)
|
|
737
|
+
record();
|
|
738
|
+
else {
|
|
739
|
+
try {
|
|
740
|
+
record();
|
|
741
|
+
}
|
|
742
|
+
catch { /* Optional failure diagnostics never replace the original error. */ }
|
|
743
|
+
}
|
|
744
|
+
changed();
|
|
745
|
+
}
|
|
746
|
+
if (!attempt.ok)
|
|
747
|
+
throw attempt.error;
|
|
748
|
+
return attempt.result;
|
|
900
749
|
}
|
|
901
750
|
finally {
|
|
902
751
|
await registry.close();
|
|
@@ -928,6 +777,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
928
777
|
const request = requestData(requestId);
|
|
929
778
|
if (!request || request.profile.kind !== 'human' || request.status !== 'WAITING_HUMAN')
|
|
930
779
|
throw new Error('Request is not waiting for a human review.');
|
|
780
|
+
if (request.configManifest?.version !== 2)
|
|
781
|
+
throw new Error('Historical reviews are available for result lookup only.');
|
|
931
782
|
if (!reviewerId || request.claimedBy !== reviewerId)
|
|
932
783
|
throw new Error('Only the reviewer who claimed this request can submit its result.');
|
|
933
784
|
if (!request.notifiedAt)
|
|
@@ -938,7 +789,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
938
789
|
workspace = await workspaceAdapter.reopenWorkspace(request.workspace);
|
|
939
790
|
// Reopening validates input under its integrity policy. No asynchronous work or
|
|
940
791
|
// user code runs between this boundary and committing the submitted result.
|
|
941
|
-
assertGeneratedReviewInputs(request);
|
|
942
792
|
workspace.signal.throwIfAborted();
|
|
943
793
|
ensureOpen();
|
|
944
794
|
inputsValidated = true;
|
|
@@ -948,8 +798,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
948
798
|
throw new Error('This human review has already completed or failed.');
|
|
949
799
|
if (current.claimedBy !== reviewerId)
|
|
950
800
|
throw new Error('Only the reviewer who claimed this request can submit its result.');
|
|
951
|
-
if (
|
|
952
|
-
throw new Error('
|
|
801
|
+
if (!ownerAlive(ownerData(current.runId)))
|
|
802
|
+
throw new Error('An in-place review requires its monitoring worker to remain alive.');
|
|
953
803
|
required(workspace, 'Workspace').signal.throwIfAborted();
|
|
954
804
|
finishWithin(requestId, { result: validated });
|
|
955
805
|
});
|