@ccdd/project 3.3.1 → 4.0.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-CaxsIXtN.js +27 -0
- package/dist/monitor-ui/assets/ValidationView-BMqI2XQ5.css +1 -0
- package/dist/monitor-ui/assets/ValidationView-CGW6-Ljw.js +1 -0
- package/dist/monitor-ui/assets/{elk-api-D5d3gWoP.js → elk-api-DIQ2_nge.js} +1 -1
- package/dist/monitor-ui/assets/{elk.bundled-0eaKiuBP.js → elk.bundled-BRtnLd_M.js} +1 -1
- package/dist/monitor-ui/assets/{index-DDwx11D7.css → index-D0yfhUlt.css} +1 -1
- package/dist/monitor-ui/assets/index-LyUbXn5P.js +7 -0
- 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 +5 -6
- 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 +14 -18
- 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 +35 -221
- 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 +12 -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 +2 -3
- package/dist/src/executors/pi.js.map +1 -1
- package/dist/src/executors/process.js +9 -0
- package/dist/src/executors/process.js.map +1 -1
- 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 +2 -2
- 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 +34 -78
- package/dist/src/tools/diagnostics.js +1 -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 +10 -14
- package/dist/src/tools/runner.js +144 -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,15 @@ 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';
|
|
11
10
|
import { createReviewTools } from '../tools/runner.js';
|
|
12
|
-
import { describeReviewTools } from '../tools/runner.js';
|
|
13
|
-
import { validateArguments } from '../tools/schema.js';
|
|
14
11
|
import { createHumanClaims, HUMAN_PREPARATION_LEASE_MS } from './human-claims.js';
|
|
15
12
|
import { prepareHumanReview } from '../executors/human-preparation.js';
|
|
16
13
|
import { prepareWorkspace, reopenWorkspace } from '../workspaces/index.js';
|
|
17
14
|
import { createProjectSnapshot } from '../project/identity.js';
|
|
18
|
-
import { assertGeneratedReviewInputs, prepareArtifactInputs } from '../artifacts/sources.js';
|
|
19
15
|
import { includedCritics, planProject } from '../project/query.js';
|
|
20
16
|
import { readEvidence } from '../project/store.js';
|
|
21
17
|
const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -142,7 +138,7 @@ function prepareStateDirectory(repoPath, stateDir) {
|
|
|
142
138
|
const canonical = canonicalFuturePath(stateDir);
|
|
143
139
|
const relative = path.relative(repoPath, canonical);
|
|
144
140
|
if (relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))) {
|
|
145
|
-
throw codedError('CCDD state, logs and
|
|
141
|
+
throw codedError('CCDD state, logs and review outputs must be outside the reviewed repository.', 'WORKSPACE_UNSAFE');
|
|
146
142
|
}
|
|
147
143
|
fs.mkdirSync(canonical, { recursive: true, mode: 0o700 });
|
|
148
144
|
return fs.realpathSync(canonical);
|
|
@@ -239,59 +235,17 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
239
235
|
event: (request, type, message) => appendEvent(request.runId, request.id, type, message),
|
|
240
236
|
assertWaiting: request => {
|
|
241
237
|
ensureOpen();
|
|
238
|
+
if (request.configManifest?.version !== 2)
|
|
239
|
+
throw new Error('Historical reviews are available for result lookup only.');
|
|
242
240
|
if (!request.notifiedAt)
|
|
243
241
|
throw new Error('Human alarm delivery is still pending.');
|
|
244
|
-
if (
|
|
245
|
-
throw new Error('
|
|
242
|
+
if (!ownerAlive(ownerData(request.runId)))
|
|
243
|
+
throw new Error('An in-place review requires its monitoring worker to remain alive.');
|
|
246
244
|
},
|
|
247
245
|
});
|
|
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
246
|
function refreshReadinessWithin(runId) {
|
|
293
247
|
const run = required(runData(runId), 'Run'), requests = runRequests(runId);
|
|
294
|
-
if (run.project) {
|
|
248
|
+
if (run.project?.version === 2) {
|
|
295
249
|
if (terminal.has(run.status))
|
|
296
250
|
return;
|
|
297
251
|
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 +262,12 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
308
262
|
}
|
|
309
263
|
return;
|
|
310
264
|
}
|
|
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
|
-
}
|
|
265
|
+
throw new Error('Historical Runs cannot be resumed; submit a new validation request.');
|
|
330
266
|
}
|
|
331
267
|
const updateRunStatus = (runId) => {
|
|
332
268
|
const states = runRequests(runId).map(request => request.status);
|
|
333
269
|
const run = required(runData(runId), 'Run');
|
|
334
|
-
if (run.project) {
|
|
270
|
+
if (run.project?.version === 2) {
|
|
335
271
|
if (terminal.has(run.status))
|
|
336
272
|
return;
|
|
337
273
|
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 +284,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
348
284
|
}
|
|
349
285
|
return;
|
|
350
286
|
}
|
|
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
|
-
}
|
|
287
|
+
throw new Error('Historical Runs are available for result lookup only.');
|
|
364
288
|
};
|
|
365
289
|
function finishWithin(requestId, outcome, expectedStates = ['RUNNING', 'WAITING_HUMAN']) {
|
|
366
290
|
const { result, error } = outcome, hasError = Object.hasOwn(outcome, 'error');
|
|
@@ -394,7 +318,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
394
318
|
saveRequest(request);
|
|
395
319
|
appendEvent(runId, request.id, 'request.error', request.error, { status: request.status, ...(request.errorCode ? { code: request.errorCode } : {}) });
|
|
396
320
|
}
|
|
397
|
-
if (run.project) {
|
|
321
|
+
if (run.project?.version === 2) {
|
|
398
322
|
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
323
|
run.project.evidenceRequestIds = [...new Set(plan.critics.flatMap(c => c.result ? [c.result.requestId] : []))];
|
|
400
324
|
}
|
|
@@ -408,13 +332,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
408
332
|
const owner = ownerData(runId);
|
|
409
333
|
if (!owner || ownerAlive(owner))
|
|
410
334
|
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
|
-
}
|
|
335
|
+
failWithin(runId, codedError('The review worker exited before completing its work. Submit a new run to retry.', 'WORKER_EXITED'));
|
|
418
336
|
db.prepare('DELETE FROM run_owners WHERE run_id = ? AND token = ?').run(runId, owner.token);
|
|
419
337
|
appendEvent(runId, null, 'worker.exited', 'The previous review worker is no longer running.');
|
|
420
338
|
}
|
|
@@ -455,7 +373,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
455
373
|
request.startedAt = now();
|
|
456
374
|
request.blockedReason = null;
|
|
457
375
|
saveRequest(request);
|
|
458
|
-
appendEvent(runId, request.id, 'request.started', `${request.title}: reviewing
|
|
376
|
+
appendEvent(runId, request.id, 'request.started', `${request.title}: reviewing the supplied workspace.`, { snapshotHash: workspace.descriptor.hash });
|
|
459
377
|
updateRunStatus(runId);
|
|
460
378
|
});
|
|
461
379
|
changed();
|
|
@@ -463,7 +381,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
463
381
|
signal.throwIfAborted();
|
|
464
382
|
await workspace.assertUnchanged();
|
|
465
383
|
const request = required(requestData(requestId), 'Request');
|
|
466
|
-
assertGeneratedReviewInputs(request);
|
|
467
384
|
const runDir = path.join(stateDir, 'runs', runId, request.id);
|
|
468
385
|
fs.mkdirSync(runDir, { recursive: true, mode: 0o700 });
|
|
469
386
|
if (required(runData(runId), 'Run').project) {
|
|
@@ -543,6 +460,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
543
460
|
stored = runData(runId);
|
|
544
461
|
if (!stored)
|
|
545
462
|
throw new Error('Unknown Run.');
|
|
463
|
+
if (stored.project?.version !== 2)
|
|
464
|
+
throw new Error('Historical Runs cannot be resumed; submit a new validation request.');
|
|
546
465
|
if (terminal.has(stored.status))
|
|
547
466
|
return false;
|
|
548
467
|
if (!stored.workspace)
|
|
@@ -598,7 +517,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
598
517
|
inFlight.set(queued.id, { kind: queued.profile.kind, promise });
|
|
599
518
|
}
|
|
600
519
|
if (inFlight.size) {
|
|
601
|
-
// Human alarms and independent
|
|
520
|
+
// Human alarms and independent evaluations progress together.
|
|
602
521
|
await Promise.race([...inFlight.values()].map(item => item.promise).concat(delay(50, undefined, { signal: reviewSignal })));
|
|
603
522
|
continue;
|
|
604
523
|
}
|
|
@@ -606,24 +525,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
606
525
|
if (waiting.length) {
|
|
607
526
|
if (waiting.some(request => !request.notifiedAt))
|
|
608
527
|
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.
|
|
528
|
+
// The workspace observer stays alive with filesystem events and metadata polls.
|
|
627
529
|
// Notification already crossed its final content boundary; idle waiting
|
|
628
530
|
// must not rehash the entire workspace on every scheduling iteration.
|
|
629
531
|
await delay(100, undefined, { signal: reviewSignal });
|
|
@@ -661,18 +563,17 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
661
563
|
return entry.promise;
|
|
662
564
|
}
|
|
663
565
|
return {
|
|
664
|
-
async submitProject({
|
|
566
|
+
async submitProject({ requesterId = 'cli', selection, recursive = false, force = false, ...removed }) {
|
|
665
567
|
ensureOpen();
|
|
666
568
|
requireExecutors();
|
|
569
|
+
if ('mode' in removed)
|
|
570
|
+
throw new Error('Workspace modes are no longer supported; supply an unchanged workspace.');
|
|
667
571
|
if (!requesterId.trim() || requesterId.length > 200)
|
|
668
572
|
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
573
|
await requireExecutors().validateWorkspace?.(repoPath);
|
|
672
|
-
const workspace = await workspaceAdapter.prepareWorkspace({ repoPath, stateDir,
|
|
574
|
+
const workspace = await workspaceAdapter.prepareWorkspace({ repoPath, stateDir, integrity: workspaceIntegrity });
|
|
673
575
|
try {
|
|
674
|
-
const
|
|
675
|
-
const config = await prepareArtifactInputs(loaded.config, workspace.descriptor.path, 'review', workspace.signal);
|
|
576
|
+
const { config } = await readWorkspaceConfig(workspace.descriptor.path, workspace.signal);
|
|
676
577
|
const snapshot = await createProjectSnapshot(config, workspace.descriptor.path, workspace.descriptor.hash, workspace.signal, workspace.descriptor.integrity);
|
|
677
578
|
const ids = includedCritics(snapshot, selection, recursive);
|
|
678
579
|
const templates = [];
|
|
@@ -680,7 +581,7 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
680
581
|
templates.push(...await prepareReviewRequests({ repoPath: workspace.descriptor.path, repoId, snapshotHash: workspace.descriptor.hash, criticId: id, preparedConfig: config }));
|
|
681
582
|
const id = randomUUID(), createdAt = now();
|
|
682
583
|
const record = { id, repoId, snapshotHash: workspace.descriptor.hash, workspace: workspace.descriptor, requesterId,
|
|
683
|
-
scope: { kind: 'project' }, graph: createGraphDefinition(config), project: { version:
|
|
584
|
+
scope: { kind: 'project' }, graph: createGraphDefinition(config), project: { version: 2, snapshot, selection, recursive, force, templates }, status: 'QUEUED', createdAt };
|
|
684
585
|
await workspace.assertUnchanged();
|
|
685
586
|
workspace.signal.throwIfAborted();
|
|
686
587
|
transaction(() => {
|
|
@@ -696,63 +597,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
696
597
|
await workspace.close();
|
|
697
598
|
}
|
|
698
599
|
},
|
|
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
600
|
run,
|
|
757
601
|
reconcile,
|
|
758
602
|
listRuns() {
|
|
@@ -779,7 +623,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
779
623
|
},
|
|
780
624
|
renewHumanTryClaim: humanClaims.renew,
|
|
781
625
|
releaseHumanTryClaim: humanClaims.release,
|
|
782
|
-
confirmHumanClaim: humanClaims.confirm,
|
|
783
626
|
async claimHuman(requestId, reviewerId, { signal } = {}) {
|
|
784
627
|
ensureOpen();
|
|
785
628
|
if (typeof reviewerId !== 'string' || !reviewerId.trim())
|
|
@@ -788,6 +631,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
788
631
|
if (existing)
|
|
789
632
|
reconcile(existing.runId);
|
|
790
633
|
const request = required(requestData(requestId), 'request');
|
|
634
|
+
if (request.configManifest?.version !== 2)
|
|
635
|
+
throw new Error('Historical reviews are available for result lookup only.');
|
|
791
636
|
if (request.status === 'WAITING_HUMAN' && request.claimedBy === reviewerId)
|
|
792
637
|
return request;
|
|
793
638
|
if (request.claimedBy)
|
|
@@ -819,32 +664,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
819
664
|
clearInterval(heartbeat);
|
|
820
665
|
}
|
|
821
666
|
},
|
|
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
667
|
async executeHumanTool(requestId, { reviewerId, toolName, arguments: args = {}, signal }) {
|
|
849
668
|
ensureOpen();
|
|
850
669
|
const first = requestData(requestId);
|
|
@@ -855,10 +674,12 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
855
674
|
const current = requestData(requestId);
|
|
856
675
|
if (!current || current.profile.kind !== 'human' || current.status !== 'WAITING_HUMAN')
|
|
857
676
|
throw new Error('Request is not waiting for a human review.');
|
|
677
|
+
if (current.configManifest?.version !== 2)
|
|
678
|
+
throw new Error('Historical reviews are available for result lookup only.');
|
|
858
679
|
if (!reviewerId || current.claimedBy !== reviewerId)
|
|
859
680
|
throw new Error('Only the reviewer who claimed this request can use its tools.');
|
|
860
|
-
if (
|
|
861
|
-
throw new Error('
|
|
681
|
+
if (!ownerAlive(ownerData(current.runId)))
|
|
682
|
+
throw new Error('An in-place review requires its monitoring worker to remain alive.');
|
|
862
683
|
return current;
|
|
863
684
|
};
|
|
864
685
|
const request = assertClaim();
|
|
@@ -870,16 +691,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
870
691
|
// remains mandatory because configuration loading and tool execution can mutate it.
|
|
871
692
|
workspace.signal.throwIfAborted();
|
|
872
693
|
inputsValidated = true;
|
|
873
|
-
if (!request.configManifest) {
|
|
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
|
-
}
|
|
881
694
|
const registry = await createReviewTools({
|
|
882
|
-
worktreePath: workspace.descriptor.path, artifacts: request.artifacts,
|
|
695
|
+
worktreePath: workspace.descriptor.path, artifacts: request.artifacts,
|
|
883
696
|
configManifest: request.configManifest, criticId: request.criticId, audience: 'human',
|
|
884
697
|
runDir: path.resolve(stateDir, 'runs', request.runId, request.id, 'human-tools'), signal: workspace.signal,
|
|
885
698
|
});
|
|
@@ -928,6 +741,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
928
741
|
const request = requestData(requestId);
|
|
929
742
|
if (!request || request.profile.kind !== 'human' || request.status !== 'WAITING_HUMAN')
|
|
930
743
|
throw new Error('Request is not waiting for a human review.');
|
|
744
|
+
if (request.configManifest?.version !== 2)
|
|
745
|
+
throw new Error('Historical reviews are available for result lookup only.');
|
|
931
746
|
if (!reviewerId || request.claimedBy !== reviewerId)
|
|
932
747
|
throw new Error('Only the reviewer who claimed this request can submit its result.');
|
|
933
748
|
if (!request.notifiedAt)
|
|
@@ -938,7 +753,6 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
938
753
|
workspace = await workspaceAdapter.reopenWorkspace(request.workspace);
|
|
939
754
|
// Reopening validates input under its integrity policy. No asynchronous work or
|
|
940
755
|
// user code runs between this boundary and committing the submitted result.
|
|
941
|
-
assertGeneratedReviewInputs(request);
|
|
942
756
|
workspace.signal.throwIfAborted();
|
|
943
757
|
ensureOpen();
|
|
944
758
|
inputsValidated = true;
|
|
@@ -948,8 +762,8 @@ export function createBroker({ repoPath, stateDir, repoId = 'demo', executors, w
|
|
|
948
762
|
throw new Error('This human review has already completed or failed.');
|
|
949
763
|
if (current.claimedBy !== reviewerId)
|
|
950
764
|
throw new Error('Only the reviewer who claimed this request can submit its result.');
|
|
951
|
-
if (
|
|
952
|
-
throw new Error('
|
|
765
|
+
if (!ownerAlive(ownerData(current.runId)))
|
|
766
|
+
throw new Error('An in-place review requires its monitoring worker to remain alive.');
|
|
953
767
|
required(workspace, 'Workspace').signal.throwIfAborted();
|
|
954
768
|
finishWithin(requestId, { result: validated });
|
|
955
769
|
});
|